Free game against rival dungeon masters (green, blue) and/or the humans

- One opponent on maps up to 128x128, two on bigger maps; with a chance of one in two one of
  them is the humans' gate instead of a rival master
- Simplified rival masters (rival.rs): own soul core and dungeon (lair, treasury to loot) that
  grows ring by ring, creatures appear and get stronger over time, guard the dungeon and attack
  in groups - the player, the other master or the humans' gate; a fallen core puts the master
  out and frees its ground
- The humans attack everybody: waves now and then march on a rival master
- Sides for units: all other sides are enemies; towers and contested ground follow them; the
  hand cannot pick up rival creatures; the flag next to a rival core attacks it
- Difficulty (easy / medium / hard) in the New Game dialog
- Colours: green and blue floors, cores, unit rings, minimap, HUD bars of the rival cores
- Victory in free games: every rival core and the gate (if any) destroyed
- Tests for setups, growth and attacks, elimination and victory, humans against rivals

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-03 14:11:23 +01:00
1 parent 52c4d1caf8
commit dd16c684e5
11 files changed
+771 -38

No files matched your search

+17 -1
View File
@@ -461,6 +461,12 @@ impl Ui {
if g.has_gate() {
segs.insert(3, vec![txt(tr("hud.gate"), BLUE), Part::Gap, Part::Bar(g.gate_hp / GATE_HP, BLUE)]);
}
// the soul cores of the rival masters
for rv in g.rivals.iter().filter(|r| r.alive) {
let col = if rv.side == 2 { Color::new(0.4, 0.95, 0.45, 1.0) } else { Color::new(0.45, 0.65, 1.0, 1.0) };
let key = if rv.side == 2 { "hud.rival1" } else { "hud.rival2" };
segs.insert(3, vec![txt(tr(key), col), Part::Gap, Part::Bar(rv.hp / crate::rival::RIVAL_CORE_HP, col)]);
}
if g.waves_on() {
segs.push(vec![txt(trf("hud.wave", &[(g.wave + 1).to_string()]), wave_col), Part::Gap, txt(mmss(g.wave_timer), TXT)]);
}
@@ -646,7 +652,15 @@ impl Ui {
let dot = (2.0 * px).max(ms);
for e in &g.ents {
if e.alive && !e.held {
let c = if e.hero { Color::new(0.6, 0.85, 1.0, 1.0) } else if e.kind == EK::Worker { Color::new(1.0, 0.6, 0.3, 1.0) } else { Color::new(1.0, 0.25, 0.25, 1.0) };
let c = if e.hero {
Color::new(0.6, 0.85, 1.0, 1.0)
} else if e.rival() {
if e.side == 2 {
Color::new(0.3, 0.95, 0.4, 1.0)
} else {
Color::new(0.4, 0.6, 1.0, 1.0)
}
} else if e.kind == EK::Worker { Color::new(1.0, 0.6, 0.3, 1.0) } else { Color::new(1.0, 0.25, 0.25, 1.0) };
draw_rectangle((mrx + e.x.floor() * ms).floor(), (mry + e.y.floor() * ms).floor(), dot, dot, c);
}
}
@@ -1061,6 +1075,8 @@ pub fn hover_info(g: &Game, ent: Option<usize>, tile: Option<(i32, i32)>) -> Vec
let owner = match t.owner {
Owner::Player => "owner.player",
Owner::Hero => "owner.hero",
Owner::Rival1 => "owner.rival1",
Owner::Rival2 => "owner.rival2",
Owner::None => "owner.none",
};
match t.kind {