Skip to main content

main.py

"""Main game entry point: title screen, character creation, game loop."""
import sys
import os

sys.path.insert(0, os.path.dirname(__file__))

from data_types import *
from character import create_player_character, JOB_BASE_STATS
from game_state import GameState
from skills_db import SKILL_DB, JOB_SKILL_POOL


def clear():
    os.system('clear' if os.name == 'posix' else 'cls')


def print_title():
    clear()
    print("""
╔══════════════════════════════════════════════════════════════╗
║                                                              ║
║          ✦  CHRONICLES OF THE ETERNAL TURN  ✦                ║
║                  A JRPG CLI ADVENTURE                        ║
║                                                              ║
╚══════════════════════════════════════════════════════════════╝
""")


def create_character() -> tuple:
    clear()
    print("╔══════════════════════════════════════════════════════════════╗")
    print("║                  CHARACTER CREATION                          ║")
    print("╚══════════════════════════════════════════════════════════════╝")

    while True:
        name = input("\n  Enter your hero's name: ").strip()
        if name:
            break
        print("  Please enter a name.")

    weapons = list(WeaponType)
    weapon_descs = {
        WeaponType.SWORD:   "Balanced. Warriors, Knights, Paladins",
        WeaponType.SPEAR:   "Long reach. Spearman, Dragoon",
        WeaponType.AXE:     "High power. Berserker, Monk",
        WeaponType.DAGGER:  "Fast & precise. Assassin",
        WeaponType.BOW:     "Ranged. Ranger, Hunter",
        WeaponType.STAFF:   "Magical focus. All Mages and Healers",
    }
    print("\n  ── Choose Your Weapon ──")
    for i, w in enumerate(weapons):
        print(f"    [{i+1}] {w.value:10s} - {weapon_descs[w]}")
    while True:
        try:
            wi = int(input("  > ")) - 1
            if 0 <= wi < len(weapons): break
        except ValueError: pass
        print("  Invalid.")
    weapon = weapons[wi]

    elements = [e for e in Element if e != Element.NONE]
    elem_descs = {
        Element.FIRE:      "Offensive burn effects",
        Element.ICE:       "Freeze and slow foes",
        Element.LIGHTNING: "Chain damage, high speed",
        Element.WIND:      "Evasive, speed-based",
        Element.EARTH:     "Defense-break, sturdy",
        Element.LIGHT:     "Holy power, vs Undead/Dark",
        Element.DARK:      "Debuff and drain",
    }
    print("\n  ── Choose Your Element ──")
    for i, e in enumerate(elements):
        print(f"    [{i+1}] {e.value:10s} - {elem_descs[e]}")
    while True:
        try:
            ei = int(input("  > ")) - 1
            if 0 <= ei < len(elements): break
        except ValueError: pass
        print("  Invalid.")
    element = elements[ei]

    all_jobs = list(JobClass)
    job_weapon_affinity = {
        WeaponType.SWORD:  [JobClass.WARRIOR, JobClass.KNIGHT, JobClass.PALADIN,
                            JobClass.ASSASSIN, JobClass.BERSERKER, JobClass.DRAGOON,
                            JobClass.DARK_MAGE, JobClass.LIGHT_MAGE],
        WeaponType.SPEAR:  [JobClass.SPEARMAN, JobClass.DRAGOON, JobClass.WARRIOR,
                            JobClass.KNIGHT, JobClass.RANGER, JobClass.HUNTER],
        WeaponType.AXE:    [JobClass.BERSERKER, JobClass.WARRIOR, JobClass.MONK,
                            JobClass.EARTH_MAGE, JobClass.DRAGOON, JobClass.KNIGHT],
        WeaponType.DAGGER: [JobClass.ASSASSIN, JobClass.RANGER, JobClass.HUNTER,
                            JobClass.WIND_MAGE, JobClass.DARK_MAGE, JobClass.MONK],
        WeaponType.BOW:    [JobClass.RANGER, JobClass.HUNTER, JobClass.ASSASSIN,
                            JobClass.STORM_MAGE, JobClass.WIND_MAGE, JobClass.LIGHT_MAGE],
        WeaponType.STAFF:  [JobClass.CLERIC, JobClass.PRIEST, JobClass.FIRE_MAGE,
                            JobClass.ICE_MAGE, JobClass.STORM_MAGE, JobClass.WIND_MAGE,
                            JobClass.EARTH_MAGE, JobClass.DARK_MAGE, JobClass.LIGHT_MAGE,
                            JobClass.ARCANE_SAGE, JobClass.PALADIN],
    }

    suggested = job_weapon_affinity.get(weapon, all_jobs)
    job_info = {
        JobClass.WARRIOR:    "Melee fighter, high PATK, balanced",
        JobClass.KNIGHT:     "Tank, high HP/DEF, Shield Bash",
        JobClass.PALADIN:    "Holy warrior, heal & attack",
        JobClass.BERSERKER:  "Rage fighter, highest PATK, low DEF",
        JobClass.ASSASSIN:   "Stealth, high CRIT/EVA, poison",
        JobClass.RANGER:     "Bow specialist, multi-hit, area",
        JobClass.HUNTER:     "Traps, dragon slayer, beast lore",
        JobClass.SPEARMAN:   "Spear master, sweep attacks",
        JobClass.DRAGOON:    "Dragon knight, jump attacks",
        JobClass.MONK:       "Unarmed master, combo strikes",
        JobClass.CLERIC:     "Healer & smiter, Light magic",
        JobClass.PRIEST:     "Pure healer, mass heals, revival",
        JobClass.FIRE_MAGE:  "Fire magic, burn effects",
        JobClass.ICE_MAGE:   "Ice magic, freeze/slow",
        JobClass.STORM_MAGE: "Lightning magic, chain effects",
        JobClass.WIND_MAGE:  "Wind magic, speed buffs",
        JobClass.EARTH_MAGE: "Earth magic, high power AOE",
        JobClass.DARK_MAGE:  "Dark magic, debuffs, drain",
        JobClass.LIGHT_MAGE: "Light magic, barriers, blind",
        JobClass.ARCANE_SAGE:"All-element magic, time manipulation",
    }

    print(f"\n  ── Choose Your Job (Weapon: {weapon.value}) ──")
    print(f"  Recommended jobs shown. Enter A for all jobs.")
    print()
    for i, job in enumerate(suggested):
        print(f"    [{i+1:2d}] {job.value:15s} - {job_info.get(job,'')}")
    print(f"    [ A] Show ALL {len(all_jobs)} jobs")

    job = None
    while True:
        raw = input("  > ").strip().upper()
        if raw == "A":
            print("\n  ALL JOBS:")
            for i, j in enumerate(all_jobs):
                print(f"    [{i+1:2d}] {j.value:15s} - {job_info.get(j,'')}")
            while True:
                try:
                    ji = int(input("  > ")) - 1
                    if 0 <= ji < len(all_jobs):
                        job = all_jobs[ji]
                        break
                except ValueError: pass
                print("  Invalid.")
            break
        else:
            try:
                idx = int(raw) - 1
                if 0 <= idx < len(suggested):
                    job = suggested[idx]
                    break
            except ValueError: pass
            print("  Invalid choice.")

    # Skill preview
    print(f"\n  ── Skill Preview for {job.value} ──")
    pool = JOB_SKILL_POOL[job]
    basics = [sid for sid in pool if SKILL_DB[sid].tier == SkillTier.BASIC][:4]
    inters = [sid for sid in pool if SKILL_DB[sid].tier == SkillTier.INTERMEDIATE][:4]
    ultims = [sid for sid in pool if SKILL_DB[sid].tier == SkillTier.ULTIMATE][:2]
    print("  Basic (start with 2):")
    for sid in basics:
        sk = SKILL_DB[sid]
        print(f"    • {sk.name:20s} MP:{sk.mp_cost:3d} PWR:{sk.power} - {sk.description}")
    print("  Intermediate (unlock lv5/10):")
    for sid in inters:
        sk = SKILL_DB[sid]
        print(f"    • {sk.name:20s} MP:{sk.mp_cost:3d} PWR:{sk.power} - {sk.description}")
    print("  Ultimate (unlock lv20):")
    for sid in ultims:
        sk = SKILL_DB[sid]
        print(f"    • {sk.name:20s} MP:{sk.mp_cost:3d} PWR:{sk.power} - {sk.description}")

    # Summary
    stats = JOB_BASE_STATS[job]
    print(f"\n  ── Summary ──")
    print(f"  Name: {name}  |  Job: {job.value}  |  Weapon: {weapon.value}  |  Element: {element.value}")
    print(f"  HP:{stats.hp} MP:{stats.mp} PATK:{stats.patk} MATK:{stats.matk} "
          f"PDEF:{stats.pdef} MDEF:{stats.mdef} SPD:{stats.spd}")

    confirm = input("\n  Confirm? (Y/N) > ").strip().upper()
    if confirm != "Y":
        return create_character()

    return name, job, weapon, element


