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,48 @@
|
||||
//! Content: the cards and the Baba Is You words. Only this directory (and the card data in `cards/`) may name
|
||||
//! specific cards, or specific Baba Is You nouns.
|
||||
//!
|
||||
//! From Module 5 every card is a JSON file in `cards/` at the crate root, in the
|
||||
//! course's card format (`course/schema/card.schema.json`). The functions that
|
||||
//! were card effects in Modules 2–4 are gone: the engine interprets the data.
|
||||
//! `start-module` copies the course's card files into `cards/`; add your own there.
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::engine::CardDef;
|
||||
|
||||
/// Every card in `cards/`, in file-name order. Read once per test run.
|
||||
pub fn cards() -> Vec<CardDef> {
|
||||
static CARDS: OnceLock<Vec<CardDef>> = OnceLock::new();
|
||||
CARDS
|
||||
.get_or_init(|| load_dir(&Path::new(env!("CARGO_MANIFEST_DIR")).join("cards")))
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Parse every `.json` file in `dir` as a card definition.
|
||||
pub fn load_dir(dir: &Path) -> Vec<CardDef> {
|
||||
let entries = std::fs::read_dir(dir)
|
||||
.unwrap_or_else(|e| panic!("cannot read card directory {}: {e}", dir.display()));
|
||||
let mut paths: Vec<_> = entries
|
||||
.map(|entry| entry.expect("card directory entry").path())
|
||||
.filter(|p| p.extension().and_then(|x| x.to_str()) == Some("json"))
|
||||
.collect();
|
||||
paths.sort();
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let text = std::fs::read_to_string(path)
|
||||
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
|
||||
serde_json::from_str(&text)
|
||||
.unwrap_or_else(|e| panic!("{} is not a valid card: {e}", path.display()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Part II: the words of Baba Is You the parser can read, besides the engine's own
|
||||
/// (`IS`, `AND`, `NOT`, `TEXT`). Nouns and properties are data: a new word is one
|
||||
/// more entry in `words.json` in this directory.
|
||||
pub fn words() -> Vec<crate::engine::puzzle::WordDef> {
|
||||
serde_json::from_str(include_str!("words.json"))
|
||||
.expect("src/content/words.json is not a word list")
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{ "name": "BABA", "role": "noun" },
|
||||
{ "name": "ROCK", "role": "noun" },
|
||||
{ "name": "WALL", "role": "noun" },
|
||||
{ "name": "FLAG", "role": "noun" },
|
||||
{ "name": "LAVA", "role": "noun" },
|
||||
{ "name": "KEY", "role": "noun" },
|
||||
{ "name": "DOOR", "role": "noun" },
|
||||
{ "name": "SKULL", "role": "noun" },
|
||||
{ "name": "WATER", "role": "noun" },
|
||||
{ "name": "YOU", "role": "property" },
|
||||
{ "name": "WIN", "role": "property" },
|
||||
{ "name": "PUSH", "role": "property" },
|
||||
{ "name": "STOP", "role": "property" }
|
||||
]
|
||||
Reference in new issue
Block a user