Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chroma Siege

Latest Release

A top-down hybrid of tower defense and horde-survivor auto-shooter, built in pure Python (standard library only — no external dependencies, rendered with Tkinter).

You move around a procedurally generated map, auto-fire at anything in range, gather resources, and place/upgrade auto-firing towers to help hold the line against endless, escalating waves of mobs. Every round ends with a shop where you spend earned EXP on permanent upgrades, in the same "clear a wave, pick an upgrade, go again" loop as games like Vampire Survivors — just with towers added into the mix.

There is no win condition. The waves never stop getting harder. The goal is survival, and how far you can push your build before you're overrun.

Running it

Windows (recommended): grab the latest ChromaSiege.exe from the Releases page and run it directly — no Python install, no dependencies, nothing to set up. Just download and double-click.

From source (Linux/macOS, or to run the latest in-development code):

python3 main.py

Requires Python 3 with Tkinter available (standard on most desktop Python installs; on Linux you may need a python3-tk package). No pip install is needed — the game has zero third-party dependencies.

Controls:

Key Action
W A S D Move
G Toggle tower placement (ghost preview)
F Confirm tower placement
E Upgrade the nearest tower in reach

Display resolution: the first thing you see on launch is a resolution picker — 1920x1080, 1600x900, 1366x768, 1280x800, 1280x720, or 640x480. Your choice is remembered for next time. The game's own layout is designed at a fixed native size; picking a smaller resolution scales that whole layout down to fit (letterboxed if the aspect ratio doesn't match 16:9 exactly) rather than cropping it, so nothing gets cut off on a smaller screen. This is purely a display setting — it never changes gameplay (speeds, ranges, map size) which stay identical at every resolution. Works on Linux/X11, Windows, and macOS; on Windows the game also marks itself DPI-aware so OS-level display scaling doesn't distort the window size.

Main features

  • Procedurally generated terrain every run (mountains, hills, and a safe lowground spawn zone), with a distance-based navigation system so mobs path around obstacles realistically instead of clipping through them.
  • An auto-fire combat system for both the player and every placed tower — no manual aiming. Each independently finds the nearest target(s) in range and leads its shot to intercept a moving target.
  • A resource economy: pickups scattered around the map fund tower placement and upgrades.
  • Tower placement and a 10-level tower upgrade track (HP, range, and Multishot all scale up independently).
  • A round-end shop: spend EXP on one of several permanent upgrade categories, drawn from a shared deck so choices feel meaningfully different run to run.
  • Infinite, procedurally scaled waves with an explicit round-by-round composition curve through round 25, then a probabilistic scaling cascade beyond that for indefinite play.
  • Full mob-vs-mob, mob-vs-terrain, mob-vs-tower, and player-vs-tower collision handling, including proximity-based mob staggering so waves don't read as a single clumped mass.
  • One life, no respawns: player HP reaching 0 ends the run immediately with a game-over screen reporting the round reached, offering Restart (same map, fresh run), New Map (freshly generated terrain), or Main Menu.

Terrain generation

