Files
systemic-game-design-course/course/11-conditions/tests/rust/test_11.rs
T
Lucas WintherandClaude Opus 5.5 34c655c1a4 feat: Module 11, Conditions (NOUN ON NOUN IS ..., checked per object at use; NEAR as the Expansion Drop)
Adds course/11-conditions: lesson, scenarios, nine test_11 tests in both
tracks, the cumulative reference (Module 10's plus conditions) and the NEAR
drop. The engine learns a condition role, rules carry conditions, and
Level.hasProperty and conversion evaluate content's condition checks against
the board whenever a rule is used. Resolution pairs only rules with the same
conditions. Catalog, harness contract, research notes and the diff-tracks
generator follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 13:00:26 +01:00

341 lines
8.9 KiB
Rust

// Module 11: Conditions. Each test matches a heading in scenarios.md.
use engine::harness::*;
/// An empty level `width` wide and `height` high.
fn empty_level(width: i32, height: i32) -> LevelDef {
LevelDef {
width,
height,
..Default::default()
}
}
/// Place one text object per word: `(word, x, y)`.
fn write(b: &mut Baba, words: &[(&str, i32, i32)]) {
for &(word, x, y) in words {
b.place(&format!("text:{word}"), x, y);
}
}
#[test]
fn test_11_condition_gates_property() {
let mut b = new_baba(empty_level(6, 4));
write(
&mut b,
&[
("KEY", 0, 0),
("ON", 1, 0),
("ROCK", 2, 0),
("IS", 3, 0),
("WIN", 4, 0),
],
);
let on_rock = b.place("key", 0, 2);
let rock = b.place("rock", 0, 2);
let alone = b.place("key", 2, 2);
assert_eq!(b.rules(), vec!["KEY ON ROCK IS WIN", "TEXT IS PUSH"]);
assert_eq!(b.active_rules(), vec!["KEY ON ROCK IS WIN", "TEXT IS PUSH"]);
assert!(b.has_property(on_rock, "WIN"));
assert!(!b.has_property(alone, "WIN"));
assert!(!b.has_property(rock, "WIN"));
}
#[test]
fn test_11_condition_follows_the_object() {
let mut b = new_baba(empty_level(6, 5));
write(&mut b, &[("BABA", 0, 0), ("IS", 1, 0), ("YOU", 2, 0)]);
write(&mut b, &[("LAVA", 0, 1), ("IS", 1, 1), ("HOT", 2, 1)]);
write(
&mut b,
&[
("BABA", 0, 2),
("ON", 1, 2),
("KEY", 2, 2),
("IS", 3, 2),
("MELT", 4, 2),
],
);
let to_key = b.place("baba", 0, 4);
b.place("key", 1, 4);
b.place("lava", 1, 4);
let to_lava = b.place("baba", 3, 4);
b.place("lava", 4, 4);
let before = b.active_rules();
assert!(!b.has_property(to_key, "MELT"));
assert!(!b.has_property(to_lava, "MELT"));
b.step(Dir::Right);
assert_eq!(b.active_rules(), before);
assert_eq!(b.pos_of(to_key), None);
assert_eq!(b.kinds_at(1, 4), vec!["key", "lava"]);
assert_eq!(b.pos_of(to_lava), Some((4, 4)));
assert_eq!(b.kinds_at(4, 4), vec!["baba", "lava"]);
}
#[test]
fn test_11_and_requires_both() {
let mut b = new_baba(empty_level(7, 4));
write(
&mut b,
&[
("KEY", 0, 0),
("ON", 1, 0),
("ROCK", 2, 0),
("AND", 3, 0),
("FLAG", 4, 0),
("IS", 5, 0),
("WIN", 6, 0),
],
);
let with_rock = b.place("key", 0, 2);
b.place("rock", 0, 2);
let with_flag = b.place("key", 2, 2);
b.place("flag", 2, 2);
let with_both = b.place("key", 4, 2);
b.place("rock", 4, 2);
b.place("flag", 4, 2);
assert_eq!(
b.rules(),
vec!["KEY ON ROCK AND FLAG IS WIN", "TEXT IS PUSH"]
);
assert!(!b.has_property(with_rock, "WIN"));
assert!(!b.has_property(with_flag, "WIN"));
assert!(b.has_property(with_both, "WIN"));
}
#[test]
fn test_11_and_counts_objects() {
let mut b = new_baba(empty_level(7, 4));
write(
&mut b,
&[
("KEY", 0, 0),
("ON", 1, 0),
("ROCK", 2, 0),
("AND", 3, 0),
("ROCK", 4, 0),
("IS", 5, 0),
("WIN", 6, 0),
],
);
let one_rock = b.place("key", 0, 2);
b.place("rock", 0, 2);
let two_rocks = b.place("key", 2, 2);
b.place("rock", 2, 2);
b.place("rock", 2, 2);
assert_eq!(
b.rules(),
vec!["KEY ON ROCK AND ROCK IS WIN", "TEXT IS PUSH"]
);
assert!(!b.has_property(one_rock, "WIN"));
assert!(b.has_property(two_rocks, "WIN"));
}
#[test]
fn test_11_conditional_not_only_where_it_holds() {
let mut b = new_baba(empty_level(6, 7));
write(&mut b, &[("BABA", 0, 0), ("IS", 1, 0), ("YOU", 2, 0)]);
write(
&mut b,
&[
("BABA", 0, 2),
("ON", 1, 2),
("KEY", 2, 2),
("IS", 3, 2),
("NOT", 4, 2),
("YOU", 5, 2),
],
);
let on_key = b.place("baba", 0, 6);
b.place("key", 0, 6);
let free = b.place("baba", 2, 6);
assert_eq!(
b.active_rules(),
vec!["BABA IS YOU", "BABA ON KEY IS NOT YOU", "TEXT IS PUSH"]
);
assert!(!b.has_property(on_key, "YOU"));
assert!(b.has_property(free, "YOU"));
write(
&mut b,
&[
("BABA", 0, 4),
("ON", 1, 4),
("KEY", 2, 4),
("IS", 3, 4),
("YOU", 4, 4),
],
);
assert!(b.rules().contains(&"BABA ON KEY IS YOU".to_string()));
assert_eq!(
b.active_rules(),
vec!["BABA IS YOU", "BABA ON KEY IS NOT YOU", "TEXT IS PUSH"]
);
b.step(Dir::Right);
assert_eq!(b.pos_of(on_key), Some((0, 6)));
assert_eq!(b.pos_of(free), Some((3, 6)));
}
#[test]
fn test_11_conditional_conversion() {
let mut b = new_baba(empty_level(6, 5));
write(&mut b, &[("BABA", 0, 0), ("IS", 1, 0), ("YOU", 2, 0)]);
write(&mut b, &[("ROCK", 0, 1), ("IS", 1, 1), ("PUSH", 2, 1)]);
write(
&mut b,
&[
("KEY", 0, 2),
("ON", 1, 2),
("ROCK", 2, 2),
("IS", 3, 2),
("FLAG", 4, 2),
],
);
let resting = b.place("key", 0, 4);
b.place("rock", 0, 4);
let baba = b.place("baba", 2, 4);
let pushed = b.place("rock", 3, 4);
let reached = b.place("key", 4, 4);
let alone = b.place("key", 5, 4);
b.step(Dir::Right);
assert_eq!(b.pos_of(baba), Some((3, 4)));
assert_eq!(b.pos_of(pushed), Some((4, 4)));
assert_eq!(b.kinds_at(0, 4), vec!["flag", "rock"]);
assert_eq!(b.pos_of(resting), Some((0, 4)));
assert_eq!(b.kinds_at(4, 4), vec!["flag", "rock"]);
assert_eq!(b.pos_of(reached), Some((4, 4)));
assert_eq!(b.kinds_at(5, 4), vec!["key"]);
assert_eq!(b.pos_of(alone), Some((5, 4)));
}
#[test]
fn test_11_condition_and_stacked_text() {
let mut b = new_baba(empty_level(6, 7));
write(
&mut b,
&[
("BABA", 0, 0),
("ON", 1, 0),
("KEY", 2, 0),
("ROCK", 2, 0),
("IS", 3, 0),
("WIN", 4, 0),
],
);
write(
&mut b,
&[
("FLAG", 0, 2),
("ON", 1, 2),
("AND", 1, 2),
("KEY", 2, 2),
("IS", 3, 2),
("PUSH", 4, 2),
],
);
let on_rock = b.place("baba", 0, 4);
b.place("rock", 0, 4);
let on_key = b.place("baba", 2, 4);
b.place("key", 2, 4);
let alone = b.place("baba", 4, 4);
assert_eq!(
b.rules(),
vec![
"BABA ON KEY IS WIN",
"BABA ON ROCK IS WIN",
"FLAG IS PUSH",
"FLAG ON KEY IS PUSH",
"KEY IS PUSH",
"TEXT IS PUSH"
]
);
assert!(b.has_property(on_rock, "WIN"));
assert!(b.has_property(on_key, "WIN"));
assert!(!b.has_property(alone, "WIN"));
}
#[test]
fn test_11_on_group_noun() {
let mut b = new_baba(empty_level(8, 4));
write(
&mut b,
&[
("ROCK", 0, 0),
("ON", 1, 0),
("ALL", 2, 0),
("IS", 3, 0),
("WIN", 4, 0),
],
);
let alone = b.place("rock", 0, 2);
let on_text = b.place("rock", 2, 2);
b.place("text:BABA", 2, 2);
let on_key = b.place("rock", 4, 2);
b.place("key", 4, 2);
let first = b.place("rock", 6, 2);
let second = b.place("rock", 6, 2);
assert_eq!(b.rules(), vec!["ROCK ON ALL IS WIN", "TEXT IS PUSH"]);
assert!(!b.has_property(alone, "WIN"));
assert!(!b.has_property(on_text, "WIN"));
assert!(b.has_property(on_key, "WIN"));
assert!(b.has_property(first, "WIN"));
assert!(b.has_property(second, "WIN"));
}
#[test]
fn test_11_condition_rechecked_between_hazards() {
let mut b = new_baba(empty_level(6, 8));
write(&mut b, &[("BABA", 0, 0), ("IS", 1, 0), ("YOU", 2, 0)]);
write(
&mut b,
&[
("BABA", 0, 1),
("AND", 1, 1),
("LAVA", 2, 1),
("IS", 3, 1),
("FLOAT", 4, 1),
],
);
write(&mut b, &[("LAVA", 0, 2), ("IS", 1, 2), ("HOT", 2, 2)]);
write(&mut b, &[("KEY", 0, 3), ("IS", 1, 3), ("SINK", 2, 3)]);
write(
&mut b,
&[
("BABA", 0, 4),
("ON", 1, 4),
("KEY", 2, 4),
("IS", 3, 4),
("MELT", 4, 4),
],
);
let baba = b.place("baba", 2, 6);
let lava = b.place("lava", 3, 6);
let key = b.place("key", 3, 6);
let rock = b.place("rock", 3, 6);
assert!(b.rules().contains(&"BABA ON KEY IS MELT".to_string()));
assert!(!b.has_property(baba, "MELT"));
b.step(Dir::Right);
assert_eq!(b.pos_of(key), None);
assert_eq!(b.pos_of(rock), None);
assert_eq!(b.pos_of(lava), Some((3, 6)));
assert_eq!(b.pos_of(baba), Some((3, 6)));
assert_eq!(b.kinds_at(3, 6), vec!["baba", "lava"]);
assert!(!b.lost());
}