Initial commit: Plants turn-based battle game

This commit is contained in:
2026-08-28 14:26:07 +02:00
commit 6840b715de
158 changed files with 13352 additions and 0 deletions
+163
View File
@@ -0,0 +1,163 @@
# Plants — 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 / speed`
interval, 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