Every map is generated fresh, in six layered passes:

  1. Lowground safe zone — a flat, guaranteed-buildable circle at the map's center, with an inner core that's perfectly flat and a soft, noise-perturbed outer blend ring (not a hard circular cutout) so it reads as a natural clearing rather than a stamped shape.
  2. Mountains — grown as scattered ridge-and-erosion massifs, not simple blobs. Each cluster is one summit with 3–5 radiating "arm" ridges, each a bent polyline whose width tapers toward its tip and whose edges are eroded by ridged fractal noise, so it reads as a natural branching massif with valleys cut into its sides. Clusters are loosely arranged in 2–3 concentric rings radiating out from the lowground, with guaranteed clearance between neighbors, plus a handful of standalone outlier clusters scattered independently.
  3. Distance-to-mountain field — a multi-source BFS computed once, immediately after mountains are placed. This single precomputed field drives both hill shading (below) and every mountain-proximity gameplay rule (mob spawn exclusion, mob navigation's keep-out buffer, resource pile placement clearance), so those checks are O(1) lookups rather than live searches.
  4. Hill topography — every non-mountain tile's elevation is a continuous function of its distance to the nearest mountain (not discretely placed hill shapes), perturbed by smooth fractal noise so elevation contours branch and waver organically like a real topographic map, quantized into graduated color bands.
  5. Boundary taper — elevation is clamped so it never rises more than one band per tile moving outward from the lowground, guaranteeing a smooth, walkable ramp rather than a sudden cliff.
  6. Fill guarantee — anything left unclaimed resolves to the lowest grass band.

Mountains are the only truly impassable terrain; everything else is walkable at variable elevation.

Leveling and progression

There's no traditional XP-to-player-level bar. Progression is round-based: killing mobs earns EXP, and clearing a round's mob queue triggers the round-end shop, which functions as the game's "level up" moment — you're offered a hand of upgrade cards, drawn from a shared deck of purchasable categories:

  • Attack Speed (player) / Tower Attack Speed — reduces fire cooldown.
  • Multifire (player) — read below.
  • Movement Speed — raises base move speed.
  • Base Health — raises max HP by one segment (up to a hard cap).
  • Force Field — raises shield capacity; shields regenerate over time and absorb contact damage before HP does. A charged shield is visible on-screen as a violet outline traced just inside the player model's own edge, so an active charge reads at a glance without checking the HUD.
  • Resource Harvesting — increases the yield of every resource pile picked up.
  • Tower Multishot / Tower Range — global bonuses applied on top of every currently placed tower's own stats, not just towers placed afterward.

Each category can be purchased up to 10 times, at an escalating EXP cost per purchase. Two categories are gated behind round thresholds (Multifire from round 10, all three tower categories from round 15) so the early game isn't overwhelmed with choices.

Multishot / Multifire, precisely defined: firing an extra simultaneous, independent projectile stream at an additional target within range — not extra damage on the same target. A shooter (player or tower) with Multishot/Multifire N picks up to N distinct nearest in-range targets each time it fires, and computes an independent lead-intercept shot for each one, so it can genuinely engage multiple mobs in the same instant.

Mob types and wave composition

Three mob tiers, each a bigger, tougher footprint than the last:

  • Small — base tier, 1 EXP on kill.
  • Medium — 2 EXP on kill.
  • Large — 3 EXP on kill.

Combat against mobs distinguishes two kinds of damage. A ranged hit (from a player or tower projectile) steps a mob down one tier — large → medium, medium → small — and only kills it outright (awarding its EXP) once it's already small. Contact damage (a mob physically touching the player or a tower) instead kills the mob instantly regardless of its tier and awards its EXP immediately, while dealing a flat 1 HP of damage back to whatever it touched — absorbed by the player's Force Field shield first, if they have one.

Wave composition follows an explicit curve for the first 25 rounds, then hands off to a procedural cascade:

  • Rounds 1–5: small mobs only, scaling up each round.
  • Rounds 6–10: small count holds steady; medium mobs start entering the mix.
  • Rounds 11–20: small and medium both keep climbing; large mobs are introduced.
  • Rounds 21–25: small count holds; medium and large keep climbing.
  • Round 26+: composition is generated probabilistically — a cascading small→medium→large conversion (a fraction of what would've been small mobs converts to medium, a further fraction of medium converts to large) — so difficulty keeps escalating indefinitely without a hardcoded ceiling.

Each round's total mob budget is capped, and mobs release from a staggered spawn queue (not all at once) so a wave arrives as a convoy rather than a single instantaneous dump. Mobs path toward the player (or a closer in-range tower — see below) over a coarse navigation graph, with local deflection logic to route around mountains, resource piles, and each other, including a "stay roughly one mob-length apart" preference so a wave reads as a moving crowd rather than a single clipped-together blob.

Tower logic

Towers are placed via a toggleable ghost-preview (no free-floating cursor — the ghost is pinned just north of the player and moves with them), then confirmed if the spot is valid: clear of terrain/mob/resource-pile overlap, far enough from the map's edge, and at least a minimum distance from any other tower (so stacking towers close together isn't viable).

Once placed, a tower auto-fires at the nearest mob(s) within its own range, completely independently of the player — no manual targeting. A mob that wanders within a tower's range and is closer to that tower than to the player will divert and "attack" it directly, arcing in from a slight random angle (adjusted to route around any mountain or resource pile in the way) rather than beelining in a dead straight line; player proximity always overrides this and pulls the mob back onto the player instead.

A tower's own HP is finite and does not restore at round end — contact damage persists across rounds, and the only way to fully heal a tower is to upgrade it. Placing/upgrading a tower spends both EXP and gathered resources.

Tower upgrade system

Every tower starts at level 1 (placement) with a small HP pool, a base range, and one projectile stream, and can be upgraded up to level 10 by walking up next to it (or close enough — see below) and pressing E, which targets whichever eligible tower is nearest. Each level has its own explicit EXP/resource cost and its own mix of gains — HP, range, and/or Multishot — rather than one flat cost/gain reused for every level:

Level Grants
2 +1 HP
3 +1 HP, +15 range
4 +1 HP, +1 Multishot
5 +1 HP, +15 range
6 +1 HP, +15 range, +1 Multishot
7 +1 HP, +15 range
8 +1 HP, +1 Multishot
9 +1 HP, +1 Multishot
10 (max) +1 HP, +15 range, +1 Multishot

Every gain stacks on top of the tower's current stats — a level 10 tower has meaningfully more HP, range, and simultaneous targets than a fresh placement. The full cost/gain table is also browsable in-game via the Tower & Upgrade Dictionary screen.

Not implemented yet

So don't be alarmed if you notice this — it's a known, deliberate gap rather than a bug:

  • Tower destruction has no visual effect yet — a destroyed tower currently just disappears cleanly, rather than the distinct burst effect eventually planned for it (separate from the mob-death effect, which already exists).

Tests

Tests live in chroma_siege/tests/ and can be run directly, e.g.:

python3 chroma_siege/tests/test_combat.py

All of them require a live display (they drive real Tkinter canvases) — on a headless machine, run them under Xvfb: xvfb-run -a python3 <file>. Most run in a few seconds. test_terrain.py (~5–6 minutes) and test_game_systems.py (~2 minutes) are the outliers since they generate real terrain across multiple seeds; test_mobs.py also generates a real map and takes under a minute.

Future development path

This alpha is a complete, playable core loop; these are the directions planned beyond it:

  • AoE effects — area-of-effect damage, both for the player/towers and for mobs.
  • Ranged mobs — mob types that attack from a distance instead of only via contact damage.
  • A new C++ engine — a from-scratch rewrite of the simulation layer for performance and control beyond what a pure-Python/Tkinter stack can offer.
  • Sprites — replacing the current flat-color pixel-grid rendering with actual sprite art.
  • Networking co-op — multiplayer support for two or more players sharing a map.
  • A scrolling map / larger perspective — moving off the current fixed single-screen board toward a camera that follows the player across a larger world.
  • New biomes and terrain types — beyond the current mountain/hill/grass generation.
  • Music and sound effects — the game is currently silent.

License

All rights reserved. See LICENSE.

About

A top-down hybrid of tower defense and horde-survivor auto-shooter: procedurally generated terrain, auto-firing towers, and infinite escalating waves. Built entirely on Python's standard library — no pip installs, zero external dependencies.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages