Files
systemic-game-design-course/course/11-conditions/reference/rust/src/content/conditions.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

55 lines
1.9 KiB
Rust

//! Part II: what each condition word means (Module 11). The engine asks these checks
//! every time it is about to use a rule with a condition, for one object, against the
//! board as it is then. A new condition word is one entry in `words.json`, with role
//! "condition", and one check here.
use std::collections::HashSet;
use crate::engine::puzzle::{ConditionCheck, ConditionChecks, Level, ObjectId};
/// The condition checks, by condition word.
pub fn conditions() -> ConditionChecks {
ConditionChecks::from([("ON".to_string(), on as ConditionCheck)])
}
/// Can each noun be given an object of its own among `others`, one the noun names?
/// `ON ROCK AND ROCK` needs two rocks (the wiki: "AND in the noun2 part requires at
/// least as many objects as are mentioned"). Tries every assignment.
fn each_has_one(level: &Level, nouns: &[String], others: &[ObjectId]) -> bool {
fn assign(
level: &Level,
nouns: &[String],
others: &[ObjectId],
taken: &mut HashSet<ObjectId>,
) -> bool {
let Some((noun, rest)) = nouns.split_first() else {
return true;
};
for &other in others {
if taken.contains(&other) || !level.names(noun, other) {
continue;
}
taken.insert(other);
if assign(level, rest, others, taken) {
return true;
}
taken.remove(&other);
}
false
}
assign(level, nouns, others, &mut HashSet::new())
}
/// ON: for each noun, another object on the object's tile that the noun names, a
/// different one for each noun. Float levels don't matter ("ON ignores FLOAT levels").
fn on(level: &Level, id: ObjectId, nouns: &[String]) -> bool {
let o = level.board().get(id);
let others: Vec<ObjectId> = level
.board()
.at(o.x, o.y)
.map(|other| other.id)
.filter(|&other| other != id)
.collect();
each_has_one(level, nouns, &others)
}