diff --git a/Cargo.lock b/Cargo.lock index a70388e..56d1b65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "coup" version = "0.1.0" dependencies = [ + "ntest", "rand", ] @@ -32,12 +33,81 @@ version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" +[[package]] +name = "ntest" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c544e496c816f0a59645c0bb69097e453df203954ae2ed4b3ac4251fad69d44" +dependencies = [ + "ntest_proc_macro_helper", + "ntest_test_cases", + "ntest_timeout", +] + +[[package]] +name = "ntest_proc_macro_helper" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f52e34b414605b77efc95c3f0ecef01df0c324bcc7f68d9a9cb7a7552777e52" + +[[package]] +name = "ntest_test_cases" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a81eb400abc87063f829560bc5c5c835177703b83d1cd991960db0b2a00abe" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ntest_timeout" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10db009e117aca57cbfb70ac332348f9a89d09ff7204497c283c0f7a0c96323" +dependencies = [ + "ntest_proc_macro_helper", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ppv-lite86" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rand" version = "0.8.5" @@ -68,6 +138,58 @@ dependencies = [ "getrandom", ] +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" + +[[package]] +name = "syn" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 5384d8a..fc367a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rand = "*" \ No newline at end of file +rand = "*" + +[dev-dependencies] +ntest = "*" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 43a6eef..d43c8c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -247,44 +247,59 @@ impl Game { } } - fn player_lose_influence(&mut self, id: usize, agents: &[&dyn Agent]) -> CoupResult<()>{ - let card = agents[id].choose_lost_influence(self); + fn player_lose_influence(&mut self, id: usize, channels: &Channels) -> CoupResult<()> { + channels.send(id, Message::LoseInfluence); + let card = channels.recv_discard(id)?; let player = &self.players[id]; if player.holds(card) { self.players[id].lose(card, &mut self.discard); + channels.broadcast(Message::Discard(card)); Ok(()) } else { Err("Player discarded a card they don't hold") } } - pub fn action(&mut self, agents: &[&dyn Agent]) -> CoupResult { - let move_ = agents[self.turn].choose_move(self); + pub fn action(&mut self, channels: &Channels) -> CoupResult { + channels.broadcast(Message::Turn(self.turn)); + let move_ = channels.recv_move(self.turn)?; if move_.action.is_targeted() && move_.target.is_none() { Err("Targeted action with no target") } else if move_.action.coin_cost() > self.players[self.turn].coins { Err("Cannot afford action") } else { + channels.broadcast(Message::Move(move_)); Ok(Phase::ActionChallenge(move_)) } // TODO there're definately more cases, find and cover these } - pub fn action_challenge(&mut self, move_: Move, agents: &[&dyn Agent]) -> CoupResult { + pub fn action_challenge(&mut self, move_: Move, channels: &Channels) -> CoupResult { match move_.action.challenger_mode() { ResMode::None => Ok(Phase::Block(move_)), ResMode::Target => unreachable!(), ResMode::Anyone => { - let challenger = self.turn_iterator().find(|&id| agents[id].should_action_challenge(self, move_)); + let wait_timer = Timer::start(); + let challenger = 'outer: loop { + if wait_timer.is_timeout() { + break None + } + for id in self.turn_iterator() { + if let Some(()) = channels.try_recv_challenge(id) { + break 'outer Some(id) + } + } + }; + if let Some(challenger) = challenger{ let current_player_wins = self.players[self.turn].wins_challenge(move_.action); if current_player_wins { // Challenger loses influence - self.player_lose_influence(challenger, agents)?; + self.player_lose_influence(challenger, channels)?; Ok(Phase::Block(move_)) } else { // Player loses influence - self.player_lose_influence(self.turn, agents)?; + self.player_lose_influence(self.turn, channels)?; // Turn is forfeit self.advance(); Ok(Phase::Done) @@ -296,50 +311,82 @@ impl Game { } } - pub fn block(&mut self, move_: Move, agents: &[&dyn Agent]) -> CoupResult { + pub fn block(&mut self, move_: Move, channels: &Channels) -> CoupResult { match move_.action.blocker_mode() { ResMode::None => Ok(Phase::Resolution(move_)), - ResMode::Target => match agents[move_.target.unwrap()].choose_block_card(self, move_) { - Some(card) => { - if card.blocks_action(move_.action) { - Ok(Phase::BlockChallenge(move_, Block { - blocker: move_.target.unwrap(), - card, - })) - } else { - Err("Card does not block action") + ResMode::Target => { + let wait_timer = Timer::start(); + let card = loop { + if wait_timer.is_timeout() { + break None } - }, - None => Ok(Phase::Resolution(move_)), - } - ResMode::Anyone => { - for id in self.turn_iterator() { - if let Some(card) = agents[id].choose_block_card(self, move_) { + if let Some(card) = channels.try_recv_block(move_.target.unwrap()) { + break Some(card) + } + }; + + match card { + Some(card) => { if card.blocks_action(move_.action) { - return Ok(Phase::BlockChallenge(move_, Block { - blocker: id, + Ok(Phase::BlockChallenge(move_, Block { + blocker: move_.target.unwrap(), card, })) } else { - return Err("Card does not block action") + Err("Card does not block action") + } + }, + None => Ok(Phase::Resolution(move_)), + } + } + ResMode::Anyone => { + let wait_timer = Timer::start(); + let block = 'outer: loop { + if wait_timer.is_timeout() { + break None + } + for id in self.turn_iterator() { + if let Some(card) = channels.try_recv_block(id) { + break 'outer Some(Block { + blocker: id, + card, + }) } } + }; + + if let Some(block) = block { + if block.card.blocks_action(move_.action) { + Ok(Phase::BlockChallenge(move_, block)) + } else { + Err("Card does not block action") + } + } else { + Ok(Phase::Resolution(move_)) } - Ok(Phase::Resolution(move_)) } } } - pub fn block_challenge(&mut self, move_: Move, block: Block, agents: &[&dyn Agent]) -> CoupResult { - if agents[self.turn].should_block_challenge(self, move_, block) { + pub fn block_challenge(&mut self, move_: Move, block: Block, channels: &Channels) -> CoupResult { + let wait_timer = Timer::start(); + let is_challenge = loop { + if wait_timer.is_timeout() { + break false + } + if let Some(()) = channels.try_recv_challenge(self.turn) { + break true + } + }; + if is_challenge { if self.players[block.blocker].holds(block.card) { // Player challenged incorrectly, loses influence and turn is forfeit - self.player_lose_influence(self.turn, agents)?; + self.player_lose_influence(self.turn, channels)?; self.advance(); Ok(Phase::Done) } else { // Player challenged correctly, blocker loses a card - self.player_lose_influence(block.blocker, agents)?; + self.player_lose_influence(block.blocker, channels)?; // Game continues Ok(Phase::Resolution(move_)) } @@ -350,7 +397,7 @@ impl Game { } } - pub fn resolution(&mut self, move_: Move, agents: &[&dyn Agent]) -> CoupResult { + pub fn resolution(&mut self, move_: Move, channels: &Channels) -> CoupResult { self.players[self.turn].coins -= move_.action.coin_cost(); match move_.action { Income => self.players[self.turn].coins += 1, @@ -360,7 +407,7 @@ impl Game { // Target may have died from challenge let target_alive = self.players[target].is_alive(); if target_alive { - self.player_lose_influence(target, agents)?; + self.player_lose_influence(target, channels)?; } } _ => return Err("Coup/Assassinate resolution has no target"), @@ -370,7 +417,10 @@ impl Game { let drawn = vec![self.deck.pop().unwrap(), self.deck.pop().unwrap()]; let hand = self.players[self.turn].cards.clone(); let mut choices = [drawn, hand].concat(); - let discarded = agents[self.turn].exchange(self, &choices); + let mut discarded = [Ambassador; 2]; + for card in &mut discarded { + *card = channels.recv_discard(self.turn)?; + } for card in discarded { if !choices.draw_first(card) { return Err("Exchanged a card that was not in choices"); @@ -421,24 +471,88 @@ pub enum Phase { Done, } -/// An interface to a game to make strategic decisions. -pub trait Agent : fmt::Debug { - /// What move should the agent take? - fn choose_move(&self, game: &Game) -> Move; - /// Should the agent challenge the action? - fn should_action_challenge(&self, game: &Game, move_: Move) -> bool; - /// Which [card](Card) the agent wishes to use to block the current action. - /// If the agent does not wish to block, it should return [None] - fn choose_block_card(&self, game: &Game, move_: Move) -> Option; - /// Should the agent challenge the block? - fn should_block_challenge(&self, game: &Game, move_: Move, block: Block) -> bool; - /// The [Ambassador]'s exchange. - /// Given 3 or 4 [Cards](Card) the agent must return two cards to the deck. - fn exchange(&self, game: &Game, cards: &[Card]) -> [Card; 2]; - /// The player has lost influence, and must choose a [Card] from their hand - /// to discard. - fn choose_lost_influence(&self, game: &Game) -> Card; +use std::sync::mpsc; + + +#[derive(Default)] +pub struct Channels { + txs: Vec>, + rxs: Vec>, } +impl Channels { + pub fn add_channel(&mut self, tx: mpsc::Sender, rx: mpsc::Receiver) { + self.txs.push(tx); + self.rxs.push(rx); + } + pub fn send(&self, id: usize, msg: Message) { + self.txs[id].send(msg).unwrap(); + } + pub fn recv(&self, id: usize) -> Message { + self.rxs[id].recv().unwrap() + } + pub fn try_recv(&self, id: usize) -> Option { + self.rxs[id].try_recv().ok() + } + pub fn broadcast(&self, msg: Message) { + for id in 0..self.rxs.len() { + self.send(id, msg); + } + } + pub fn recv_move(&self, id: usize) -> CoupResult { + let msg = self.recv(id); + match msg { + Message::Move(move_) => Ok(move_), + _ => Err("Expected move"), + } + } + pub fn recv_discard(&self, id: usize) -> CoupResult { + let msg = self.recv(id); + match msg { + Message::Discard(card) => Ok(card), + _ => Err("Expected discard") + } + } + pub fn try_recv_challenge(&self, id: usize) -> Option<()> { + let msg = self.try_recv(id); + match msg { + Some(Message::Challenge) => Some(()), + _ => None, + } + } + pub fn try_recv_block(&self, id: usize) -> Option { + let msg = self.try_recv(id); + match msg { + Some(Message::Block(card)) => Some(card), + _ => None, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Message { + Turn(usize), + LoseInfluence, + Move(Move), + Challenge, + Block(Card), + Discard(Card), +} + +use std::time::{ Instant, Duration }; +const WAIT_TIME: Duration = Duration::from_secs(1); +struct Timer { + start: Instant, +} +impl Timer { + fn start() -> Self { + Timer { start: Instant::now() } + } + fn is_timeout(&self) -> bool { + Instant::now() > self.start + WAIT_TIME + } +} + + #[cfg(test)] mod test; diff --git a/src/main.rs b/src/main.rs index bc9d233..efcd2a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,22 @@ use coup::*; -mod cli; -use cli::HumanAgent; +// mod cli; +// use cli::HumanAgent; fn main() -> CoupResult<()> { - let mut game = Game::new(2); - let agents: &[&dyn Agent] = &[&HumanAgent::new(0), &HumanAgent::new(1)]; - while !game.is_game_over() { - let mut phase = game.action(agents)?; - while phase != Phase::Done { - phase = match phase { - Phase::ActionChallenge(move_) => game.action_challenge(move_, agents)?, - Phase::Block(move_) => game.block(move_, agents)?, - Phase::BlockChallenge(move_, block) => game.block_challenge(move_, block, agents)?, - Phase::Resolution(move_) => game.resolution(move_, agents)?, - Phase::Done => unreachable!(), - } - } - } + let mut _game = Game::new(2); + + // while !game.is_game_over() { + // let mut phase = game.action(agents)?; + // while phase != Phase::Done { + // phase = match phase { + // Phase::ActionChallenge(move_) => game.action_challenge(move_, agents)?, + // Phase::Block(move_) => game.block(move_, agents)?, + // Phase::BlockChallenge(move_, block) => game.block_challenge(move_, block, agents)?, + // Phase::Resolution(move_) => game.resolution(move_, agents)?, + // Phase::Done => unreachable!(), + // } + // } + // } Ok(()) } diff --git a/src/test.rs b/src/test.rs index f7ad68a..933131e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,32 +1,26 @@ use super::*; -#[derive(Debug)] -struct DummyAgent(Card, Option, bool); -impl Agent for DummyAgent { - fn choose_move(&self, _game: &Game) -> Move { - unimplemented!(); +use ntest::timeout; + +use std::sync::mpsc; + +fn make_channels(nplayers: usize) -> (Vec>, Vec>, Channels) { + let mut txs = Vec::new(); + let mut rxs = Vec::new(); + let mut channels = Channels::default(); + for _ in 0..nplayers { + let (ptx, grx) = mpsc::channel(); + let (gtx, prx) = mpsc::channel(); + txs.push(ptx); + rxs.push(prx); + channels.add_channel(gtx, grx); } - fn should_action_challenge(&self, _game: &Game, _move: Move) -> bool { - self.2 - } - fn choose_block_card(&self, _game: &Game, _move: Move) -> Option { - self.1 - } - fn should_block_challenge(&self, _game: &Game, _move: Move, _block: Block) -> bool { - self.2 - } - fn exchange(&self, _game: &Game, _cards: &[Card]) -> [Card; 2] { - [self.0, self.1.unwrap()] - } - fn choose_lost_influence(&self, _game: &Game) -> Card { - self.0 - } + (txs, rxs, channels) } #[test] +#[timeout(1100)] fn test_action_challenge() { - let challenge_agent = DummyAgent(Contessa, None, true); - let non_challenge_agent = DummyAgent(Duke, None, false); let deck = vec![]; let discard = vec![]; let players = vec![ @@ -44,52 +38,75 @@ fn test_action_challenge() { // Test non-challengable { let mut game = game.clone(); + let (_txs, _rxs, channels) = make_channels(3); + assert_eq!( + game.action_challenge(Move { + action: Assassinate, + target: Some(2), + }, &channels), + Ok(Phase::Block(Move { + action: Assassinate, + target: Some(2), + })) + ); + } + + // Test passed challenge + { + let mut game = game.clone(); + let (_txs, _rxs, channels) = make_channels(3); assert_eq!( game.action_challenge(Move { action: Income, target: None, - }, &[&challenge_agent, &challenge_agent, &challenge_agent]), + }, &channels), Ok(Phase::Block(Move { action: Income, target: None })) ); } + // Test failed challenge { let mut game = game.clone(); + let (txs, rxs, channels) = make_channels(3); + txs[2].send(Message::Challenge).unwrap(); + txs[2].send(Message::Discard(Ambassador)).unwrap(); assert_eq!( game.action_challenge(Move { action: Steal, target: Some(2), - }, &[&challenge_agent, &challenge_agent, &challenge_agent]), + }, &channels), Ok(Phase::Block(Move { action: Steal, target: Some(2) })) ); + assert_eq!(rxs[2].recv(), Ok(Message::LoseInfluence)); } // Test successful challenge { let mut game = game.clone(); + let (txs, rxs, channels) = make_channels(3); + txs[2].send(Message::Challenge).unwrap(); + txs[0].send(Message::Discard(Duke)).unwrap(); assert_eq!( game.action_challenge(Move { action: Assassinate, target: Some(2), - }, &[&non_challenge_agent, &challenge_agent, &challenge_agent]), + }, &channels), Ok(Phase::Done), ); + assert_eq!(rxs[0].recv(), Ok(Message::LoseInfluence)); } } #[test] +#[timeout(2100)] fn test_block() { - let non_blocking_agent = DummyAgent(Duke, None, false); - let ambassador_block_agent = DummyAgent(Ambassador, Some(Ambassador), false); - let duke_block_agent = DummyAgent(Duke, Some(Duke), false); - let deck = vec![]; let discard = vec![]; let players = vec![ @@ -107,12 +124,13 @@ fn test_block() { // Test unblockable { + let (_txs, _rxs, channels) = make_channels(3); let mut game = game.clone(); assert_eq!( game.block(Move { action: Income, target: None, - }, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]), + }, &channels), Ok(Phase::Resolution(Move { action: Income, target: None @@ -123,11 +141,13 @@ fn test_block() { // Test target blocked { let mut game = game.clone(); + let (txs, _rxs, channels) = make_channels(3); + txs[2].send(Message::Block(Ambassador)).unwrap(); assert_eq!( game.block(Move { action: Steal, target: Some(2), - }, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]), + }, &channels), Ok(Phase::BlockChallenge( Move { action: Steal, @@ -141,14 +161,15 @@ fn test_block() { ); } - // Test target non-blocked + // Test target passed block { let mut game = game.clone(); + let (_txs, _rxs, channels) = make_channels(3); assert_eq!( game.block(Move { action: Steal, target: Some(2), - }, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent]), + }, &channels), Ok(Phase::Resolution(Move { action: Steal, target: Some(2), @@ -159,11 +180,13 @@ fn test_block() { // Test anyone blocked { let mut game = game.clone(); + let (txs, _rxs, channels) = make_channels(3); + txs[2].send(Message::Block(Duke)).unwrap(); assert_eq!( game.block(Move { action: ForeignAid, target: None, - }, &[&non_blocking_agent, &non_blocking_agent, &duke_block_agent]), + }, &channels), Ok(Phase::BlockChallenge( Move { action: ForeignAid, @@ -180,11 +203,12 @@ fn test_block() { // Test no one blocked { let mut game = game.clone(); + let (_txs, _rxs, channels) = make_channels(3); assert_eq!( game.block(Move { action: ForeignAid, target: None, - }, &[&non_blocking_agent, &non_blocking_agent, &non_blocking_agent]), + }, &channels), Ok(Phase::Resolution(Move { action: ForeignAid, target: None, @@ -195,21 +219,21 @@ fn test_block() { // Test bad block card { let mut game = game.clone(); + let (txs, _rxs, channels) = make_channels(3); + txs[2].send(Message::Block(Contessa)).unwrap(); assert!( game.block(Move { action: ForeignAid, target: None, - }, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent]) + }, &channels) .is_err() ); } } #[test] +#[timeout(1100)] fn test_block_challenge() { - let challenge_agent = DummyAgent(Assassin, None, true); - let non_challenge_agent = DummyAgent(Assassin, None, false); - let block_agent = DummyAgent(Contessa, None, false); let deck = vec![]; let discard = vec![]; let players = vec![ @@ -226,6 +250,7 @@ fn test_block_challenge() { // Test non-challenge { let mut game = game.clone(); + let (_txs, _rxs, channels) = make_channels(2); assert_eq!( game.block_challenge( Move { @@ -236,7 +261,7 @@ fn test_block_challenge() { blocker: 1, card: Contessa, }, - &[&non_challenge_agent, &block_agent] + &channels ), Ok(Phase::Done) ); @@ -246,6 +271,9 @@ fn test_block_challenge() { // Test failed challenge { let mut game = game.clone(); + let (txs, rxs, channels) = make_channels(2); + txs[0].send(Message::Challenge).unwrap(); + txs[0].send(Message::Discard(Assassin)).unwrap(); assert_eq!( game.block_challenge( Move { @@ -256,16 +284,20 @@ fn test_block_challenge() { blocker: 1, card: Contessa, }, - &[&challenge_agent, &block_agent] + &channels ), Ok(Phase::Done) ); + assert_eq!(rxs[0].recv(), Ok(Message::LoseInfluence)); assert!(!game.discard.is_empty()); } // Test successful challenge { let mut game = game.clone(); + let (txs, rxs, channels) = make_channels(2); + txs[0].send(Message::Challenge).unwrap(); + txs[1].send(Message::Discard(Contessa)).unwrap(); assert_eq!( game.block_challenge( Move { @@ -276,21 +308,21 @@ fn test_block_challenge() { blocker: 1, card: Duke, }, - &[&challenge_agent, &block_agent] + &channels, ), Ok(Phase::Resolution(Move { action: ForeignAid, target: None, })) ); + assert_eq!(rxs[1].recv(), Ok(Message::LoseInfluence)); assert!(!game.discard.is_empty()); } } #[test] +#[timeout(100)] fn test_resolution() { - let dummy_agent = DummyAgent(Assassin, Some(Duke), false); - let loser_agent = DummyAgent(Captain, Some(Duke), false); let deck = vec![Contessa, Contessa]; let discard = vec![]; let players = vec![ @@ -307,31 +339,36 @@ fn test_resolution() { // Test income { let mut game = game.clone(); + let (_txs, _rxs, channels) = make_channels(2); game.resolution(Move { action: Income, target: None, - }, &[&dummy_agent, &dummy_agent]).unwrap(); + }, &channels).unwrap(); assert_eq!(game.players[0].coins, 8); } // Test foreign aid { let mut game = game.clone(); + let (_txs, _rxs, channels) = make_channels(2); game.resolution(Move { action: ForeignAid, target: None, - }, &[&dummy_agent, &dummy_agent]).unwrap(); + }, &channels).unwrap(); assert_eq!(game.players[0].coins, 9); } // Test coup / assassinate { let mut game = game.clone(); + let (txs, rxs, channels) = make_channels(2); + txs[1].send(Message::Discard(Captain)).unwrap(); game.resolution(Move { action: Coup, target: Some(1), - }, &[&dummy_agent, &loser_agent]).unwrap(); + }, &channels).unwrap(); assert!(game.players[1].cards.is_empty()); + assert_eq!(rxs[1].recv(), Ok(Message::LoseInfluence)); assert_eq!(game.discard, vec![Captain]); assert_eq!(game.players[0].coins, 0); } @@ -339,10 +376,11 @@ fn test_resolution() { // Test steal { let mut game = game.clone(); + let (_txs, _rxs, channels) = make_channels(2); game.resolution(Move { action: Steal, target: Some(1), - }, &[&dummy_agent, &dummy_agent]).unwrap(); + }, &channels).unwrap(); assert_eq!(game.players[0].coins, 8); assert_eq!(game.players[1].coins, 0); } @@ -350,14 +388,17 @@ fn test_resolution() { // Test exchange { let mut game = game.clone(); + let (txs, _rxs, channels) = make_channels(2); + txs[0].send(Message::Discard(Duke)).unwrap(); + txs[0].send(Message::Discard(Contessa)).unwrap(); game.resolution(Move { action: Exchange, target: Some(1), - }, &[&dummy_agent, &dummy_agent]).unwrap(); + }, &channels).unwrap(); game.players[0].cards.sort(); - assert_eq!(game.players[0].cards, vec![Contessa, Contessa]); + assert_eq!(game.players[0].cards, vec![Assassin, Contessa]); game.deck.sort(); - assert_eq!(game.deck, vec![Duke, Assassin]); + assert_eq!(game.deck, vec![Duke, Contessa]); assert!(game.discard.is_empty()); } -} + }