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>
341 lines
8.9 KiB
Rust
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());
|
|
}
|