//! Entities, zones, order of play, stats and the event log: the game's state. //! The rules for resolving actions live in `queue.rs`. use super::card::{Aura, Card, CardDef, CardLibrary}; use super::event::Event; /// A handle to an entity. Ids are never reused and stay valid when the entity changes zone. pub type EntityId = u32; pub type PlayerId = u8; /// Starting Health of each hero. pub const HERO_HEALTH: i32 = 30; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Zone { Deck, Hand, Board, Graveyard, } #[derive(Debug)] struct Entity { card: String, owner: PlayerId, zone: Zone, /// Stamped each time the entity enters the board; `None` until it first does. order_of_play: Option, attack: i32, /// Current Health. At 0 or below the entity is mortally wounded: still in play until the /// Death Creation Step. health: i32, /// Hit by a destroy effect: still in play until the Death Creation Step. pending_destroy: bool, } /// An aura in force: which player's minion has it. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ActiveAura { pub owner: PlayerId, pub aura: Aura, } /// One player's zones. Each is an ordered list of ids (the board is left to right). #[derive(Debug, Default)] struct Side { deck: Vec, hand: Vec, board: Vec, graveyard: Vec, hero: EntityId, armor: i32, } impl Side { fn zone(&self, zone: Zone) -> &Vec { match zone { Zone::Deck => &self.deck, Zone::Hand => &self.hand, Zone::Board => &self.board, Zone::Graveyard => &self.graveyard, } } fn zone_mut(&mut self, zone: Zone) -> &mut Vec { match zone { Zone::Deck => &mut self.deck, Zone::Hand => &mut self.hand, Zone::Board => &mut self.board, Zone::Graveyard => &mut self.graveyard, } } } /// Makes a player's choices: called with what is being chosen and the options, it /// returns one of the options. Hearthstone would ask the player or pick at random. pub type Chooser = Box String>; pub struct World { library: CardLibrary, chooser: Chooser, /// Indexed by `EntityId`. entities: Vec, sides: [Side; 2], next_order_of_play: u32, log: Vec, /// The auras in force as of the last Aura Update. Changes only there (see `aura.rs`). auras: Vec, } impl World { /// A new game: both heroes are in play from the start, so they are the oldest entities. pub fn new(library: CardLibrary) -> World { let mut world = World { library, // Until a chooser is set, take the first option. chooser: Box::new(|_, options| options[0].clone()), entities: Vec::new(), sides: Default::default(), next_order_of_play: 0, log: Vec::new(), auras: Vec::new(), }; for owner in 0..2 { let id = world.entities.len() as EntityId; world.next_order_of_play += 1; world.entities.push(Entity { card: "Hero".to_string(), owner, zone: Zone::Board, order_of_play: Some(world.next_order_of_play), attack: 0, health: HERO_HEALTH, pending_destroy: false, }); // In play, but not on the board list: the board holds minions only. world.sides[owner as usize].hero = id; } world } /// Create an entity of `card` for `owner` at the end of `zone` (the right end for the board). pub fn create(&mut self, card: &str, owner: PlayerId, zone: Zone) -> EntityId { self.create_at(card, owner, zone, None) } /// Create an entity of `card` for `owner` in `zone` at index `at` (clamped to the /// zone's length), or at the end if `at` is `None`. pub fn create_at( &mut self, card: &str, owner: PlayerId, zone: Zone, at: Option, ) -> EntityId { assert!(owner < 2, "no player {owner}"); let def = &self.library.get(card).def; let entity = Entity { card: def.name.clone(), owner, zone, order_of_play: None, attack: def.attack, health: def.health, pending_destroy: false, }; let id = self.entities.len() as EntityId; self.entities.push(entity); self.enter(id, zone, at); id } /// Move an entity to the end of `zone` on its owner's side, keeping its id. pub fn move_to(&mut self, id: EntityId, zone: Zone) { let (owner, from) = (self.entity(id).owner, self.entity(id).zone); self.sides[owner as usize] .zone_mut(from) .retain(|&e| e != id); self.enter(id, zone, None); } /// The single place an entity arrives in a zone. Entering the board starts a new order of play. fn enter(&mut self, id: EntityId, zone: Zone, at: Option) { let owner = self.entity(id).owner; let list = self.sides[owner as usize].zone_mut(zone); list.insert(at.map_or(list.len(), |i| i.min(list.len())), id); let entity = &mut self.entities[id as usize]; entity.zone = zone; if zone == Zone::Board { self.next_order_of_play += 1; entity.order_of_play = Some(self.next_order_of_play); self.record(Event::Summoned { id }); } } fn entity(&self, id: EntityId) -> &Entity { self.entities .get(id as usize) .unwrap_or_else(|| panic!("unknown entity id {id}")) } fn entity_mut(&mut self, id: EntityId) -> &mut Entity { self.entities .get_mut(id as usize) .unwrap_or_else(|| panic!("unknown entity id {id}")) } // ---- Queries ------------------------------------------------------- pub fn card(&self, name: &str) -> &Card { self.library.get(name) } pub fn has_card(&self, name: &str) -> bool { self.library.contains(name) } /// The cards an Assemble may offer, sorted by name. pub fn pool(&self) -> Vec<&Card> { self.library.pool() } pub fn zone_of(&self, id: EntityId) -> Zone { self.entity(id).zone } pub fn card_name(&self, id: EntityId) -> &str { &self.entity(id).card } pub fn owner_of(&self, id: EntityId) -> PlayerId { self.entity(id).owner } /// When the entity last entered the board (larger is later); `None` if it never has. pub fn order_of_play(&self, id: EntityId) -> Option { self.entity(id).order_of_play } /// The ids in one player's zone, in zone order (left to right for the board). pub fn zone(&self, owner: PlayerId, zone: Zone) -> &[EntityId] { assert!(owner < 2, "no player {owner}"); self.sides[owner as usize].zone(zone) } pub fn hero(&self, owner: PlayerId) -> EntityId { assert!(owner < 2, "no player {owner}"); self.sides[owner as usize].hero } pub fn is_hero(&self, id: EntityId) -> bool { self.sides.iter().any(|s| s.hero == id) } /// Every minion on either board, oldest first (order of play). pub fn minions_in_play(&self) -> Vec { let mut ids: Vec = self.sides.iter().flat_map(|s| s.board.clone()).collect(); ids.sort_by_key(|&id| self.order_of_play(id)); ids } pub fn attack(&self, id: EntityId) -> i32 { self.entity(id).attack } pub fn health(&self, id: EntityId) -> i32 { self.entity(id).health } /// The cost printed on the entity's card. pub fn cost(&self, id: EntityId) -> i32 { self.card(self.card_name(id)).def.cost } /// The minions in a player's hand, in hand order. pub fn minions_in_hand(&self, owner: PlayerId) -> Vec { self.zone(owner, Zone::Hand) .iter() .copied() .filter(|&id| !self.card(self.card_name(id)).is_spell()) .collect() } /// Mortally wounded (0 or less Health) or pending destroy: it leaves play at the next /// Death Creation Step. pub fn is_doomed(&self, id: EntityId) -> bool { let entity = self.entity(id); entity.health <= 0 || entity.pending_destroy } /// Index of a minion on its owner's board, left to right; `None` if it is not on the board. pub fn board_index(&self, id: EntityId) -> Option { self.zone(self.owner_of(id), Zone::Board) .iter() .position(|&e| e == id) } pub fn armor(&self, owner: PlayerId) -> i32 { self.sides[owner as usize].armor } pub fn active_auras(&self) -> &[ActiveAura] { &self.auras } pub fn log(&self) -> &[Event] { &self.log } // ---- Changes: each one records its event ---------------------------- pub fn record(&mut self, event: Event) { self.log.push(event); } /// Add a card to the library, or replace the card with the same name. pub fn register(&mut self, def: CardDef) { self.library.register(def); } /// Add a card made by Assemble (see `assemble.rs`). pub fn register_assembled(&mut self, def: CardDef) { self.library.register_assembled(def); } pub fn set_chooser(&mut self, chooser: Chooser) { self.chooser = chooser; } /// Ask the chooser to pick one of `options` (not empty). Panics if it answers /// something that isn't an option. pub fn choose(&mut self, kind: &str, options: &[String]) -> String { let answer = (self.chooser)(kind, options); assert!( options.contains(&answer), "chooser answered {answer:?} for {kind:?}; the options were {options:?}" ); answer } /// Only an Aura Update calls this. pub fn set_active_auras(&mut self, auras: Vec) { self.auras = auras; } pub fn clear_log(&mut self) { self.log.clear(); } pub fn damage(&mut self, target: EntityId, amount: i32) { self.entity_mut(target).health -= amount; self.record(Event::Damaged { target, amount }); } /// Draw the top card of `player`'s deck (the first in the list). An empty deck /// records `Fatigue` and changes nothing else. pub fn draw(&mut self, player: PlayerId) { match self.zone(player, Zone::Deck).first() { Some(&id) => { self.move_to(id, Zone::Hand); self.record(Event::Drew { player, id }); } None => self.record(Event::Fatigue { player }), } } /// Mark for destruction. Nothing leaves play until the Death Creation Step. pub fn mark_destroyed(&mut self, id: EntityId) { self.entity_mut(id).pending_destroy = true; } /// Move a minion from play to its owner's graveyard, recording its Death Event. pub fn kill(&mut self, id: EntityId, position: usize) { self.entity_mut(id).pending_destroy = false; self.move_to(id, Zone::Graveyard); self.record(Event::Destroyed { id, position }); } pub fn gain_armor(&mut self, player: PlayerId, amount: i32) { self.sides[player as usize].armor += amount; self.record(Event::ArmorGained { player, amount }); } pub fn buff(&mut self, id: EntityId, attack: i32, health: i32) { let entity = self.entity_mut(id); entity.attack += attack; entity.health += health; let (attack, health) = (entity.attack, entity.health); self.record(Event::StatsChanged { id, attack, health }); } pub fn gain_attack(&mut self, id: EntityId, amount: i32) { let entity = self.entity_mut(id); entity.attack += amount; let (attack, health) = (entity.attack, entity.health); self.record(Event::StatsChanged { id, attack, health }); } }