7.4 KiB
Flora — Turn-Based Battle Game
A turn-based battle game in Python. Normal battles are 4v4; the Tower mode starts small and ramps up.
Run it
pip install -r requirements.txt
python main.py
You land on the main menu:
- Seeds — seed pack opening; the main way to get/enhance plants.
- Plants — the plants you own, with their level/stage; open a plant to see its stats and Water (+1 level) or Evolve it (at milestone levels, consuming duplicates).
- Battle — pick a difficulty stage (1–5, gated by enemy rarity), then the enemy team is rolled so you know the matchup, and you pick your 4 allies and fight. Higher stages grant more packs on a win; any loss grants a lesser pack.
- Tower — a 20-stage gauntlet. Enemies start with 1 common plant and get stronger (more enemies, higher rarity, higher level) each stage; your team is fresh every fight. After each win you choose one of three roguelike blessings (shields, stat boosts, enemy debuffs, heal-over-time…) that last for the rest of the run — the core of the tower's roguelike loop. Stages 1–5 pay 1 water each, stages 6–20 pay scaling Tower packs (high-rarity odds), which you can open or redeem for 20 water.
- Collection — plants you've discovered (they're added when you battle them);
undiscovered plants show as blacked-out silhouettes. Saved in
data/collection.json. - Soil — retrieve a seed from any owned plant (once per cooldown, from its detail page), plant it, then grow a copy in stages. Each stage fills over time; fertilizing (1 fertilizer per stage) advances it. Higher rarities have more stages; harvesting grants a duplicate of that plant. You start with 1 plot; extra plots unlock for escalating fertilizer (10, then 20, 30…, up to 5 total). Soil unlocks once you own 8 distinct plants.
Rarities
Every plant has a rarity tag (common / rare / epic / legendary) — purely cosmetic, only used to weight seed-pack drops. A victory grants seed packs equal to the difficulty stage (1–5) plus fertilizer equal to the floor (1–5); a defeat grants 1 lesser pack (worse rarity odds, no type weighting). Type packs roll the rarity first (matching the general pack) and then pick a plant of that rarity from the type when possible; rarer plants are rarer pulls.
Watering, fertilizer & Evolution
Two resources:
-
Water — the scarce, valuable currency. Watering gives an instant +1 level by spending 1 water, and 5 water can be converted into a seed pack. Water comes mainly from Tower rewards (stage 1–5 wins pay 1 water, Tower packs redeem for 10).
-
Fertilizer — the common currency earned from battle wins. It's used to fertilize growing Soil plots (each fertilizer advances a plot one stage).
-
XP is earned from battles (participation + win bonus); allies auto-level up. Higher difficulty stages pay more XP (scales with the stage number), and Tower wins pay XP too. The lowest-level plant on your team earns bonus XP based on the level gap (catch-up).
-
Evolution unlocks at milestone levels (5 → stage 2, 10 → stage 3) and consumes duplicates. Stages multiply base stats before the level curve.
-
Enemies in battle are scaled to roughly your team's level.
-
Stat growth: attack/defense +6%, health +8%, speed +4% per level (see
engine/growth.py).
Roguelike blessings (Tower)
After every Tower win you're offered 3 random blessings and keep one (or skip). Blessings stack for the rest of the run and apply at the start of each battle. They come in three rarity tiers:
- Common — small shields/stat boosts and start-of-battle buffs (e.g. "team starts with a shield for 10% of max health").
- Rare — bigger boosts, heal-over-time, stronger healing, enemy debuffs (Speed/Attack Down).
- Epic — strong shields, "+15% attack", "+20% max health", enemies start Vulnerable.
- Legendary — rare picks with big payoff: block the first damage taken, a 40% shield, a one-time revive, or a large HP/attack combo.
Blessings are offered from a rarity-weighted pool, so legendary relics show up rarely.
Blessings are defined declaratively in engine/relics.py (RELIC_POOL); each
relic is a Relic with an apply(battle) function that runs once per battle in
Battle.start(). They're tracked per-run (not saved). An "All blessings"
overview (ui/relics_overview.py) shows the whole pool grouped by rarity with
the ones you've collected highlighted in gold; click any card for a closer look.
It opens from the blessing-choice screen and by clicking the "Blessings:" line
on the Tower battle screen.
Plant types & type advantages
Five types with a rock-paper-scissors cycle:
Radiant > Nocturnal > Ice > Garden > Wildflowers > Radiant
- Hitting a type you're strong against deals +30% damage.
- Hitting a type that's strong against you deals −30%.
- Applies only to damage — heals and shields are unaffected.
Stats & damage
- Health — the damage pool.
- Attack / Defense — damage = skill power + attack − defense (min 1).
- Speed — an initiative timeline: each plant acts based on
100 / speedinterval, so faster plants act more often and interleave with slower ones (e.g. a 110-speed enemy goes 110 → 75 → 110 → 50…). The blue bar under each unit's health shows how close it is to its next turn. Speed buffs/debuffs change the interval, and "reduce turn progress" effects literally push a plant's next turn back. - Crit — base 10%, doubles damage.
Debuffs
- Wilt — damage over 3 turns (total = % of the triggering hit's damage); reapplying resets it to 3 turns.
- Shield — temporary HP that absorbs damage; cannot be healed.
- Damage-over-time (Dark Corrosion, Chill) — deals its listed damage every turn while active; Dark Corrosion stacks.
- Sleep — skips turns until the target has taken damage twice.
- Vulnerable X — +50% damage for the next X hits.
- Intoxicate / Oblivion — an Intoxicated plant's attacks splash half their damage onto one of its own allies; an Oblivious plant may only use its weakest skill (both last their stated duration, e.g. 1-2 turns).
- Plus speed/crit/attack/defense down, Madness, Tether, and more.
The plant pool (27)
All defined declaratively in engine/plants.py so new plants are easy to add:
- Radiant: Zinnia, Sunflower, Daisy, Marigold, Daffodil
- Garden: Rose, Peony, Magnolia, Hydrangea, Sweet Pea, Chrysanthemum
- Wildflower: Lily, Hibiscus, Poppy, Tulip, Lotus, Dandelion
- Nocturnal: Nightshade, Moonflower, Orchid, Lavender, Venus Flytrap
- Ice: Snowdrop, Primrose, Winter Jasmine, Hellebore, Camelia
Each has a role (Attacker / Support / Tank), a unique passive, three skills with cooldowns, and base stats. Tanks protect their team from single-target attacks (AOE still hits everyone).
Architecture
engine/ Pure-Python battle logic (types, skills, effects, plants, battle).
No pygame — reusable for a web UI or Flet later.
ui/ pygame frontend: team picker + battle screen.
tests/ unittest coverage of the engine.
Adding a plant later = add one entry to POOL in engine/plants.py.
Tests
python -m unittest discover -s tests
Roadmap ideas
- Balance pass (sim currently favors the player's chosen teams)
- More team strategies, saving team compositions
- Web UI via Flask, or a real mobile app via Flet