diff --git a/src/lib.rs b/src/lib.rs index 417e3ba..8cd94a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,507 +1,510 @@ -use rand::seq::SliceRandom; - -use std::fmt; - -use Action::*; -use Card::*; - -pub type CoupResult = Result; - -#[repr(u8)] -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub enum Card { - Duke, - Assassin, - Captain, - Ambassador, - Contessa, -} - -impl Card { - pub fn allows_action(self, action: Action) -> bool { - matches!((self, action), - (_, Income) | - (_, ForeignAid) | - (Duke, Tax) | - (Assassin, Assassinate) | - (Ambassador, Exchange) | - (Captain, Steal)) - } - pub fn blocks_action(self, action: Action) -> bool { - matches!((self, action), - (Duke, Income) | - (Captain, Steal) | - (Ambassador, Steal) | - (Contessa, Assassinate)) - } -} - -trait Stack { - fn draw(&mut self, player: &mut Player) -> CoupResult<()>; - fn draw_first(&mut self, card: Card) -> bool; - fn shuffle(&mut self); -} - -impl Stack for Vec { - fn draw(&mut self, player: &mut Player) -> CoupResult<()> { - match self.pop() { - Some(card) => { - player.cards.push(card); - Ok(()) - }, - None => Err("Tried to draw from an empty deck!"), - } - } - - fn draw_first(&mut self, card: Card) -> bool { - match self.iter().position(|c| *c == card) { - Some(i) => { - self.remove(i); - true - }, - None => false, - } - } - - fn shuffle(&mut self) { - let deck = self.as_mut_slice(); - let mut rng = rand::thread_rng(); - deck.shuffle(&mut rng); - } -} - -impl fmt::Display for Card { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let name = match self { - Duke => "Duke", - Assassin => "Assassin", - Captain => "Captain", - Ambassador => "Ambassador", - Contessa => "Contessa", - }; - write!(f, "{}", name) - } -} - -#[repr(u8)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum Action { - Income, - ForeignAid, - Coup, - Tax, - Assassinate, - Exchange, - Steal, -} - -impl Action { - pub fn is_targeted(self) -> bool { - matches!(self, Coup | Steal | Assassinate) - } - - pub fn challenger_mode(self) -> ResMode { - match self { - Income | ForeignAid | Coup => ResMode::None, - Assassinate | Steal | Tax | Exchange => ResMode::Anyone, - } - } - - pub fn blocker_mode(self) -> ResMode { - match self { - Income | Tax | Exchange | Coup => ResMode::None, - Assassinate | Steal => ResMode::Target, - ForeignAid => ResMode::Anyone, - } - } - - pub fn coin_cost(self) -> u8 { - match self { - Assassinate => 3, - Coup => 7, - _ => 0, - } - } -} - -impl fmt::Display for Action { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let name = match self { - Income => "Income", - ForeignAid => "Foreign Aid", - Coup => "Coup", - Tax => "Tax", - Assassinate => "Assassinate", - Exchange => "Exchange", - Steal => "Steal", - }; - write!(f, "{}", name) - } -} - -#[repr(u8)] -#[derive(Clone, Copy)] -pub enum ResMode { - None, - Target, - Anyone, -} - -#[derive(Clone)] -pub struct Player { - pub coins: u8, - pub cards: Vec, -} - -impl Player { - pub fn is_alive(&self) -> bool { - !self.cards.is_empty() - } - pub fn lose(&mut self, card: Card, deck: &mut Vec) { - self.cards.draw_first(card); - deck.push(card); - } - pub fn holds(&self, card: Card) -> bool { - self.cards.iter().find(|&c| *c == card).is_some() - } -} - -impl Default for Player { - fn default() -> Self { - Player { - cards: Vec::new(), - coins: 2, - } - } -} - -#[derive(Clone)] -pub struct Game { - pub players: Vec, - pub deck: Vec, - pub discard: Vec, - pub turn: usize, -} - -macro_rules! thrice { - ($($e:expr),*) => { - vec![$($e, $e, $e),*] - } -} - -impl Game { - pub fn new(num_players: usize) -> Self { - let mut deck = thrice![Duke, Assassin, Captain, Ambassador, Contessa]; - deck.shuffle(); - let mut players = Vec::new(); - players.resize_with(num_players, Player::default); - for player in &mut players { - deck.draw(player).unwrap(); - deck.draw(player).unwrap(); - } - Game { - deck, - players, - discard: Vec::new(), - turn: 0, - } - } - pub fn is_game_over(&self) -> bool { - self.players.iter().filter(|p| p.is_alive()).count() == 1 - } - - fn turn_iterator(&self) -> impl Iterator + '_ { - let players_turn_order = std::iter::successors(Some(self.turn), |p| { - let next = (p + 1) % self.players.len(); - if next == self.turn { - None - } else { - Some(next) - } - }); - players_turn_order.skip(1).filter(|p| self.players[*p].is_alive()) - } - - fn advance(&mut self) { - let next = self.turn_iterator().next(); - if let Some(next) = next { - self.turn = next; - } - } - - pub fn block_challenge(&mut self, block_challenge: BlockChallenge, agents: &[&dyn Agent]) -> CoupResult { - if agents[self.turn].should_block_challenge(&block_challenge) { - if self.players[block_challenge.blocker].holds(block_challenge.block_card) { - // Player challenged incorrectly, loses influence and turn is forfeit - let current_player = &mut self.players[self.turn]; - let card = agents[self.turn] - .choose_lost_influence(¤t_player.cards); - current_player.lose(card, &mut self.discard); - Ok(Phase::Done) - } else { - // Player challenged correctly, blocker loses a card - let blocking_player = &mut self.players[block_challenge.blocker]; - let card = agents[block_challenge.blocker] - .choose_lost_influence(&blocking_player.cards); - blocking_player.lose(card, &mut self.discard); - - // Game continues - Ok(Phase::Resolution(Resolution { - action: block_challenge.action, - target: block_challenge.target, - })) - } - } else { - // Player chose not to challenge the block, turn is forfeit - self.advance(); - Ok(Phase::Done) - } - } - - pub fn resolution(&mut self, resolution: Resolution, agents: &[&dyn Agent]) -> CoupResult { - let current_player = &mut self.players.get_mut(self.turn).unwrap(); - let current_agent = agents[self.turn]; - match resolution.action { - Income => current_player.coins += 1, - ForeignAid => current_player.coins += 2, - Coup | Assassinate => match resolution.target { - Some(target) => { - let target_player = &self.players[target]; - if target_player.is_alive() { // Target may have died from challenge - let card = agents[target].choose_lost_influence(&target_player.cards); - self.players[target].lose(card, &mut self.discard); - } - } - _ => return Err("Coup/Assassinate resolution has no target"), - }, - Tax => current_player.coins += 3, - Exchange => { - let drawn = vec![self.deck.pop().unwrap(), self.deck.pop().unwrap()]; - let hand = current_player.cards.clone(); - let mut choices = [drawn, hand].concat(); - let discarded = current_agent.exchange(&choices); - for card in discarded { - if !choices.draw_first(card) { - return Err("Exchanged a card that was not in choices"); - } else { - self.deck.push(card); - } - } - current_player.cards = choices; - self.deck.shuffle(); - } - Steal => match resolution.target { - Some(target) => { - let val = self.players[target].coins.min(2); - self.players[self.turn].coins += val; - self.players[target].coins -= val; - - } - _ => return Err("Steal resolution has no target"), - } - } - self.advance(); - Ok(Phase::Done) - } -} - -#[derive(PartialEq, Debug)] -pub enum Phase { - Action(Action), - //ActionChallenge(ActionChallenge), - //Block(Block), - BlockChallenge(BlockChallenge), - Resolution(Resolution), - Done, -} - -#[derive(PartialEq, Debug)] -pub struct BlockChallenge { - blocker: usize, - block_card: Card, - action: Action, - target: Option, -} - -#[derive(PartialEq, Debug)] -pub struct Resolution { - action: Action, - target: Option, -} - -pub trait Agent : fmt::Debug { - fn should_block_challenge(&self, block_challenge: &BlockChallenge) -> bool; - fn exchange(&self, cards: &[Card]) -> [Card; 2]; - fn choose_lost_influence(&self, cards: &[Card]) -> Card; -} - -#[cfg(test)] -mod test { - use super::*; - - macro_rules! make_dummy_agent { - ($sbc: expr, $e: expr, $cli: expr) => {{ - #[derive(Debug)] - struct DummyAgent; - impl Agent for DummyAgent { - #[allow(unused)] - fn should_block_challenge(&self, block_challenge: &BlockChallenge) -> bool { - $sbc - } - #[allow(unused)] - fn exchange(&self, cards: &[Card]) -> [Card; 2] { - $e - } - #[allow(unused)] - fn choose_lost_influence(&self, cards: &[Card]) -> Card { - $cli - } - } - DummyAgent - }} - } - - #[test] - fn test_block_challenge() { - let challenge_agent = make_dummy_agent!(true, unimplemented!(), Assassin); - let non_challenge_agent = make_dummy_agent!(false, unimplemented!(), Assassin); - let block_agent = make_dummy_agent!(unimplemented!(), unimplemented!(), Contessa); - let deck = vec![]; - let discard = vec![]; - let players = vec![ - Player { coins: 2, cards: vec![Assassin] }, - Player { coins: 2, cards: vec![Contessa] }, - ]; - let game = Game { - deck, - discard, - players, - turn: 0 - }; - - // Test non-challenge - { - let mut game = game.clone(); - assert_eq!( - game.block_challenge( BlockChallenge { - blocker: 1, - block_card: Contessa, - action: Assassinate, - target: Some(1), - }, &[&non_challenge_agent, &block_agent]), - Ok(Phase::Done) - ); - assert!(game.discard.is_empty()); - } - - // Test failed challenge - { - let mut game = game.clone(); - assert_eq!( - game.block_challenge( BlockChallenge { - blocker: 1, - block_card: Contessa, - action: Assassinate, - target: Some(1), - }, &[&challenge_agent, &block_agent]), - Ok(Phase::Done) - ); - assert!(!game.discard.is_empty()); - } - - // Test successful challenge - { - let mut game = game.clone(); - assert_eq!( - game.block_challenge( BlockChallenge { - blocker: 1, - block_card: Duke, - action: ForeignAid, - target: None, - }, &[&challenge_agent, &block_agent]), - Ok(Phase::Resolution(Resolution { - action: ForeignAid, - target: None, - })) - ); - assert!(!game.discard.is_empty()); - } - - } - - - - - #[test] - fn test_resolution() { - let dummy_agent = make_dummy_agent!(unimplemented!(), [Contessa, Duke], Captain); - let deck = vec![Contessa, Contessa]; - let discard = vec![]; - let players = vec![ - Player { coins: 2, cards: vec![Duke, Assassin] }, - Player { coins: 1, cards: vec![Captain] }, - ]; - let game = Game { - deck, - discard, - players, - turn: 0, - }; - - // Test income - { - let mut game = game.clone(); - game.resolution(Resolution { - action: Income, - target: None, - }, &[&dummy_agent, &dummy_agent]).unwrap(); - assert_eq!(game.players[0].coins, 3); - } - - // Test foreign aid - { - let mut game = game.clone(); - game.resolution(Resolution { - action: ForeignAid, - target: None, - }, &[&dummy_agent, &dummy_agent]).unwrap(); - assert_eq!(game.players[0].coins, 4); - } - - // Test coup / assassinate - { - let mut game = game.clone(); - game.resolution(Resolution { - action: Coup, - target: Some(1), - }, &[&dummy_agent, &dummy_agent]).unwrap(); - assert!(game.players[1].cards.is_empty()); - assert_eq!(game.discard, vec![Captain]); - } - - // Test steal - { - let mut game = game.clone(); - game.resolution(Resolution { - action: Steal, - target: Some(1), - }, &[&dummy_agent, &dummy_agent]).unwrap(); - assert_eq!(game.players[0].coins, 3); - assert_eq!(game.players[1].coins, 0); - } - - // Test exchange - { - let mut game = game.clone(); - game.resolution(Resolution { - action: Exchange, - target: Some(1), - }, &[&dummy_agent, &dummy_agent]).unwrap(); - game.players[0].cards.sort(); - assert_eq!(game.players[0].cards, vec![Assassin, Contessa]); - game.deck.sort(); - assert_eq!(game.deck, vec![Duke, Contessa]); - assert!(game.discard.is_empty()); - } - } -} +use rand::seq::SliceRandom; + +use std::fmt; + +use Action::*; +use Card::*; + +pub type CoupResult = Result; + +#[repr(u8)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub enum Card { + Duke, + Assassin, + Captain, + Ambassador, + Contessa, +} + +impl Card { + pub fn allows_action(self, action: Action) -> bool { + matches!((self, action), + (_, Income) | + (_, ForeignAid) | + (Duke, Tax) | + (Assassin, Assassinate) | + (Ambassador, Exchange) | + (Captain, Steal)) + } + pub fn blocks_action(self, action: Action) -> bool { + matches!((self, action), + (Duke, Income) | + (Captain, Steal) | + (Ambassador, Steal) | + (Contessa, Assassinate)) + } +} + +trait Stack { + fn draw(&mut self, player: &mut Player) -> CoupResult<()>; + fn draw_first(&mut self, card: Card) -> bool; + fn shuffle(&mut self); +} + +impl Stack for Vec { + fn draw(&mut self, player: &mut Player) -> CoupResult<()> { + match self.pop() { + Some(card) => { + player.cards.push(card); + Ok(()) + }, + None => Err("Tried to draw from an empty deck!"), + } + } + + fn draw_first(&mut self, card: Card) -> bool { + match self.iter().position(|c| *c == card) { + Some(i) => { + self.remove(i); + true + }, + None => false, + } + } + + fn shuffle(&mut self) { + let deck = self.as_mut_slice(); + let mut rng = rand::thread_rng(); + deck.shuffle(&mut rng); + } +} + +impl fmt::Display for Card { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + Duke => "Duke", + Assassin => "Assassin", + Captain => "Captain", + Ambassador => "Ambassador", + Contessa => "Contessa", + }; + write!(f, "{}", name) + } +} + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum Action { + Income, + ForeignAid, + Coup, + Tax, + Assassinate, + Exchange, + Steal, +} + +impl Action { + pub fn is_targeted(self) -> bool { + matches!(self, Coup | Steal | Assassinate) + } + + pub fn challenger_mode(self) -> ResMode { + match self { + Income | ForeignAid | Coup => ResMode::None, + Assassinate | Steal | Tax | Exchange => ResMode::Anyone, + } + } + + pub fn blocker_mode(self) -> ResMode { + match self { + Income | Tax | Exchange | Coup => ResMode::None, + Assassinate | Steal => ResMode::Target, + ForeignAid => ResMode::Anyone, + } + } + + pub fn coin_cost(self) -> u8 { + match self { + Assassinate => 3, + Coup => 7, + _ => 0, + } + } +} + +impl fmt::Display for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + Income => "Income", + ForeignAid => "Foreign Aid", + Coup => "Coup", + Tax => "Tax", + Assassinate => "Assassinate", + Exchange => "Exchange", + Steal => "Steal", + }; + write!(f, "{}", name) + } +} + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum ResMode { + None, + Target, + Anyone, +} + +#[derive(Clone)] +pub struct Player { + pub coins: u8, + pub cards: Vec, +} + +impl Player { + pub fn is_alive(&self) -> bool { + !self.cards.is_empty() + } + pub fn lose(&mut self, card: Card, deck: &mut Vec) { + self.cards.draw_first(card); + deck.push(card); + } + pub fn holds(&self, card: Card) -> bool { + self.cards.iter().find(|&c| *c == card).is_some() + } +} + +impl Default for Player { + fn default() -> Self { + Player { + cards: Vec::new(), + coins: 2, + } + } +} + +#[derive(Clone)] +pub struct Game { + pub players: Vec, + pub deck: Vec, + pub discard: Vec, + pub turn: usize, +} + +macro_rules! thrice { + ($($e:expr),*) => { + vec![$($e, $e, $e),*] + } +} + +impl Game { + pub fn new(num_players: usize) -> Self { + let mut deck = thrice![Duke, Assassin, Captain, Ambassador, Contessa]; + deck.shuffle(); + let mut players = Vec::new(); + players.resize_with(num_players, Player::default); + for player in &mut players { + deck.draw(player).unwrap(); + deck.draw(player).unwrap(); + } + Game { + deck, + players, + discard: Vec::new(), + turn: 0, + } + } + pub fn is_game_over(&self) -> bool { + self.players.iter().filter(|p| p.is_alive()).count() == 1 + } + + fn turn_iterator(&self) -> impl Iterator + '_ { + let players_turn_order = std::iter::successors(Some(self.turn), |p| { + let next = (p + 1) % self.players.len(); + if next == self.turn { + None + } else { + Some(next) + } + }); + players_turn_order.skip(1).filter(|p| self.players[*p].is_alive()) + } + + fn advance(&mut self) { + let next = self.turn_iterator().next(); + if let Some(next) = next { + self.turn = next; + } + } + + fn player_lose_influence(&mut self, id: usize, agents: &[&dyn Agent]) { + let player = &mut self.players[id]; + let card = agents[id] + .choose_lost_influence(&player.cards); + player.lose(card, &mut self.discard); + } + + + pub fn block_challenge(&mut self, block_challenge: BlockChallenge, agents: &[&dyn Agent]) -> CoupResult { + if agents[self.turn].should_block_challenge(&block_challenge) { + if self.players[block_challenge.blocker].holds(block_challenge.block_card) { + // Player challenged incorrectly, loses influence and turn is forfeit + self.player_lose_influence(self.turn, agents); + Ok(Phase::Done) + } else { + // Player challenged correctly, blocker loses a card + self.player_lose_influence(block_challenge.blocker, agents); + // Game continues + Ok(Phase::Resolution(Resolution { + action: block_challenge.action, + target: block_challenge.target, + })) + } + } else { + // Player chose not to challenge the block, turn is forfeit + self.advance(); + Ok(Phase::Done) + } + } + + pub fn resolution(&mut self, resolution: Resolution, agents: &[&dyn Agent]) -> CoupResult { + let current_player = &mut self.players.get_mut(self.turn).unwrap(); + let current_agent = agents[self.turn]; + match resolution.action { + Income => current_player.coins += 1, + ForeignAid => current_player.coins += 2, + Coup | Assassinate => match resolution.target { + Some(target) => { + // Target may have died from challenge + let target_alive = self.players[target].is_alive(); + if target_alive { + self.player_lose_influence(target, agents); + } + } + _ => return Err("Coup/Assassinate resolution has no target"), + }, + Tax => current_player.coins += 3, + Exchange => { + let drawn = vec![self.deck.pop().unwrap(), self.deck.pop().unwrap()]; + let hand = current_player.cards.clone(); + let mut choices = [drawn, hand].concat(); + let discarded = current_agent.exchange(&choices); + for card in discarded { + if !choices.draw_first(card) { + return Err("Exchanged a card that was not in choices"); + } else { + self.deck.push(card); + } + } + current_player.cards = choices; + self.deck.shuffle(); + } + Steal => match resolution.target { + Some(target) => { + let val = self.players[target].coins.min(2); + self.players[self.turn].coins += val; + self.players[target].coins -= val; + + } + _ => return Err("Steal resolution has no target"), + } + } + self.advance(); + Ok(Phase::Done) + } +} + +#[derive(PartialEq, Debug)] +pub enum Phase { + Action(Action), + //ActionChallenge(ActionChallenge), + Block(Block), + BlockChallenge(BlockChallenge), + Resolution(Resolution), + Done, +} + +#[derive(PartialEq, Debug)] +pub struct Block { + +} + +#[derive(PartialEq, Debug)] +pub struct BlockChallenge { + blocker: usize, + block_card: Card, + action: Action, + target: Option, +} + +#[derive(PartialEq, Debug)] +pub struct Resolution { + action: Action, + target: Option, +} + +pub trait Agent : fmt::Debug { + fn should_block_challenge(&self, block_challenge: &BlockChallenge) -> bool; + fn exchange(&self, cards: &[Card]) -> [Card; 2]; + fn choose_lost_influence(&self, cards: &[Card]) -> Card; +} + +#[cfg(test)] +mod test { + use super::*; + + macro_rules! make_dummy_agent { + ($sbc: expr, $e: expr, $cli: expr) => {{ + #[derive(Debug)] + struct DummyAgent; + impl Agent for DummyAgent { + #[allow(unused)] + fn should_block_challenge(&self, block_challenge: &BlockChallenge) -> bool { + $sbc + } + #[allow(unused)] + fn exchange(&self, cards: &[Card]) -> [Card; 2] { + $e + } + #[allow(unused)] + fn choose_lost_influence(&self, cards: &[Card]) -> Card { + $cli + } + } + DummyAgent + }} + } + + #[test] + fn test_block_challenge() { + let challenge_agent = make_dummy_agent!(true, unimplemented!(), Assassin); + let non_challenge_agent = make_dummy_agent!(false, unimplemented!(), Assassin); + let block_agent = make_dummy_agent!(unimplemented!(), unimplemented!(), Contessa); + let deck = vec![]; + let discard = vec![]; + let players = vec![ + Player { coins: 2, cards: vec![Assassin] }, + Player { coins: 2, cards: vec![Contessa] }, + ]; + let game = Game { + deck, + discard, + players, + turn: 0 + }; + + // Test non-challenge + { + let mut game = game.clone(); + assert_eq!( + game.block_challenge( BlockChallenge { + blocker: 1, + block_card: Contessa, + action: Assassinate, + target: Some(1), + }, &[&non_challenge_agent, &block_agent]), + Ok(Phase::Done) + ); + assert!(game.discard.is_empty()); + } + + // Test failed challenge + { + let mut game = game.clone(); + assert_eq!( + game.block_challenge( BlockChallenge { + blocker: 1, + block_card: Contessa, + action: Assassinate, + target: Some(1), + }, &[&challenge_agent, &block_agent]), + Ok(Phase::Done) + ); + assert!(!game.discard.is_empty()); + } + + // Test successful challenge + { + let mut game = game.clone(); + assert_eq!( + game.block_challenge( BlockChallenge { + blocker: 1, + block_card: Duke, + action: ForeignAid, + target: None, + }, &[&challenge_agent, &block_agent]), + Ok(Phase::Resolution(Resolution { + action: ForeignAid, + target: None, + })) + ); + assert!(!game.discard.is_empty()); + } + + } + + #[test] + fn test_resolution() { + let dummy_agent = make_dummy_agent!(unimplemented!(), [Contessa, Duke], Captain); + let deck = vec![Contessa, Contessa]; + let discard = vec![]; + let players = vec![ + Player { coins: 2, cards: vec![Duke, Assassin] }, + Player { coins: 1, cards: vec![Captain] }, + ]; + let game = Game { + deck, + discard, + players, + turn: 0, + }; + + // Test income + { + let mut game = game.clone(); + game.resolution(Resolution { + action: Income, + target: None, + }, &[&dummy_agent, &dummy_agent]).unwrap(); + assert_eq!(game.players[0].coins, 3); + } + + // Test foreign aid + { + let mut game = game.clone(); + game.resolution(Resolution { + action: ForeignAid, + target: None, + }, &[&dummy_agent, &dummy_agent]).unwrap(); + assert_eq!(game.players[0].coins, 4); + } + + // Test coup / assassinate + { + let mut game = game.clone(); + game.resolution(Resolution { + action: Coup, + target: Some(1), + }, &[&dummy_agent, &dummy_agent]).unwrap(); + assert!(game.players[1].cards.is_empty()); + assert_eq!(game.discard, vec![Captain]); + } + + // Test steal + { + let mut game = game.clone(); + game.resolution(Resolution { + action: Steal, + target: Some(1), + }, &[&dummy_agent, &dummy_agent]).unwrap(); + assert_eq!(game.players[0].coins, 3); + assert_eq!(game.players[1].coins, 0); + } + + // Test exchange + { + let mut game = game.clone(); + game.resolution(Resolution { + action: Exchange, + target: Some(1), + }, &[&dummy_agent, &dummy_agent]).unwrap(); + game.players[0].cards.sort(); + assert_eq!(game.players[0].cards, vec![Assassin, Contessa]); + game.deck.sort(); + assert_eq!(game.deck, vec![Duke, Contessa]); + assert!(game.discard.is_empty()); + } + } +}