Module 20

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

ModuleWhat You Used From It
Module 07 - RoguelikeTurn-based input handling, discrete game states, tile-based world representation
Module 10 - DeckbuilderTurn 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.

Demo: Damage Formula Calculator

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.

Demo: Turn Order Visualizer

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.

Demo: Mini RPG Battle

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

MVP Spec

ElementMinimum Viable Version
Party2-3 characters with distinct stats, roles, and 2-3 skills each
Enemies3-4 enemy types with different elements, stats, and XP values
CombatTurn-based with initiative, menu selection, damage formulas
Weakness TableAt least 3 elements with a clear strength/weakness cycle
LevelingXP gain after battle, level-ups that increase stats
Encounters3-4 sequential battles
UICombat menu with Attack / Magic / Item / Defend options
Win/LoseParty 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 ConceptAnalogy
Turn Order / InitiativeLike a priority queue — tasks (combatants) are processed in order of priority (speed stat)
Stat-Based Damage FormulasLike a business rules engine — multiple inputs processed through a deterministic formula
Experience / LevelingLike cache warming — the system starts cold and gradually increases capacity (stats)
Party ManagementLike microservice architecture — each service has a specialized role
Elemental Weakness TableLike a routing table or compatibility matrix — given input type A and target type B, look up the multiplier
Menu-Based Combat UILike a CLI with nested subcommands
Random EncountersLike a probabilistic rate limiter — the chance of an event increases over time

For Frontend Developers

Core ConceptAnalogy
Turn Order / InitiativeLike z-index or rendering order — elements are processed in a specific stacking order
Stat-Based Damage FormulasLike CSS specificity calculations — multiple properties combine through a formula
Experience / LevelingLike progressive enhancement — capabilities are unlocked as the user demonstrates readiness
Party ManagementLike component composition — each component has a responsibility
Elemental Weakness TableLike a theme token lookup
Menu-Based Combat UILike a nested dropdown menu
Random EncountersLike showing a modal after N scroll events

For Data / ML Engineers

Core ConceptAnalogy
Turn Order / InitiativeLike job scheduling in a compute cluster — tasks with higher priority are allocated resources first
Stat-Based Damage FormulasLike a linear regression with multiple features — the formula is the model
Experience / LevelingLike training epochs — each battle is an epoch, level-up is crossing a performance threshold
Party ManagementLike an ensemble model — each member specializes in a different aspect
Elemental Weakness TableLike a confusion matrix or lookup table
Menu-Based Combat UILike a decision tree — at each node the player selects a branch
Random EncountersLike Poisson process sampling

Discussion Questions

  1. JRPGs live or die on their damage formulas, yet most players never see the math. How transparent should the numbers be?
  2. Random encounters were a hardware limitation solution. What does randomness add that visible enemies on the map do not?
  3. The "holy trinity" of tank/healer/DPS appears everywhere. Can you design a compelling party system without these archetypes?
  4. Persona 5's "One More" system makes the weakness table feel urgent. How does layering a bonus on top of a multiplier change behavior?