feat: module 08 push chains
Movement planned in full, then committed: a Planner builds a plan (moving and destroyed ids) by running content's movement responses (OPEN/SHUT, PUSH, STOP) for every step, rolling back failed branches. Edge of level blocks; YOU objects move furthest-ahead first; rules are reparsed once after movement. Harness: move/move_dir(Dir), posOf, kindsAt. Expansion Drop: PULL as one word plus one content response. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d993457772
commit
bd80cefbef
@@ -0,0 +1,91 @@
|
||||
//! The interpreter: turns one effect, as data, into engine actions.
|
||||
//!
|
||||
//! This is the only place that knows what an effect *means*. A card's text is a
|
||||
//! small tree (clauses holding effects holding targets); the engine walks it. The
|
||||
//! effect vocabulary is closed: a card can only do what this match can do, which is
|
||||
//! what makes a data card safe to load.
|
||||
|
||||
use super::card::{Effect, Target};
|
||||
use super::queue::{Action, Ctx};
|
||||
use super::world::EntityId;
|
||||
|
||||
/// The actions one effect performs, in order, given where and for whom it runs.
|
||||
pub fn actions(effect: &Effect, ctx: &Ctx) -> Vec<Action> {
|
||||
let owner = ctx.owner;
|
||||
match effect {
|
||||
Effect::DealDamage { target, amount } => vec![Action::Damage {
|
||||
targets: targets(*target, ctx),
|
||||
amount: *amount,
|
||||
}],
|
||||
Effect::Draw { count } => (0..*count)
|
||||
.map(|_| Action::Draw { player: owner })
|
||||
.collect(),
|
||||
Effect::Summon { card, count } => (0..*count)
|
||||
.map(|_| Action::Summon {
|
||||
card: card.clone(),
|
||||
player: owner,
|
||||
at: summon_position(ctx),
|
||||
})
|
||||
.collect(),
|
||||
Effect::GainArmor { amount } => vec![Action::GainArmor {
|
||||
player: owner,
|
||||
amount: *amount,
|
||||
}],
|
||||
Effect::GainAttack { target, amount } => targets(*target, ctx)
|
||||
.into_iter()
|
||||
.map(|id| Action::GainAttack {
|
||||
id,
|
||||
amount: *amount,
|
||||
})
|
||||
.collect(),
|
||||
Effect::Destroy { target } => vec![Action::Destroy {
|
||||
targets: targets(*target, ctx),
|
||||
}],
|
||||
Effect::Assemble { filter } => vec![Action::Assemble {
|
||||
player: owner,
|
||||
filter: filter.clone(),
|
||||
}],
|
||||
Effect::BuffHand { attack, health } => ctx
|
||||
.world
|
||||
.minions_in_hand(owner)
|
||||
.into_iter()
|
||||
.map(|id| Action::Buff {
|
||||
id,
|
||||
attack: *attack,
|
||||
health: *health,
|
||||
})
|
||||
.collect(),
|
||||
Effect::SpawnText { .. } => panic!("spawnText needs a grid: it is a Part III effect"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The entities a target names, from where the effect runs.
|
||||
fn targets(target: Target, ctx: &Ctx) -> Vec<EntityId> {
|
||||
let world = ctx.world;
|
||||
let others = |ids: Vec<EntityId>| -> Vec<EntityId> {
|
||||
ids.into_iter()
|
||||
.filter(|&id| Some(id) != ctx.source)
|
||||
.collect()
|
||||
};
|
||||
match target {
|
||||
Target::This => vec![ctx.minion()],
|
||||
Target::AllMinions => world.minions_in_play(),
|
||||
Target::AllOtherMinions => others(world.minions_in_play()),
|
||||
Target::AllOtherCharacters => {
|
||||
let mut ids = vec![world.hero(0), world.hero(1)];
|
||||
ids.extend(world.minions_in_play());
|
||||
others(ids)
|
||||
}
|
||||
Target::EnemyHero => vec![ctx.enemy_hero()],
|
||||
Target::Chosen => ctx.target.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a summon goes: to the right of a source still on the board; where a dead
|
||||
/// source stood (its Death Event's position); at the right end for a spell.
|
||||
fn summon_position(ctx: &Ctx) -> Option<usize> {
|
||||
match ctx.source.and_then(|s| ctx.world.board_index(s)) {
|
||||
Some(index) => Some(index + 1),
|
||||
None => ctx.position,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user