Turn-Based RPG (JRPG)
Menus, math, and party management — your spreadsheet has a storyline.
"In my experience, there is no such thing as luck." — Obi-Wan Kenobi
Prerequisites
| Module | What You Used From It |
|---|---|
| Module 07 - Roguelike | Turn-based input handling, discrete game states, tile-based world representation |
| Module 10 - Deckbuilder | Turn phase structure, hand/resource management per turn, sequential decision-making |
Week 1: History & Design Theory
The Origin
The turn-based RPG crystallized in Japan during the late 1980s, when Dragon Quest (1986) and Final Fantasy (1987) translated Western tabletop RPG concepts — stats, levels, parties, random encounters — into a format designed for console controllers and television screens. The critical insight was replacing the freeform decision-making of tabletop games with menu-driven combat: the player selects from a fixed set of actions (Attack, Magic, Item, Defend), and the outcome is resolved through deterministic or semi-random formulas.
How the Genre Evolved
Final Fantasy VI (1994): Squaresoft's masterpiece refined the Active Time Battle (ATB) system, where a per-character timer fills based on their Speed stat. FFVI also set the standard for ensemble casts — fourteen playable characters, each with unique abilities — proving that party management and character specialization could carry emotional and mechanical depth simultaneously.
Chrono Trigger (1995): Squaresoft and Enix's collaboration eliminated random encounters entirely, showing enemies on the overworld. Its "Dual Tech" and "Triple Tech" system demonstrated that party composition was not just a stat optimization problem but a creative, expressive one.
Persona 5 (2016): Atlus reimagined the JRPG for a modern audience. Its "One More" system — exploit an enemy's weakness and you get an extra turn — made elemental weakness tables feel dynamic and rewarding rather than rote. Persona 5 proved that turn-based combat could feel fast, flashy, and contemporary.
What Makes a Turn-Based RPG "Great"
A great JRPG makes every menu selection feel meaningful. The best entries ensure that "Attack" is rarely the optimal choice — the player must consider elemental weaknesses, party member roles, resource conservation (MP, items), status effects, and turn order to make the right call. Outside combat, the progression loop — gaining XP, leveling up, choosing equipment, building your party — must create a steady drumbeat of "I am getting stronger."
The Essential Mechanic
Selecting actions from menus where stats, types, and party composition determine outcomes.
Week 2: Build the MVP
What You're Building
A combat encounter system where the player controls a party of 2-3 characters against groups of enemies, selecting actions from menus each turn. Characters have stats, elemental types, and abilities. Enemies have weaknesses. Victory grants XP that leads to level-ups.
Core Concepts (Must Implement)
1. Turn Order / Initiative System
A speed stat determines which characters and enemies act first each round. Fast characters act first but may be fragile; slow tanks hit hard but enemies may strike before them.
function calculate_turn_order(all_combatants):
sorted = all_combatants.sort_by(c => c.stats.speed + random(0, 5), descending)
return sorted
function run_combat_round():
turn_order = calculate_turn_order(party + enemies)
for combatant in turn_order:
if combatant.is_dead: continue
if combatant.is_player_controlled:
action = show_menu_and_wait_for_input(combatant)
else:
action = enemy_ai_choose_action(combatant)
execute_action(action)
check_for_battle_end() Why it matters: Turn order transforms combat from simultaneous to sequential, creating information asymmetry. Speed becomes as important as raw power.
Adjust the sliders to see how Attack, Defense, Elemental Multiplier, and Crit Chance affect the damage formula. Click "Roll Damage" to see a random outcome with crit/non-crit results. The formula is shown live.
2. Stat-Based Damage Formulas
The mathematical backbone of RPG combat. Attack power minus defense, multiplied by elemental modifiers, with variance for unpredictability.
function calculate_damage(attacker, defender, skill):
base = (attacker.stats.attack * skill.power) / 100
reduced = base - (defender.stats.defense * 0.5)
reduced = max(reduced, 1)
type_mult = get_type_multiplier(skill.element, defender.element)
# 2.0 = weak to, 1.0 = neutral, 0.5 = resists
crit_roll = random(0, 100)
crit_mult = 1.5 if crit_roll < attacker.stats.luck else 1.0
variance = random(0.9, 1.1)
final_damage = floor(reduced * type_mult * crit_mult * variance)
return { damage: final_damage, was_crit: crit_mult > 1.0 } Why it matters: The damage formula is the contract between the game and the player. Transparent math builds trust; opaque math breeds frustration.
Adjust speed stats with the sliders. The timeline shows turn order sorted by speed. Party members are blue, enemies are red. Watch how changing speeds rearranges the timeline in real time.
3. Experience / Leveling System
After each battle, characters earn experience points. When XP exceeds a threshold, the character levels up, stats increase, and new abilities may unlock.
function xp_required_for_level(level):
return floor(100 * (level ^ 1.5))
function award_xp(party, enemies_defeated):
total_xp = sum(e.xp_value for e in enemies_defeated)
per_member = floor(total_xp / len(party))
for character in party:
character.xp += per_member
while character.xp >= xp_required_for_level(character.level + 1):
character.xp -= xp_required_for_level(character.level + 1)
level_up(character)
function level_up(character):
character.level += 1
character.stats.hp_max += character.growth_rates.hp
character.stats.attack += character.growth_rates.attack
character.stats.defense += character.growth_rates.defense
character.stats.speed += character.growth_rates.speed
character.stats.hp = character.stats.hp_max Why it matters: The leveling system is the primary reward loop. It gives the player a tangible sense of growth.
4. Party Management
Multiple characters with different roles — a tank who absorbs damage, a healer who restores HP, a damage dealer who exploits weaknesses.
warrior = {
name: "Kael", role: "tank",
stats: { hp: 120, attack: 15, defense: 18, speed: 6, mp: 10 },
skills: [
{ name: "Shield Bash", power: 80, element: "physical", mp_cost: 0 },
{ name: "Taunt", effect: "force_target_self", mp_cost: 5 }
]
}
mage = {
name: "Lyra", role: "dps",
stats: { hp: 55, attack: 8, defense: 7, speed: 12, mp: 50 },
skills: [
{ name: "Fireball", power: 120, element: "fire", mp_cost: 8 },
{ name: "Ice Shard", power: 120, element: "ice", mp_cost: 8 }
]
}
healer = {
name: "Sera", role: "healer",
stats: { hp: 70, attack: 6, defense: 10, speed: 10, mp: 40 },
skills: [
{ name: "Heal", effect: "restore_hp", power: 50, mp_cost: 6 },
{ name: "Cure", effect: "remove_status", mp_cost: 4 }
]
} Why it matters: Party management is where strategy lives between battles.
5. Elemental Weakness Tables
A rock-paper-scissors system of types where fire beats ice, ice beats wind, wind beats earth, and so on.
weakness_table = {
"fire": { "fire": 0.5, "ice": 2.0, "wind": 1.0, "earth": 1.0 },
"ice": { "fire": 0.5, "ice": 0.5, "wind": 2.0, "earth": 1.0 },
"wind": { "fire": 1.0, "ice": 1.0, "wind": 0.5, "earth": 2.0 },
"earth": { "fire": 2.0, "ice": 1.0, "wind": 0.5, "earth": 0.5 },
"physical":{ "fire": 1.0, "ice": 1.0, "wind": 1.0, "earth": 1.0 }
}
function get_type_multiplier(attack_element, defender_element):
if attack_element in weakness_table:
return weakness_table[attack_element].get(defender_element, 1.0)
return 1.0 Why it matters: Weakness tables add a knowledge layer to combat. This rewards learning and encourages experimentation.
6. Menu-Based Combat UI
function show_combat_menu(character):
choice = show_menu(["Attack", "Magic", "Item", "Defend", "Flee"])
if choice == "Attack":
target = select_target(enemies_alive)
return { type: "attack", user: character, target: target }
elif choice == "Magic":
usable_spells = character.skills.filter(s => s.mp_cost <= character.stats.mp)
spell = show_menu(usable_spells)
target = select_target(get_valid_targets(spell))
return { type: "magic", user: character, skill: spell, target: target }
elif choice == "Item":
item = show_menu(inventory.filter(i => i.usable_in_battle))
target = select_target(get_valid_targets(item))
return { type: "item", user: character, item: item, target: target }
elif choice == "Defend":
character.defending = true
return { type: "defend", user: character } Why it matters: The menu IS the gameplay. Good menu design surfaces relevant information and allows quick navigation.
A turn-based battle! Select Attack, Magic, or Defend for each party member. Exploit elemental weaknesses (fire beats ice, ice beats wind, wind beats earth). HP bars show health. Defeat all enemies to win!
7. Random Encounters / Encounter Rate
function on_player_step(current_area):
steps_since_encounter += 1
encounter_chance = steps_since_encounter / area_data[current_area].base_encounter_rate
if random(0.0, 1.0) < encounter_chance:
steps_since_encounter = 0
enemy_group = weighted_random(area_data[current_area].enemy_pool)
start_battle(enemy_group) Why it matters: Encounter rate is one of the most delicate balance points in JRPG design. The distribution of encounters paces the entire game.
Stretch Goals
- Status effects: Poison (damage over time), sleep (skip turn), blind (reduced accuracy).
- Active Time Battle: Replace pure turn-based with per-character timers.
- Equipment system: Weapons and armor that modify stats.
- Boss encounter: A multi-phase boss with changing weaknesses.
MVP Spec
| Element | Minimum Viable Version |
|---|---|
| Party | 2-3 characters with distinct stats, roles, and 2-3 skills each |
| Enemies | 3-4 enemy types with different elements, stats, and XP values |
| Combat | Turn-based with initiative, menu selection, damage formulas |
| Weakness Table | At least 3 elements with a clear strength/weakness cycle |
| Leveling | XP gain after battle, level-ups that increase stats |
| Encounters | 3-4 sequential battles |
| UI | Combat menu with Attack / Magic / Item / Defend options |
| Win/Lose | Party wipe = game over, final battle victory = win screen |
Deliverable
A playable turn-based combat game where the player commands a party through a sequence of battles. The final battle should require the player to use what they have learned about the weakness system and party roles to succeed.
Analogies by Background
These analogies map game dev concepts to patterns you already know.
For Backend Developers
| Core Concept | Analogy |
|---|---|
| Turn Order / Initiative | Like a priority queue — tasks (combatants) are processed in order of priority (speed stat) |
| Stat-Based Damage Formulas | Like a business rules engine — multiple inputs processed through a deterministic formula |
| Experience / Leveling | Like cache warming — the system starts cold and gradually increases capacity (stats) |
| Party Management | Like microservice architecture — each service has a specialized role |
| Elemental Weakness Table | Like a routing table or compatibility matrix — given input type A and target type B, look up the multiplier |
| Menu-Based Combat UI | Like a CLI with nested subcommands |
| Random Encounters | Like a probabilistic rate limiter — the chance of an event increases over time |
For Frontend Developers
| Core Concept | Analogy |
|---|---|
| Turn Order / Initiative | Like z-index or rendering order — elements are processed in a specific stacking order |
| Stat-Based Damage Formulas | Like CSS specificity calculations — multiple properties combine through a formula |
| Experience / Leveling | Like progressive enhancement — capabilities are unlocked as the user demonstrates readiness |
| Party Management | Like component composition — each component has a responsibility |
| Elemental Weakness Table | Like a theme token lookup |
| Menu-Based Combat UI | Like a nested dropdown menu |
| Random Encounters | Like showing a modal after N scroll events |
For Data / ML Engineers
| Core Concept | Analogy |
|---|---|
| Turn Order / Initiative | Like job scheduling in a compute cluster — tasks with higher priority are allocated resources first |
| Stat-Based Damage Formulas | Like a linear regression with multiple features — the formula is the model |
| Experience / Leveling | Like training epochs — each battle is an epoch, level-up is crossing a performance threshold |
| Party Management | Like an ensemble model — each member specializes in a different aspect |
| Elemental Weakness Table | Like a confusion matrix or lookup table |
| Menu-Based Combat UI | Like a decision tree — at each node the player selects a branch |
| Random Encounters | Like Poisson process sampling |
Discussion Questions
- JRPGs live or die on their damage formulas, yet most players never see the math. How transparent should the numbers be?
- Random encounters were a hardware limitation solution. What does randomness add that visible enemies on the map do not?
- The "holy trinity" of tank/healer/DPS appears everywhere. Can you design a compelling party system without these archetypes?
- Persona 5's "One More" system makes the weakness table feel urgent. How does layering a bonus on top of a multiplier change behavior?