//! The naive engine for Module 0, The Rewrite Trap. //! //! It is written the "obvious" way on purpose, and it is wrong in ways worth //! studying. Read course/00-rewrite-trap/lesson.md before you change it. //! You throw this engine away at the end of Module 0. use std::cell::RefCell; use std::rc::Rc; /// A minion is shared by reference: the board and anyone holding it see the same one. pub type MinionRef = Rc>; /// What a card does, written as a plain function the engine calls directly. pub type Effect = fn(&mut Game, &MinionRef); pub struct Minion { pub name: String, pub attack: i32, pub health: i32, /// Called the moment this minion takes damage. pub on_damaged: Option, /// Called the moment this minion dies. pub deathrattle: Option, } #[derive(Default)] pub struct Game { /// The minions on the board, left to right. There is only one player. pub board: Vec, pub cards_drawn: u32, /// What happened, in order, as plain text. pub log: Vec, } impl Game { pub fn new() -> Self { Self::default() } /// Puts a minion on the right end of the board and returns it. pub fn summon(&mut self, card: &str) -> MinionRef { let minion = Rc::new(RefCell::new(make_minion(card))); self.board.push(Rc::clone(&minion)); minion } pub fn cast(&mut self, card: &str) { match card { // Deal 1 damage to ALL minions. "Whirlwind" => { for minion in self.board.clone() { self.deal_damage(&minion, 1); } } other => panic!("unknown spell: {other}"), } } /// Damage happens now. The damage trigger fires now. Death happens now. pub fn deal_damage(&mut self, target: &MinionRef, amount: i32) { let (name, on_damaged) = { let mut minion = target.borrow_mut(); minion.health -= amount; (minion.name.clone(), minion.on_damaged) }; self.log.push(format!("damaged {name} {amount}")); if let Some(effect) = on_damaged { self.log.push(format!("triggered {name}")); effect(self, target); } if target.borrow().health <= 0 { self.kill(target); } } /// Removes the minion and runs its Deathrattle straight away. fn kill(&mut self, target: &MinionRef) { self.board.retain(|minion| !Rc::ptr_eq(minion, target)); let (name, deathrattle) = { let minion = target.borrow(); (minion.name.clone(), minion.deathrattle) }; self.log.push(format!("died {name}")); if let Some(effect) = deathrattle { self.log.push(format!("deathrattle {name}")); effect(self, target); } } pub fn draw(&mut self) { self.cards_drawn += 1; self.log.push("drew a card".to_string()); } pub fn board_names(&self) -> Vec { self.board.iter().map(|minion| minion.borrow().name.clone()).collect() } } /// Every minion the game knows. A new card means a new branch here. fn make_minion(card: &str) -> Minion { let stats = |attack, health| Minion { name: card.to_string(), attack, health, on_damaged: None, deathrattle: None, }; match card { // Whenever this minion takes damage, draw a card. "Acolyte of Pain" => Minion { on_damaged: Some(|game, _| game.draw()), ..stats(1, 4) }, // Deathrattle: Draw a card. "Loot Hoarder" => Minion { deathrattle: Some(|game, _| game.draw()), ..stats(2, 1) }, other => panic!("unknown minion: {other}"), } }