def main():
    print_title()
    print("""
  Welcome to Chronicles of the Eternal Turn!

  OBJECTIVE: Survive as many battles as you can.
             Collect companions, level up, grow powerful.

  COMBAT TIPS:
  • Attack enemies' Weaknesses to reduce their Shield Points
  • When Shield = 0, enemy is BROKEN (50% more damage, can't act)
  • Use Defend to reduce incoming damage and gain turn priority
  • If a party member is KO'd, revive within 3 turns or they DIE
  • If YOUR character dies → GAME OVER

  COMPANION SYSTEM:
  • Win battles to earn companions (3★/4★/5★)
  • Party maximum: 4 members
  • Companions can permanently die in battle
""")
    input("  [Press Enter to begin]")

    name, job, weapon, element = create_character()
    clear()

    gs = GameState()
    player = create_player_character(name, job, weapon, element)
    gs.player = player
    gs.party = [player]
    gs.all_party_members = [player]

    print(f"\n  ✦ {player.name} the {player.job.value} sets forth!")
    print(f"  Starting skills: " +
          ", ".join(SKILL_DB[s].name for s in player.unlocked_skills if s in SKILL_DB))
    input("\n  [Press Enter to begin your adventure]")

    # Main game loop
    while not gs.game_over:
        print(f"\n{'═'*65}")
        print(f"  ✦ BATTLE {gs.battle_number + 1}")

        if gs.battle_number > 0:
            cont = gs.between_battles_menu()
            if not cont:
                print("\n  Thanks for playing! Farewell, hero.")
                print(f"  Final record: {gs.battle_number} battles | {gs.player.name} Lv{gs.player.level}")
                break

        alive = [ch for ch in gs.party if not ch.is_dead]
        if not alive:
            gs.game_over = True
            break

        survived = gs.run_battle()

        if not survived or gs.game_over:
            gs.show_game_over_screen()
            break

    if not gs.game_over and gs.battle_number > 0:
        print(f"\n  Journey ended. {gs.battle_number} battles completed.")
        print(f"  {gs.player.name} reached Level {gs.player.level}. Well done!")


if __name__ == "__main__":
    main()