feat: module 06 parser
First Part II module. The cumulative reference adds a second engine (src/engine/puzzle: grid, lexer, parser) beside Part I's; nouns and properties are data in src/content/words.json, and the engine names only IS/AND/NOT/TEXT and the base rule TEXT IS PUSH. Rules are recompiled from the board on every change; rules() is a sorted set. Adds the M6 scenarios and tests (rows, columns, shared IS, AND distribution, stacked text, base rule), the HOT word as a data-only Expansion Drop, the lesson with the Part II bridge, and the Rust LevelDef shape in harness.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e04dc1a11f
commit
68fe9834c3
55 files changed
+5679
-1
No files matched your search
@@ -0,0 +1,118 @@
|
||||
// Module 6: Grid, lexer and parser. 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()
|
||||
}
|
||||
}
|
||||
|
||||
fn object(kind: &str, x: i32, y: i32) -> LevelObject {
|
||||
LevelObject {
|
||||
kind: kind.to_string(),
|
||||
x,
|
||||
y,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_06_horizontal_rule() {
|
||||
let b = new_baba(LevelDef {
|
||||
width: 5,
|
||||
height: 3,
|
||||
objects: vec![
|
||||
object("text:BABA", 0, 0),
|
||||
object("text:IS", 1, 0),
|
||||
object("text:YOU", 2, 0),
|
||||
object("baba", 3, 2),
|
||||
object("text:YOU", 0, 2),
|
||||
object("text:IS", 1, 2),
|
||||
object("text:FLAG", 2, 2),
|
||||
],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert_eq!(b.rules(), vec!["BABA IS YOU", "TEXT IS PUSH"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_06_crossword_shared_is() {
|
||||
let mut b = new_baba(empty_level(3, 3));
|
||||
write(
|
||||
&mut b,
|
||||
&[("BABA", 0, 1), ("IS", 1, 1), ("YOU", 2, 1), ("FLAG", 1, 0)],
|
||||
);
|
||||
|
||||
assert_eq!(b.rules(), vec!["BABA IS YOU", "TEXT IS PUSH"]);
|
||||
|
||||
b.place("text:WIN", 1, 2);
|
||||
|
||||
assert_eq!(
|
||||
b.rules(),
|
||||
vec!["BABA IS YOU", "FLAG IS WIN", "TEXT IS PUSH"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_06_and_distributes() {
|
||||
let mut b = new_baba(empty_level(7, 1));
|
||||
write(
|
||||
&mut b,
|
||||
&[
|
||||
("BABA", 0, 0),
|
||||
("AND", 1, 0),
|
||||
("ROCK", 2, 0),
|
||||
("IS", 3, 0),
|
||||
("PUSH", 4, 0),
|
||||
("AND", 5, 0),
|
||||
("STOP", 6, 0),
|
||||
],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
b.rules(),
|
||||
vec![
|
||||
"BABA IS PUSH",
|
||||
"BABA IS STOP",
|
||||
"ROCK IS PUSH",
|
||||
"ROCK IS STOP",
|
||||
"TEXT IS PUSH"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_06_stacked_text_all_combinations() {
|
||||
let mut b = new_baba(empty_level(3, 1));
|
||||
write(
|
||||
&mut b,
|
||||
&[("BABA", 0, 0), ("ROCK", 0, 0), ("IS", 1, 0), ("YOU", 2, 0)],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
b.rules(),
|
||||
vec!["BABA IS YOU", "ROCK IS YOU", "TEXT IS PUSH"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_06_default_text_is_push() {
|
||||
let b = new_baba(LevelDef {
|
||||
width: 3,
|
||||
height: 3,
|
||||
objects: vec![object("baba", 1, 1)],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert_eq!(b.rules(), vec!["TEXT IS PUSH"]);
|
||||
}
|
||||
Reference in new issue
Block a user