Switch from using dyn traits to channels for io (breaks cli for now)
This commit is contained in:
parent
06352344fb
commit
cab89f5d64
122
Cargo.lock
generated
122
Cargo.lock
generated
@ -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"
|
||||
|
@ -6,4 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = "*"
|
||||
rand = "*"
|
||||
|
||||
[dev-dependencies]
|
||||
ntest = "*"
|
218
src/lib.rs
218
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<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;
|
||||
|
32
src/main.rs
32
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(())
|
||||
}
|
||||
|
145
src/test.rs
145
src/test.rs
@ -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!();
|
||||
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);
|
||||
}
|
||||
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
|
||||
}
|
||||
(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user