Switch from using dyn traits to channels for io (breaks cli for now)

This commit is contained in:
Dane Johnson 2022-05-24 13:42:49 -05:00
parent 06352344fb
commit cab89f5d64
5 changed files with 401 additions and 121 deletions

122
Cargo.lock generated
View File

@ -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"

View File

@ -7,3 +7,6 @@ edition = "2021"
[dependencies]
rand = "*"
[dev-dependencies]
ntest = "*"

View File

@ -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<Phase> {
let move_ = agents[self.turn].choose_move(self);
pub fn action(&mut self, channels: &Channels) -> CoupResult<Phase> {
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<Phase> {
pub fn action_challenge(&mut self, move_: Move, channels: &Channels) -> CoupResult<Phase> {
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<Phase> {
pub fn block(&mut self, move_: Move, channels: &Channels) -> CoupResult<Phase> {
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<Phase> {
if agents[self.turn].should_block_challenge(self, move_, block) {
pub fn block_challenge(&mut self, move_: Move, block: Block, channels: &Channels) -> CoupResult<Phase> {
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<Phase> {
pub fn resolution(&mut self, move_: Move, channels: &Channels) -> CoupResult<Phase> {
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<Card>;
/// 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<mpsc::Sender<Message>>,
rxs: Vec<mpsc::Receiver<Message>>,
}
impl Channels {
pub fn add_channel(&mut self, tx: mpsc::Sender<Message>, rx: mpsc::Receiver<Message>) {
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<Message> {
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<Move> {
let msg = self.recv(id);
match msg {
Message::Move(move_) => Ok(move_),
_ => Err("Expected move"),
}
}
pub fn recv_discard(&self, id: usize) -> CoupResult<Card> {
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<Card> {
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;

View File

@ -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(())
}

View File

@ -1,32 +1,26 @@
use super::*;
#[derive(Debug)]
struct DummyAgent(Card, Option<Card>, bool);
impl Agent for DummyAgent {
fn choose_move(&self, _game: &Game) -> Move {
unimplemented!();
}
fn should_action_challenge(&self, _game: &Game, _move: Move) -> bool {
self.2
}
fn choose_block_card(&self, _game: &Game, _move: Move) -> Option<Card> {
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
use ntest::timeout;
use std::sync::mpsc;
fn make_channels(nplayers: usize) -> (Vec<mpsc::Sender<Message>>, Vec<mpsc::Receiver<Message>>, 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);
}
(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,11 +38,28 @@ 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
@ -56,40 +67,46 @@ fn test_action_challenge() {
);
}
// 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());
}
}
}