Adds IS NOT to the Part II parser and a resolution pass (negation, then
identity lock) that turns parsed rules into the rules in force; harness
gains activeRules() and hasProperty(). A leading NOT now breaks the
sentence instead of being ignored (carried from M6 review). Nouns say
which objects they name ("selects"), so the ALL Expansion Drop is one
line of data. Corrects lesson 06's NOT wording; harness.md M7 details.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
139 lines
3.5 KiB
Rust
139 lines
3.5 KiB
Rust
// Module 7: Resolving rules. 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_07_not_overrides() {
|
|
let mut b = new_baba(empty_level(4, 5));
|
|
write(&mut b, &[("BABA", 0, 0), ("IS", 1, 0), ("YOU", 2, 0)]);
|
|
let baba = b.place("baba", 0, 4);
|
|
|
|
assert!(b.has_property(baba, "YOU"));
|
|
|
|
write(
|
|
&mut b,
|
|
&[("BABA", 0, 2), ("IS", 1, 2), ("NOT", 2, 2), ("YOU", 3, 2)],
|
|
);
|
|
|
|
assert_eq!(
|
|
b.rules(),
|
|
vec!["BABA IS NOT YOU", "BABA IS YOU", "TEXT IS PUSH"]
|
|
);
|
|
assert_eq!(b.active_rules(), vec!["BABA IS NOT YOU", "TEXT IS PUSH"]);
|
|
assert!(!b.has_property(baba, "YOU"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_07_x_is_x_blocks_conversion() {
|
|
let mut b = new_baba(empty_level(3, 5));
|
|
write(
|
|
&mut b,
|
|
&[
|
|
("BABA", 0, 0),
|
|
("IS", 1, 0),
|
|
("BABA", 2, 0),
|
|
("BABA", 0, 2),
|
|
("IS", 1, 2),
|
|
("ROCK", 2, 2),
|
|
("ROCK", 0, 4),
|
|
("IS", 1, 4),
|
|
("BABA", 2, 4),
|
|
],
|
|
);
|
|
|
|
assert_eq!(
|
|
b.rules(),
|
|
vec![
|
|
"BABA IS BABA",
|
|
"BABA IS ROCK",
|
|
"ROCK IS BABA",
|
|
"TEXT IS PUSH"
|
|
]
|
|
);
|
|
assert_eq!(
|
|
b.active_rules(),
|
|
vec!["BABA IS BABA", "ROCK IS BABA", "TEXT IS PUSH"]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_07_x_is_not_x_deletes_and_unlocks() {
|
|
let mut b = new_baba(empty_level(4, 5));
|
|
write(
|
|
&mut b,
|
|
&[
|
|
("BABA", 0, 0),
|
|
("IS", 1, 0),
|
|
("NOT", 2, 0),
|
|
("BABA", 3, 0),
|
|
("BABA", 0, 2),
|
|
("IS", 1, 2),
|
|
("BABA", 2, 2),
|
|
("BABA", 0, 4),
|
|
("IS", 1, 4),
|
|
("ROCK", 2, 4),
|
|
],
|
|
);
|
|
|
|
assert_eq!(
|
|
b.rules(),
|
|
vec![
|
|
"BABA IS BABA",
|
|
"BABA IS NOT BABA",
|
|
"BABA IS ROCK",
|
|
"TEXT IS PUSH"
|
|
]
|
|
);
|
|
let active = b.active_rules();
|
|
assert!(active.contains(&"BABA IS ROCK".to_string()));
|
|
assert!(!active.contains(&"BABA IS BABA".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_07_text_is_not_push() {
|
|
let mut b = new_baba(empty_level(4, 3));
|
|
let rock_text = b.place("text:ROCK", 0, 2);
|
|
let rock = b.place("rock", 3, 2);
|
|
|
|
assert_eq!(b.active_rules(), vec!["TEXT IS PUSH"]);
|
|
assert!(b.has_property(rock_text, "PUSH"));
|
|
assert!(!b.has_property(rock, "PUSH"));
|
|
|
|
let text_text = b.place("text:TEXT", 0, 0);
|
|
write(&mut b, &[("IS", 1, 0), ("NOT", 2, 0), ("PUSH", 3, 0)]);
|
|
|
|
assert_eq!(b.rules(), vec!["TEXT IS NOT PUSH", "TEXT IS PUSH"]);
|
|
assert_eq!(b.active_rules(), vec!["TEXT IS NOT PUSH"]);
|
|
assert!(!b.has_property(rock_text, "PUSH"));
|
|
assert!(!b.has_property(text_text, "PUSH"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_07_leading_not_breaks_sentence() {
|
|
let mut b = new_baba(empty_level(4, 2));
|
|
write(
|
|
&mut b,
|
|
&[("NOT", 0, 0), ("BABA", 1, 0), ("IS", 2, 0), ("YOU", 3, 0)],
|
|
);
|
|
let baba = b.place("baba", 0, 1);
|
|
|
|
assert_eq!(b.rules(), vec!["TEXT IS PUSH"]);
|
|
assert_eq!(b.active_rules(), vec!["TEXT IS PUSH"]);
|
|
assert!(!b.has_property(baba, "YOU"));
|
|
}
|