Title demo: lava and water rivers around a rock tongue, pillars and walls
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1094c7c5f2
commit
2a6892f9a6
1 file changed
+98
-35
+98
-35
@@ -1,5 +1,5 @@
|
|||||||
// The endless battle behind the title menu: the dungeon's creatures and the knights fight at
|
// The endless battle behind the title menu: the dungeon's creatures and the knights fight on
|
||||||
// a bridge over an underground river. Both sides get balanced reinforcements, destroyed towers
|
// a tongue of rock between a lava river (north) and an underground river (south). Both sides get balanced reinforcements, destroyed towers
|
||||||
// are rebuilt after a while. Nothing here is ever saved.
|
// are rebuilt after a while. Nothing here is ever saved.
|
||||||
|
|
||||||
use crate::game::*;
|
use crate::game::*;
|
||||||
@@ -8,8 +8,13 @@ use crate::render3d::Cam;
|
|||||||
|
|
||||||
const W: usize = 72;
|
const W: usize = 72;
|
||||||
const H: usize = 48;
|
const H: usize = 48;
|
||||||
const RIVER_X: i32 = 34; // first water column (four tiles wide)
|
const RIVER_X: i32 = 35; // middle column of the rivers (five tiles wide, winding a little)
|
||||||
const BRIDGE_Y: i32 = 23; // first bridge row (three tiles wide)
|
const BRIDGE_Y: i32 = 22; // first row of the rock tongue across the rivers (three rows)
|
||||||
|
|
||||||
|
// middle column of the river in a row: it winds around RIVER_X
|
||||||
|
fn river_mid(y: i32) -> i32 {
|
||||||
|
RIVER_X + (1.6 * (y as f32 * 0.3).sin()).round() as i32
|
||||||
|
}
|
||||||
const PER_SIDE: usize = 9; // at most this many units per side
|
const PER_SIDE: usize = 9; // at most this many units per side
|
||||||
const REBUILD: f32 = 25.0; // seconds until a destroyed tower stands again
|
const REBUILD: f32 = 25.0; // seconds until a destroyed tower stands again
|
||||||
|
|
||||||
@@ -41,53 +46,81 @@ impl Game {
|
|||||||
let mut g = Game::from_map(map, seed);
|
let mut g = Game::from_map(map, seed);
|
||||||
g.ents.clear();
|
g.ents.clear();
|
||||||
let mut rng = Rng(seed | 1);
|
let mut rng = Rng(seed | 1);
|
||||||
// a wide cave on both banks: the dungeon's ground on the left, knight ground on the right
|
let phase = rng.f() * 6.0;
|
||||||
for y in 3..H as i32 - 3 {
|
// a wide cave with a ragged outline: the dungeon's ground on the left, knight ground on the right
|
||||||
for x in 3..W as i32 - 3 {
|
for y in 2..H as i32 - 2 {
|
||||||
let cy = (y - H as i32 / 2) as f32 / (H as f32 * 0.42);
|
for x in 2..W as i32 - 2 {
|
||||||
let cx = (x - W as i32 / 2) as f32 / (W as f32 * 0.47);
|
let cy = (y - H as i32 / 2) as f32 / (H as f32 * 0.44);
|
||||||
let edge = 1.0 + (rng.f() - 0.5) * 0.12;
|
let cx = (x - W as i32 / 2) as f32 / (W as f32 * 0.48);
|
||||||
if cx * cx + cy * cy > edge {
|
let a = cy.atan2(cx);
|
||||||
|
let edge = 1.0 + 0.11 * (3.0 * a + phase).sin() + 0.07 * (7.0 * a - phase).sin() + (rng.f() - 0.5) * 0.06;
|
||||||
|
if cx * cx + cy * cy > edge * edge {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let t = &mut g.map.t[idx(x, y)];
|
let t = &mut g.map.t[idx(x, y)];
|
||||||
if matches!(t.kind, Kind::Core | Kind::Treasury) {
|
if matches!(t.kind, Kind::Core | Kind::Treasury) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let m = river_mid(y);
|
||||||
t.kind = Kind::Floor;
|
t.kind = Kind::Floor;
|
||||||
t.gold = 0;
|
t.gold = 0;
|
||||||
t.owner = if x < RIVER_X { Owner::Player } else if x >= RIVER_X + 4 { Owner::Hero } else { Owner::None };
|
t.reinforced = false;
|
||||||
|
t.owner = if x < m - 2 { Owner::Player } else if x > m + 2 { Owner::Hero } else { Owner::None };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// the river from top to bottom, and the bridge across
|
// the rivers from top to bottom: lava north of the tongue, water south of it; they cut
|
||||||
for y in 2..H as i32 - 2 {
|
// through the rock to the map edge
|
||||||
for x in RIVER_X..RIVER_X + 4 {
|
for y in 1..H as i32 - 1 {
|
||||||
|
let m = river_mid(y);
|
||||||
|
for x in m - 2..=m + 2 {
|
||||||
let t = &mut g.map.t[idx(x, y)];
|
let t = &mut g.map.t[idx(x, y)];
|
||||||
t.owner = Owner::None;
|
t.owner = Owner::None;
|
||||||
t.gold = 0;
|
t.gold = 0;
|
||||||
t.kind = if (BRIDGE_Y..BRIDGE_Y + 3).contains(&y) { Kind::Bridge } else { Kind::Water };
|
t.reinforced = false;
|
||||||
|
t.kind = if y < BRIDGE_Y { Kind::Lava } else if y >= BRIDGE_Y + 3 { Kind::Water } else { Kind::Floor };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for y in BRIDGE_Y..BRIDGE_Y + 3 {
|
// the tongue's edges are uneven: a bulge into the lava on the west, into the water on the east
|
||||||
for x in RIVER_X..RIVER_X + 4 {
|
for (x, y) in [(river_mid(BRIDGE_Y - 1) - 2, BRIDGE_Y - 1), (river_mid(BRIDGE_Y - 1) - 1, BRIDGE_Y - 1), (river_mid(BRIDGE_Y + 3) + 1, BRIDGE_Y + 3), (river_mid(BRIDGE_Y + 3) + 2, BRIDGE_Y + 3)] {
|
||||||
g.map.t[idx(x, y)].owner = Owner::Player;
|
g.map.t[idx(x, y)].kind = Kind::Floor;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// lava pools in front of the human arrow towers, with gaps so the path to the bridge stays open
|
// rock and earth: pillars in the cave and shoulders along the banks
|
||||||
for (x0, y0, x1, y1) in [(RIVER_X + 5, BRIDGE_Y - 4, RIVER_X + 6, BRIDGE_Y - 3), (RIVER_X + 9, BRIDGE_Y - 1, RIVER_X + 10, BRIDGE_Y)] {
|
let blobs: [(f32, f32, f32, bool); 13] = [
|
||||||
for y in y0..=y1 {
|
// dungeon side: earth
|
||||||
for x in x0..=x1 {
|
(12.0, 10.5, 1.7, false),
|
||||||
|
(23.5, 8.5, 1.4, false),
|
||||||
|
(19.0, 34.0, 1.9, false),
|
||||||
|
(27.5, 38.0, 1.3, false),
|
||||||
|
(6.5, 24.5, 1.1, false),
|
||||||
|
(29.0, 14.0, 1.2, false),
|
||||||
|
// knight side: rock
|
||||||
|
(47.5, 9.5, 1.5, true),
|
||||||
|
(58.0, 13.0, 1.8, true),
|
||||||
|
(52.0, 36.5, 1.7, true),
|
||||||
|
(62.0, 29.5, 1.3, true),
|
||||||
|
(44.5, 39.0, 1.1, true),
|
||||||
|
(42.0, 31.5, 1.2, true),
|
||||||
|
// a stalagmite on the tongue's west end, beside the path
|
||||||
|
(31.5, BRIDGE_Y as f32 - 1.5, 0.75, false),
|
||||||
|
];
|
||||||
|
for &(bx, by, r, rock) in blobs.iter() {
|
||||||
|
for y in (by - r - 1.0) as i32..=(by + r + 1.0) as i32 {
|
||||||
|
for x in (bx - r - 1.0) as i32..=(bx + r + 1.0) as i32 {
|
||||||
|
let d = ((x as f32 + 0.5 - bx).powi(2) + (y as f32 + 0.5 - by).powi(2)).sqrt();
|
||||||
|
if d > r + (rng.f() - 0.5) * 0.5 || !inb(x, y) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let t = &mut g.map.t[idx(x, y)];
|
let t = &mut g.map.t[idx(x, y)];
|
||||||
if t.kind == Kind::Floor && t.owner == Owner::Hero {
|
if t.kind == Kind::Floor {
|
||||||
t.kind = Kind::Lava;
|
t.kind = if rock { Kind::Rock } else { Kind::Earth };
|
||||||
t.owner = Owner::None;
|
t.owner = Owner::None;
|
||||||
t.gold = 0;
|
t.reinforced = !rock && x < RIVER_X - 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// a few rooms on the dungeon side for the look of it
|
// a few rooms on the dungeon side for the look of it
|
||||||
for (x0, y0, x1, y1, k) in [(8, 13, 13, 17, Kind::Lair), (8, 30, 13, 34, Kind::Training), (17, 12, 21, 15, Kind::Farm)] {
|
for (x0, y0, x1, y1, k) in [(6, 15, 11, 19, Kind::Lair), (9, 29, 14, 33, Kind::Training), (16, 13, 20, 16, Kind::Farm)] {
|
||||||
for y in y0..=y1 {
|
for y in y0..=y1 {
|
||||||
for x in x0..=x1 {
|
for x in x0..=x1 {
|
||||||
let t = &mut g.map.t[idx(x, y)];
|
let t = &mut g.map.t[idx(x, y)];
|
||||||
@@ -97,13 +130,16 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// towers on both banks: the arrow towers stand by the lava and by the water, so missed
|
||||||
|
// arrows come down in both rivers
|
||||||
|
let (n, s_) = (BRIDGE_Y - 4, BRIDGE_Y + 6);
|
||||||
let towers = vec![
|
let towers = vec![
|
||||||
(RIVER_X - 4, BRIDGE_Y - 3, TowerKind::Arrow, false, 0.0),
|
(river_mid(n) - 4, n, TowerKind::Arrow, false, 0.0),
|
||||||
(RIVER_X - 4, BRIDGE_Y + 5, TowerKind::Cannon, false, 0.0),
|
(river_mid(s_) - 4, s_, TowerKind::Cannon, false, 0.0),
|
||||||
(RIVER_X - 8, BRIDGE_Y + 1, TowerKind::Magic, false, 0.0),
|
(RIVER_X - 9, BRIDGE_Y + 1, TowerKind::Magic, false, 0.0),
|
||||||
(RIVER_X + 7, BRIDGE_Y - 3, TowerKind::Arrow, true, 0.0),
|
(river_mid(n) + 4, n, TowerKind::Arrow, true, 0.0),
|
||||||
(RIVER_X + 7, BRIDGE_Y + 5, TowerKind::Cannon, true, 0.0),
|
(river_mid(s_) + 4, s_, TowerKind::Arrow, true, 0.0),
|
||||||
(RIVER_X + 11, BRIDGE_Y + 1, TowerKind::Arrow, true, 0.0),
|
(RIVER_X + 9, BRIDGE_Y + 1, TowerKind::Cannon, true, 0.0),
|
||||||
];
|
];
|
||||||
g.demo = Some(Demo { spawn_t: 0.0, towers, rng: Rng(seed.wrapping_mul(747796405) | 1) });
|
g.demo = Some(Demo { spawn_t: 0.0, towers, rng: Rng(seed.wrapping_mul(747796405) | 1) });
|
||||||
g.gate_hp = 0.0;
|
g.gate_hp = 0.0;
|
||||||
@@ -116,7 +152,7 @@ impl Game {
|
|||||||
g.demo_spawn(true);
|
g.demo_spawn(true);
|
||||||
g.demo_spawn(false);
|
g.demo_spawn(false);
|
||||||
}
|
}
|
||||||
let mut cam = Cam::new(RIVER_X as f32 + 2.0, BRIDGE_Y as f32 + 1.5);
|
let mut cam = Cam::new(RIVER_X as f32 + 0.5, BRIDGE_Y as f32 + 1.5);
|
||||||
cam.zoom = 16.0;
|
cam.zoom = 16.0;
|
||||||
(g, cam)
|
(g, cam)
|
||||||
}
|
}
|
||||||
@@ -297,6 +333,33 @@ impl Game {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_map_has_both_rivers_and_a_tongue() {
|
||||||
|
let (g, _) = Game::new_demo(7);
|
||||||
|
for y in 0..H as i32 {
|
||||||
|
let row: String = (0..W as i32)
|
||||||
|
.map(|x| match g.map.t[idx(x, y)].kind {
|
||||||
|
Kind::Lava => '~',
|
||||||
|
Kind::Water => '=',
|
||||||
|
Kind::Rock => '#',
|
||||||
|
Kind::Earth => '%',
|
||||||
|
Kind::Tower => 'T',
|
||||||
|
k if g.map.t[idx(x, y)].walkable() && k != Kind::Floor => 'r',
|
||||||
|
_ if g.map.t[idx(x, y)].walkable() => '.',
|
||||||
|
_ => ' ',
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
println!("{}", row);
|
||||||
|
}
|
||||||
|
let m = river_mid(BRIDGE_Y + 1);
|
||||||
|
// lava borders the tongue in the north, water in the south
|
||||||
|
assert_eq!(g.map.t[idx(m, BRIDGE_Y - 2)].kind, Kind::Lava);
|
||||||
|
assert_eq!(g.map.t[idx(m, BRIDGE_Y + 4)].kind, Kind::Water);
|
||||||
|
// the tongue connects both banks
|
||||||
|
assert!(g.map.path_to(idx(m - 7, BRIDGE_Y + 1), idx(m + 6, BRIDGE_Y + 1)).is_some(), "across the tongue");
|
||||||
|
assert_eq!(g.towers.len(), 6);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_battle_goes_on() {
|
fn the_battle_goes_on() {
|
||||||
let (mut g, _) = Game::new_demo(7);
|
let (mut g, _) = Game::new_demo(7);
|
||||||
|
|||||||
Reference in new issue
Block a user