//! Test harness: the thin adapter the course tests call. It translates each //! call into engine operations and holds no rules of its own. use crate::content; use crate::engine::{run_sequence, Action, Event, World}; pub use crate::engine::Zone; pub type Id = u32; pub type Player = u8; #[derive(Debug, Default, Clone)] pub struct GameOpts { pub decks: Option<[Vec; 2]>, } /// The events the tests read from `log()` (see course/harness.md). #[derive(Debug, Clone, PartialEq, Eq)] pub enum GameEvent { Summoned { id: Id }, Damaged { target: Id, amount: i32 }, Triggered { source: Id, trigger: String }, Destroyed { id: Id }, DeathPhase { round: u32 }, Drew { player: Player, id: Id }, Fatigue { player: Player }, ArmorGained { player: Player, amount: i32 }, StatsChanged { id: Id, attack: i32, health: i32 }, SpellCast { card: String }, } impl From<&Event> for GameEvent { fn from(event: &Event) -> GameEvent { match event.clone() { Event::Summoned { id } => GameEvent::Summoned { id }, Event::Damaged { target, amount } => GameEvent::Damaged { target, amount }, Event::Triggered { source, on } => GameEvent::Triggered { source, trigger: on.label().to_string(), }, Event::Drew { player, id } => GameEvent::Drew { player, id }, Event::Fatigue { player } => GameEvent::Fatigue { player }, Event::ArmorGained { player, amount } => GameEvent::ArmorGained { player, amount }, Event::StatsChanged { id, attack, health } => { GameEvent::StatsChanged { id, attack, health } } Event::SpellCast { card } => GameEvent::SpellCast { card }, Event::Destroyed { id, .. } => GameEvent::Destroyed { id }, Event::DeathPhase { round } => GameEvent::DeathPhase { round }, } } } pub struct Game { world: World, } pub fn new_game(opts: GameOpts) -> Game { let mut world = World::new(content::library()); if let Some(decks) = opts.decks { for (player, deck) in decks.iter().enumerate() { for card in deck { world.create(card, player as Player, Zone::Deck); } } } Game { world } } impl Game { /// Put `card` at the right end of `player`'s board. pub fn summon(&mut self, card: &str, player: Player) -> Id { self.world.create(card, player, Zone::Board) } pub fn add_to_hand(&mut self, card: &str, player: Player) -> Id { self.world.create(card, player, Zone::Hand) } pub fn add_to_deck(&mut self, card: &str, player: Player) -> Id { self.world.create(card, player, Zone::Deck) } /// Play a card from hand. This is the Module 1 Expansion Drop: see /// course/01-handles/expansion/README.md. pub fn play(&mut self, _hand_id: Id) { todo!("Module 1 Expansion Drop") } pub fn zone_of(&self, id: Id) -> Zone { self.world.zone_of(id) } /// Board ids, left to right. pub fn board(&self, player: Player) -> Vec { self.world.zone(player, Zone::Board).to_vec() } pub fn hand(&self, player: Player) -> Vec { self.world.zone(player, Zone::Hand).to_vec() } pub fn order_of_play(&self, id: Id) -> u32 { self.world .order_of_play(id) .unwrap_or_else(|| panic!("entity {id} has not entered play")) } pub fn name_of(&self, id: Id) -> String { self.world.card_name(id).to_string() } // ---- Module 2 ---- pub fn hero(&self, player: Player) -> Id { self.world.hero(player) } /// Deal damage to one character, as one Sequence. pub fn deal_damage(&mut self, target: Id, amount: i32) { let targets = vec![target]; run_sequence(&mut self.world, Action::Damage { targets, amount }); } pub fn cast(&mut self, card: &str, player: Player, target: Option) { let card = card.to_string(); run_sequence( &mut self.world, Action::Cast { card, player, target, }, ); } pub fn health(&self, id: Id) -> i32 { self.world.health(id) } pub fn attack(&self, id: Id) -> i32 { self.world.attack(id) } pub fn armor(&self, player: Player) -> i32 { self.world.armor(player) } pub fn log(&self) -> Vec { self.world.log().iter().map(GameEvent::from).collect() } pub fn clear_log(&mut self) { self.world.clear_log(); } // ---- Module 3 ---- /// Mark `target` pending destroy, as one Sequence. It leaves play in the death step. pub fn destroy(&mut self, target: Id) { let targets = vec![target]; run_sequence(&mut self.world, Action::Destroy { targets }); } }