new layer model
This commit is contained in:
@@ -1,36 +1,30 @@
|
||||
use super::load_board;
|
||||
use crate::archetype::Archetype;
|
||||
use crate::glyph::Glyph;
|
||||
use crate::map_file::parse_color;
|
||||
|
||||
#[test]
|
||||
fn fill_builds_a_full_grid_of_one_char() {
|
||||
// Layer 0 is a solid fill of a fixed floor glyph; a later sparse layer places
|
||||
// the player. Every cell of layer 0 must be that floor glyph.
|
||||
// `fill` fills the whole single grid with one palette char. No player char is
|
||||
// possible in a filled grid, so the player falls back to (0, 0) and wins (clears)
|
||||
// that cell; every *other* cell is the filled archetype.
|
||||
let toml = r##"
|
||||
[map]
|
||||
name = "Test"
|
||||
width = 3
|
||||
height = 2
|
||||
|
||||
[[layers]]
|
||||
fill = "f"
|
||||
palette = { "f" = { kind = "floor", tile = ".", fg = "#112233", bg = "#445566" } }
|
||||
|
||||
[[layers]]
|
||||
sparse = [ { x = 0, y = 0, ch = "@" } ]
|
||||
palette = { " " = { kind = "empty" }, "@" = { kind = "player" } }
|
||||
[grid]
|
||||
fill = "#"
|
||||
palette = { "#" = { kind = "wall", tile = 35, fg = "#808080", bg = "#606060" } }
|
||||
"##;
|
||||
let board = load_board(toml);
|
||||
assert!(board.is_valid());
|
||||
let floor = Glyph {
|
||||
tile: '.' as u32,
|
||||
fg: parse_color("#112233"),
|
||||
bg: parse_color("#445566"),
|
||||
};
|
||||
for y in 0..2 {
|
||||
for x in 0..3 {
|
||||
assert_eq!(*board.get(0, x, y), (floor, Archetype::Empty));
|
||||
let expected = if (x, y) == (0, 0) {
|
||||
Archetype::Empty // player won its fallback cell
|
||||
} else {
|
||||
Archetype::Wall
|
||||
};
|
||||
assert_eq!(board.get(x, y).1, expected, "cell ({x}, {y})");
|
||||
}
|
||||
}
|
||||
assert_eq!((board.player.x, board.player.y), (0, 0));
|
||||
@@ -38,18 +32,14 @@ palette = { " " = { kind = "empty" }, "@" = { kind = "player" } }
|
||||
|
||||
#[test]
|
||||
fn sparse_places_only_listed_cells() {
|
||||
// A grass floor underneath; a sparse layer holding just the player and one object.
|
||||
// A sparse grid holding just the player and one object; every other cell empty.
|
||||
let toml = r##"
|
||||
[map]
|
||||
name = "Test"
|
||||
width = 4
|
||||
height = 1
|
||||
|
||||
[[layers]]
|
||||
fill = "g"
|
||||
palette = { "g" = { kind = "floor", generator = "grass" } }
|
||||
|
||||
[[layers]]
|
||||
[grid]
|
||||
sparse = [ { x = 0, y = 0, ch = "@" }, { x = 2, y = 0, ch = "O" } ]
|
||||
palette = { " " = { kind = "empty" }, "@" = { kind = "player" }, "O" = { kind = "object", tile = 64, fg = "#00FFFF", bg = "#000000" } }
|
||||
"##;
|
||||
@@ -59,7 +49,7 @@ palette = { " " = { kind = "empty" }, "@" = { kind = "player" }, "O" = { kind =
|
||||
assert_eq!(board.objects.len(), 1);
|
||||
assert_eq!((board.objects[&1].x, board.objects[&1].y), (2, 0));
|
||||
// An unlisted sparse cell is a transparent empty.
|
||||
assert_eq!(board.get(1, 1, 0).1, Archetype::Empty);
|
||||
assert_eq!(board.get(1, 0).1, Archetype::Empty);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -70,7 +60,7 @@ name = "Test"
|
||||
width = 2
|
||||
height = 1
|
||||
|
||||
[[layers]]
|
||||
[grid]
|
||||
sparse = [ { x = 5, y = 0, ch = "O" }, { x = 0, y = 0, ch = "@" } ]
|
||||
palette = { " " = { kind = "empty" }, "@" = { kind = "player" }, "O" = { kind = "object", tile = 64, fg = "#00FFFF", bg = "#000000" } }
|
||||
"##;
|
||||
@@ -95,18 +85,14 @@ name = "Test"
|
||||
width = 2
|
||||
height = 1
|
||||
|
||||
[[layers]]
|
||||
[grid]
|
||||
fill = "xy"
|
||||
palette = { " " = { kind = "empty" } }
|
||||
|
||||
[[layers]]
|
||||
sparse = [ { x = 0, y = 0, ch = "@" } ]
|
||||
palette = { " " = { kind = "empty" }, "@" = { kind = "player" } }
|
||||
"##;
|
||||
let board = load_board(toml);
|
||||
assert!(
|
||||
!board.is_valid(),
|
||||
"a non-single-char fill is a nonfatal error"
|
||||
);
|
||||
assert_eq!(board.get(0, 1, 0).1, Archetype::Empty);
|
||||
assert_eq!(board.get(1, 0).1, Archetype::Empty);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{layer, load_board, map};
|
||||
use super::{grid, load_board, map};
|
||||
use crate::archetype::Archetype;
|
||||
use crate::board::Board;
|
||||
use crate::map_file::MapFile;
|
||||
@@ -6,7 +6,7 @@ use crate::map_file::MapFile;
|
||||
#[test]
|
||||
fn grid_wrong_row_count_returns_error() {
|
||||
// height = 3 but only 2 rows in the layer grid.
|
||||
let toml = map(3, 3, &[layer("...\n...", &[(".", "kind = \"empty\"")])]);
|
||||
let toml = map(3, 3, &grid("...\n...", &[(".", "kind = \"empty\"")]));
|
||||
let mf: MapFile = toml::from_str(&toml).unwrap();
|
||||
let result = Board::try_from(mf);
|
||||
assert!(result.is_err());
|
||||
@@ -24,7 +24,7 @@ fn grid_wrong_row_count_returns_error() {
|
||||
#[test]
|
||||
fn grid_wrong_row_width_returns_error() {
|
||||
// width = 4 but second row is only 3 characters.
|
||||
let toml = map(4, 2, &[layer("....\n...", &[(".", "kind = \"empty\"")])]);
|
||||
let toml = map(4, 2, &grid("....\n...", &[(".", "kind = \"empty\"")]));
|
||||
let mf: MapFile = toml::from_str(&toml).unwrap();
|
||||
let result = Board::try_from(mf);
|
||||
assert!(result.is_err());
|
||||
@@ -46,14 +46,14 @@ fn unknown_kind_produces_error_block() {
|
||||
let toml = map(
|
||||
2,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"X@",
|
||||
&[("X", "kind = \"frobnicate\""), ("@", "kind = \"player\"")],
|
||||
)],
|
||||
),
|
||||
);
|
||||
let board = load_board(&toml);
|
||||
assert_eq!(
|
||||
*board.get(0, 0, 0),
|
||||
*board.get(0, 0),
|
||||
(Archetype::ErrorBlock.default_glyph(), Archetype::ErrorBlock)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,33 +16,29 @@ fn load_board(toml: &str) -> Board {
|
||||
Board::try_from(mf).expect("convert to board")
|
||||
}
|
||||
|
||||
/// Builds one `[[layers]]` block: a triple-quoted `content` grid plus an inline
|
||||
/// Builds the `[grid]` block: a triple-quoted `content` grid plus an inline
|
||||
/// `palette` table. Each palette entry is `(char_key, inline-body)` where the
|
||||
/// body is the inside of the entry's `{ ... }` (e.g. `kind = "wall"`).
|
||||
fn layer(content: &str, palette: &[(&str, &str)]) -> String {
|
||||
fn grid(content: &str, palette: &[(&str, &str)]) -> String {
|
||||
let pal = palette
|
||||
.iter()
|
||||
.map(|(k, body)| format!("\"{k}\" = {{ {body} }}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
format!("\n[[layers]]\ncontent = \"\"\"\n{content}\n\"\"\"\npalette = {{ {pal} }}\n")
|
||||
format!("\n[grid]\ncontent = \"\"\"\n{content}\n\"\"\"\npalette = {{ {pal} }}\n")
|
||||
}
|
||||
|
||||
/// Wraps a `[map]` header of the given size around the supplied `[[layers]]`
|
||||
/// blocks (each produced by [`layer`]).
|
||||
fn map(width: usize, height: usize, layers: &[String]) -> String {
|
||||
let mut s = format!("[map]\nname = \"Test\"\nwidth = {width}\nheight = {height}\n");
|
||||
for l in layers {
|
||||
s.push_str(l);
|
||||
}
|
||||
s
|
||||
/// Wraps a `[map]` header of the given size around the single `[grid]` block
|
||||
/// (produced by [`grid`]).
|
||||
fn map(width: usize, height: usize, grid_block: &str) -> String {
|
||||
format!("[map]\nname = \"Test\"\nwidth = {width}\nheight = {height}\n{grid_block}")
|
||||
}
|
||||
|
||||
/// A 3×1 single-layer map: an `empty`/`wall` palette plus one `object` entry
|
||||
/// placed by char `ch` (cyan `@` glyph), with `extra` appended to its body. The
|
||||
/// player is placed at the far-right cell via a second char where room allows;
|
||||
/// callers that need the player elsewhere build the map directly.
|
||||
fn map_3x1_object(grid: &str, ch: &str, extra: &str) -> String {
|
||||
fn map_3x1_object(grid_str: &str, ch: &str, extra: &str) -> String {
|
||||
let body = if extra.is_empty() {
|
||||
"kind = \"object\", tile = 64, fg = \"#00FFFF\", bg = \"#000000\"".to_string()
|
||||
} else {
|
||||
@@ -51,8 +47,8 @@ fn map_3x1_object(grid: &str, ch: &str, extra: &str) -> String {
|
||||
map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
grid,
|
||||
&grid(
|
||||
grid_str,
|
||||
&[
|
||||
(" ", "kind = \"empty\""),
|
||||
(".", "kind = \"empty\""),
|
||||
@@ -63,6 +59,6 @@ fn map_3x1_object(grid: &str, ch: &str, extra: &str) -> String {
|
||||
("@", "kind = \"player\""),
|
||||
(ch, &body),
|
||||
],
|
||||
)],
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
use super::{layer, load_board, map, map_3x1_object};
|
||||
use super::{grid, load_board, map, map_3x1_object};
|
||||
use crate::archetype::Archetype;
|
||||
|
||||
/// Palette shorthands.
|
||||
const EMPTY: (&str, &str) = (".", "kind = \"empty\"");
|
||||
/// Palette shorthand.
|
||||
const PLAYER: (&str, &str) = ("@", "kind = \"player\"");
|
||||
const WALL: (&str, &str) = (
|
||||
"#",
|
||||
"kind = \"wall\", tile = 35, fg = \"#808080\", bg = \"#606060\"",
|
||||
);
|
||||
|
||||
/// An object palette entry body with the given `extra` fields appended.
|
||||
fn obj(extra: &str) -> String {
|
||||
@@ -25,14 +20,14 @@ fn duplicate_name_clears_second_but_keeps_both_objects() {
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"GH@",
|
||||
&[
|
||||
("G", &obj("name = \"gate\"")),
|
||||
("H", &obj("name = \"gate\", solid = false")),
|
||||
PLAYER,
|
||||
],
|
||||
)],
|
||||
),
|
||||
));
|
||||
assert_eq!(board.objects.len(), 2, "both objects survive");
|
||||
assert_eq!(board.objects[&1].name.as_deref(), Some("gate"));
|
||||
@@ -46,7 +41,7 @@ fn palette_placement_puts_object_on_empty_floor() {
|
||||
let board = load_board(&map_3x1_object("G.@", "G", ""));
|
||||
assert_eq!(board.objects.len(), 1);
|
||||
assert_eq!((board.objects[&1].x, board.objects[&1].y), (0, 0));
|
||||
assert_eq!(board.get(0, 0, 0).1, Archetype::Empty);
|
||||
assert_eq!(board.get(0, 0).1, Archetype::Empty);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -65,10 +60,10 @@ fn palette_char_multi_occurrence_only_first_keeps_name() {
|
||||
let board = load_board(&map(
|
||||
4,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"GGG@",
|
||||
&[("G", &obj("solid = false, name = \"guard\"")), PLAYER],
|
||||
)],
|
||||
),
|
||||
));
|
||||
assert_eq!(board.objects.len(), 3);
|
||||
assert_eq!(board.objects[&1].name.as_deref(), Some("guard"));
|
||||
@@ -77,41 +72,11 @@ fn palette_char_multi_occurrence_only_first_keeps_name() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solid_object_on_wall_is_dropped_but_non_solid_is_kept() {
|
||||
// Solid object stacked on a wall (different layer): dropped for the conflict.
|
||||
let solid = map(
|
||||
3,
|
||||
1,
|
||||
&[
|
||||
layer("#.@", &[WALL, EMPTY, PLAYER]),
|
||||
layer("O..", &[("O", &obj("")), EMPTY]),
|
||||
],
|
||||
);
|
||||
assert!(load_board(&solid).objects.is_empty());
|
||||
|
||||
// Same placement but non-solid: kept (it doesn't claim the cell's solidity).
|
||||
let nonsolid = map(
|
||||
3,
|
||||
1,
|
||||
&[
|
||||
layer("#.@", &[WALL, EMPTY, PLAYER]),
|
||||
layer("O..", &[("O", &obj("solid = false")), EMPTY]),
|
||||
],
|
||||
);
|
||||
assert_eq!(load_board(&nonsolid).objects.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_solid_object_on_a_cell_is_dropped() {
|
||||
// Two solid objects on the same cell across layers: the upper one is dropped.
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[
|
||||
layer("@O.", &[PLAYER, ("O", &obj("")), EMPTY]),
|
||||
layer(".O.", &[EMPTY, ("O", &obj(""))]),
|
||||
],
|
||||
));
|
||||
fn non_solid_object_and_wall_coexist_in_separate_cells() {
|
||||
// With one grid each cell holds a single palette char, so a solid object can
|
||||
// never be authored onto a wall cell. A wall and a (separate-cell) non-solid
|
||||
// object both load fine.
|
||||
let board = load_board(&map_3x1_object("#G@", "G", "solid = false"));
|
||||
assert_eq!(board.objects.len(), 1);
|
||||
assert!(!board.is_valid());
|
||||
assert!(board.is_valid());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{layer, load_board, map};
|
||||
use super::{grid, load_board, map};
|
||||
use crate::archetype::Archetype;
|
||||
|
||||
/// Palette shorthands shared by these tests.
|
||||
@@ -11,61 +11,22 @@ const WALL: (&str, &str) = (
|
||||
|
||||
#[test]
|
||||
fn player_char_places_on_empty_floor() {
|
||||
let b = load_board(&map(3, 1, &[layer(".@.", &[EMPTY, PLAYER])]));
|
||||
let b = load_board(&map(3, 1, &grid(".@.", &[EMPTY, PLAYER])));
|
||||
assert_eq!((b.player.x, b.player.y), (1, 0));
|
||||
assert_eq!(b.get(0, 1, 0).1, Archetype::Empty);
|
||||
assert!(b.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_wins_solid_terrain_silently() {
|
||||
// Wall on layer 0, player on layer 1 at the same cell: the wall is cleared,
|
||||
// no error reported.
|
||||
let b = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer("#..", &[WALL, EMPTY]), layer("@..", &[PLAYER, EMPTY])],
|
||||
));
|
||||
assert_eq!((b.player.x, b.player.y), (0, 0));
|
||||
assert_eq!(b.get(0, 0, 0).1, Archetype::Empty); // wall cleared on layer 0
|
||||
assert!(b.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_wins_against_a_solid_object_silently() {
|
||||
// A solid object on the player's cell (different layer) is dropped silently.
|
||||
let b = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[
|
||||
layer(
|
||||
".O.",
|
||||
&[
|
||||
EMPTY,
|
||||
(
|
||||
"O",
|
||||
"kind = \"object\", tile = 64, fg = \"#00FFFF\", bg = \"#000000\"",
|
||||
),
|
||||
],
|
||||
),
|
||||
layer(".@.", &[EMPTY, PLAYER]),
|
||||
],
|
||||
));
|
||||
assert_eq!((b.player.x, b.player.y), (1, 0));
|
||||
assert!(b.objects.is_empty());
|
||||
assert_eq!(b.get(1, 0).1, Archetype::Empty);
|
||||
assert!(b.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_char_appearing_twice_uses_first() {
|
||||
let b = load_board(&map(3, 1, &[layer("@.@", &[EMPTY, PLAYER])]));
|
||||
let b = load_board(&map(3, 1, &grid("@.@", &[EMPTY, PLAYER])));
|
||||
assert_eq!((b.player.x, b.player.y), (0, 0));
|
||||
assert!(!b.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_char_missing_falls_back_to_origin() {
|
||||
let b = load_board(&map(3, 1, &[layer("...", &[EMPTY])]));
|
||||
let b = load_board(&map(3, 1, &grid("...", &[EMPTY])));
|
||||
assert_eq!((b.player.x, b.player.y), (0, 0));
|
||||
assert!(!b.is_valid());
|
||||
}
|
||||
@@ -74,8 +35,30 @@ fn player_char_missing_falls_back_to_origin() {
|
||||
fn player_fallback_to_origin_clears_solid_terrain() {
|
||||
// No player cell, so the player falls back to (0, 0) — which holds a wall. The
|
||||
// player wins its cell: the wall is cleared. The fallback is still reported.
|
||||
let b = load_board(&map(3, 1, &[layer("#..", &[WALL, EMPTY])]));
|
||||
let b = load_board(&map(3, 1, &grid("#..", &[WALL, EMPTY])));
|
||||
assert_eq!((b.player.x, b.player.y), (0, 0));
|
||||
assert_eq!(b.get(0, 0, 0).1, Archetype::Empty);
|
||||
assert_eq!(b.get(0, 0).1, Archetype::Empty);
|
||||
assert!(!b.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_fallback_wins_against_a_solid_object() {
|
||||
// No player cell → player falls back to (0, 0), which holds a solid object. The
|
||||
// player wins its cell and the object is dropped.
|
||||
let b = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&grid(
|
||||
"O..",
|
||||
&[
|
||||
EMPTY,
|
||||
(
|
||||
"O",
|
||||
"kind = \"object\", tile = 64, fg = \"#00FFFF\", bg = \"#000000\"",
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
assert_eq!((b.player.x, b.player.y), (0, 0));
|
||||
assert!(b.objects.is_empty());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{layer, load_board, map};
|
||||
use super::{grid, load_board, map};
|
||||
|
||||
const EMPTY: (&str, &str) = (".", "kind = \"empty\"");
|
||||
const PLAYER: (&str, &str) = ("@", "kind = \"player\"");
|
||||
@@ -9,7 +9,7 @@ fn portal_duplicate_name_drops_second() {
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"12@",
|
||||
&[
|
||||
(
|
||||
@@ -22,7 +22,7 @@ fn portal_duplicate_name_drops_second() {
|
||||
),
|
||||
PLAYER,
|
||||
],
|
||||
)],
|
||||
),
|
||||
));
|
||||
assert_eq!(
|
||||
board.portals.len(),
|
||||
@@ -38,7 +38,7 @@ fn portal_palette_char_places_portal_at_grid_position() {
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"1.@",
|
||||
&[
|
||||
(
|
||||
@@ -48,7 +48,7 @@ fn portal_palette_char_places_portal_at_grid_position() {
|
||||
EMPTY,
|
||||
PLAYER,
|
||||
],
|
||||
)],
|
||||
),
|
||||
));
|
||||
assert_eq!(board.portals.len(), 1);
|
||||
assert_eq!((board.portals[0].x, board.portals[0].y), (0, 0));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Pushers are now scripted objects (the `pusher_*` archetypes expand into objects
|
||||
//! carrying the embedded `pusher.rhai` plus a `BUILTIN_pusher_<dir>` tag).
|
||||
|
||||
use super::{layer, load_board, map};
|
||||
use super::{grid, load_board, map};
|
||||
use crate::archetype::Archetype;
|
||||
use crate::game::GameState;
|
||||
use crate::map_file::MapFile;
|
||||
@@ -24,10 +24,10 @@ fn pusher_loads_as_a_tagged_scripted_solid_object() {
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"P @",
|
||||
&[("P", "kind = \"pusher_east\""), ("@", "kind = \"player\"")],
|
||||
)],
|
||||
),
|
||||
));
|
||||
let mut id = 0;
|
||||
let p = pusher(&board, &mut id);
|
||||
@@ -48,14 +48,14 @@ fn pusher_advances_and_shoves_a_crate() {
|
||||
let board = load_board(&map(
|
||||
5,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"Po @",
|
||||
&[
|
||||
("P", "kind = \"pusher_east\""),
|
||||
("o", "kind = \"crate\""),
|
||||
("@", "kind = \"player\""),
|
||||
],
|
||||
)],
|
||||
),
|
||||
));
|
||||
let mut pid = 0;
|
||||
pusher(&board, &mut pid);
|
||||
@@ -69,12 +69,12 @@ fn pusher_advances_and_shoves_a_crate() {
|
||||
let b = game.board();
|
||||
assert!(b.objects[&pid].x > 0, "pusher advanced east");
|
||||
assert_eq!(
|
||||
b.get(0, 1, 0).1,
|
||||
b.get(1, 0).1,
|
||||
Archetype::Empty,
|
||||
"crate left its start cell"
|
||||
);
|
||||
assert!(
|
||||
(2..b.width).any(|x| b.get(0, x, 0).1 == Archetype::Crate),
|
||||
(2..b.width).any(|x| b.get(x, 0).1 == Archetype::Crate),
|
||||
"crate was shoved east"
|
||||
);
|
||||
}
|
||||
@@ -84,14 +84,14 @@ fn pusher_blocked_by_wall_stays_put() {
|
||||
let board = load_board(&map(
|
||||
4,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"P# @",
|
||||
&[
|
||||
("P", "kind = \"pusher_east\""),
|
||||
("#", "kind = \"wall\""),
|
||||
("@", "kind = \"player\""),
|
||||
],
|
||||
)],
|
||||
),
|
||||
));
|
||||
let mut pid = 0;
|
||||
pusher(&board, &mut pid);
|
||||
@@ -113,10 +113,10 @@ fn pusher_round_trips_to_its_keyword() {
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"P @",
|
||||
&[("P", "kind = \"pusher_east\""), ("@", "kind = \"player\"")],
|
||||
)],
|
||||
),
|
||||
));
|
||||
// Save collapses the expanded object back into the `pusher_east` keyword.
|
||||
let toml_out = toml::to_string_pretty(&MapFile::from(&board)).unwrap();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{layer, load_board, map};
|
||||
use super::{grid, load_board, map};
|
||||
use crate::glyph::Glyph;
|
||||
use crate::map_file::{MapFile, parse_color};
|
||||
use color::Rgba8;
|
||||
@@ -25,7 +25,7 @@ fn round_trip(toml: &str) -> crate::board::Board {
|
||||
|
||||
#[test]
|
||||
fn object_glyph_round_trips_through_toml() {
|
||||
let toml = map(3, 1, &[layer("@O.", &[PLAYER, ("O", &obj("")), EMPTY])]);
|
||||
let toml = map(3, 1, &grid("@O.", &[PLAYER, ("O", &obj("")), EMPTY]));
|
||||
let board = load_board(&toml);
|
||||
assert_eq!(board.objects.len(), 1);
|
||||
let obj0 = &board.objects[&1];
|
||||
@@ -53,10 +53,10 @@ fn object_tags_round_trip_through_toml() {
|
||||
let toml = map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"@O.",
|
||||
&[PLAYER, ("O", &obj("tags = [\"enemy\", \"boss\"]")), EMPTY],
|
||||
)],
|
||||
),
|
||||
);
|
||||
let board = load_board(&toml);
|
||||
let obj0 = &board.objects[&1];
|
||||
@@ -75,7 +75,7 @@ fn object_tags_round_trip_through_toml() {
|
||||
|
||||
#[test]
|
||||
fn object_empty_tags_omitted_from_toml() {
|
||||
let toml = map(3, 1, &[layer("@O.", &[PLAYER, ("O", &obj("")), EMPTY])]);
|
||||
let toml = map(3, 1, &grid("@O.", &[PLAYER, ("O", &obj("")), EMPTY]));
|
||||
let board = load_board(&toml);
|
||||
let toml_out = toml::to_string_pretty(&MapFile::from(&board)).unwrap();
|
||||
assert!(
|
||||
@@ -89,10 +89,10 @@ fn object_name_round_trips_through_toml() {
|
||||
let toml = map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"@O.",
|
||||
&[PLAYER, ("O", &obj("name = \"beacon\"")), EMPTY],
|
||||
)],
|
||||
),
|
||||
);
|
||||
let board = load_board(&toml);
|
||||
assert_eq!(board.objects[&1].name.as_deref(), Some("beacon"));
|
||||
@@ -108,7 +108,7 @@ fn object_name_round_trips_through_toml() {
|
||||
|
||||
#[test]
|
||||
fn unnamed_object_name_stays_none_through_toml() {
|
||||
let toml = map(3, 1, &[layer("@O.", &[PLAYER, ("O", &obj("")), EMPTY])]);
|
||||
let toml = map(3, 1, &grid("@O.", &[PLAYER, ("O", &obj("")), EMPTY]));
|
||||
let board2 = round_trip(&toml);
|
||||
assert_eq!(
|
||||
board2.objects[&1].name, None,
|
||||
@@ -117,31 +117,41 @@ fn unnamed_object_name_stays_none_through_toml() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixed_floor_glyph_round_trips_through_toml() {
|
||||
// A fixed floor glyph (visual-only Empty cell) must survive save→load.
|
||||
let toml = map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
"@F.",
|
||||
&[
|
||||
PLAYER,
|
||||
(
|
||||
"F",
|
||||
"kind = \"floor\", tile = \"#\", fg = \"#010203\", bg = \"#040506\"",
|
||||
),
|
||||
EMPTY,
|
||||
],
|
||||
)],
|
||||
);
|
||||
fn fixed_floor_attribute_round_trips_through_toml() {
|
||||
// A fixed floor glyph is now a board attribute (`floor = { … }`), not a grid
|
||||
// cell. It must survive save→load and show through empty grid cells.
|
||||
let toml = "[map]\nname = \"Test\"\nwidth = 3\nheight = 1\n\
|
||||
floor = { tile = \"#\", fg = \"#010203\", bg = \"#040506\" }\n\
|
||||
[grid]\ncontent = \"\"\"\n@..\n\"\"\"\n\
|
||||
palette = { \"@\" = { kind = \"player\" }, \".\" = { kind = \"empty\" } }\n";
|
||||
let fixed = Glyph {
|
||||
tile: '#' as u32,
|
||||
fg: parse_color("#010203"),
|
||||
bg: parse_color("#040506"),
|
||||
};
|
||||
let board = load_board(&toml);
|
||||
let board = load_board(toml);
|
||||
// An empty grid cell reveals the fixed floor.
|
||||
assert_eq!(board.glyph_at(1, 0), fixed);
|
||||
|
||||
let board2 = round_trip(&toml);
|
||||
let board2 = round_trip(toml);
|
||||
assert_eq!(board2.glyph_at(1, 0), fixed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn biome_floor_round_trips_generator_name() {
|
||||
// A biome floor re-emits its generator name (not baked glyphs), so the save
|
||||
// stays compact and the reloaded board is identical.
|
||||
let toml = "[map]\nname = \"Test\"\nwidth = 3\nheight = 1\n\
|
||||
floor = { generator = \"grass\" }\n\
|
||||
[grid]\ncontent = \"\"\"\n@..\n\"\"\"\n\
|
||||
palette = { \"@\" = { kind = \"player\" }, \".\" = { kind = \"empty\" } }\n";
|
||||
let board = load_board(toml);
|
||||
let toml_out = toml::to_string_pretty(&MapFile::from(&board)).unwrap();
|
||||
assert!(
|
||||
toml_out.contains("generator = \"grass\""),
|
||||
"biome floor must re-emit its generator name, got: {toml_out}"
|
||||
);
|
||||
// Reloaded floor glyphs match (deterministic seed).
|
||||
let board2 = load_board(&toml_out);
|
||||
assert_eq!(board2.glyph_at(1, 0), board.glyph_at(1, 0));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! into objects carrying the embedded `spinner.rhai` plus a `BUILTIN_spinner_<dir>`
|
||||
//! tag, and collapse back to the keyword on save (just like pushers).
|
||||
|
||||
use super::{layer, load_board, map};
|
||||
use super::{grid, load_board, map};
|
||||
use crate::map_file::MapFile;
|
||||
|
||||
#[test]
|
||||
@@ -10,10 +10,10 @@ fn spinner_loads_as_a_tagged_scripted_solid_object() {
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"S @",
|
||||
&[("S", "kind = \"spinner_cw\""), ("@", "kind = \"player\"")],
|
||||
)],
|
||||
),
|
||||
));
|
||||
let (_, obj) = board
|
||||
.objects
|
||||
@@ -36,10 +36,10 @@ fn spinner_round_trips_to_its_keyword() {
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"S @",
|
||||
&[("S", "kind = \"spinner_ccw\""), ("@", "kind = \"player\"")],
|
||||
)],
|
||||
),
|
||||
));
|
||||
// Save collapses the expanded object back into the `spinner_ccw` keyword.
|
||||
let toml_out = toml::to_string_pretty(&MapFile::from(&board)).unwrap();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! `BUILTIN_transporter_<dir>` tag). Bumping one from its facing side teleports
|
||||
//! the bumper past it, or out of a paired opposite-facing transporter.
|
||||
|
||||
use super::{layer, load_board, map};
|
||||
use super::{grid, load_board, map};
|
||||
use crate::game::GameState;
|
||||
use crate::map_file::MapFile;
|
||||
use crate::utils::Direction;
|
||||
@@ -14,10 +14,10 @@ fn transporter_loads_as_a_tagged_scripted_solid_object() {
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"@T ",
|
||||
&[("@", "kind = \"player\""), ("T", "kind = \"transporter_east\"")],
|
||||
)],
|
||||
),
|
||||
));
|
||||
let (_, obj) = board
|
||||
.objects
|
||||
@@ -37,10 +37,10 @@ fn bumping_a_transporter_drops_you_on_its_far_side() {
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"@T ",
|
||||
&[("@", "kind = \"player\""), ("T", "kind = \"transporter_east\"")],
|
||||
)],
|
||||
),
|
||||
));
|
||||
let mut game = GameState::new(board);
|
||||
game.run_init();
|
||||
@@ -63,7 +63,7 @@ fn a_blocked_far_side_transports_out_of_the_paired_transporter() {
|
||||
let board = load_board(&map(
|
||||
6,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"@T# W ",
|
||||
&[
|
||||
("@", "kind = \"player\""),
|
||||
@@ -71,7 +71,7 @@ fn a_blocked_far_side_transports_out_of_the_paired_transporter() {
|
||||
("#", "kind = \"wall\", tile = 35, fg = \"#808080\", bg = \"#606060\""),
|
||||
("W", "kind = \"transporter_west\""),
|
||||
],
|
||||
)],
|
||||
),
|
||||
));
|
||||
let mut game = GameState::new(board);
|
||||
game.run_init();
|
||||
@@ -95,14 +95,14 @@ fn a_pushed_crate_is_transported_through() {
|
||||
let board = load_board(&map(
|
||||
4,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"@oT ",
|
||||
&[
|
||||
("@", "kind = \"player\""),
|
||||
("o", "kind = \"crate\""),
|
||||
("T", "kind = \"transporter_east\""),
|
||||
],
|
||||
)],
|
||||
),
|
||||
));
|
||||
let mut game = GameState::new(board);
|
||||
game.run_init();
|
||||
@@ -130,10 +130,10 @@ fn transporter_round_trips_to_its_keyword() {
|
||||
let board = load_board(&map(
|
||||
3,
|
||||
1,
|
||||
&[layer(
|
||||
&grid(
|
||||
"@T ",
|
||||
&[("@", "kind = \"player\""), ("T", "kind = \"transporter_east\"")],
|
||||
)],
|
||||
),
|
||||
));
|
||||
// Save collapses the expanded object back into the `transporter_east` keyword.
|
||||
let toml_out = toml::to_string_pretty(&MapFile::from(&board)).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user