Add block challenge resolution phase

This commit is contained in:
Dane Johnson 2022-05-17 17:17:35 -05:00
parent 0dfad4ad30
commit 4101157e5f

View File

@ -84,7 +84,7 @@ impl fmt::Display for Card {
}
#[repr(u8)]
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Action {
Income,
ForeignAid,
@ -161,6 +161,9 @@ impl Player {
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 {
@ -219,7 +222,43 @@ impl Game {
players_turn_order.skip(1).filter(|p| self.players[*p].is_alive())
}
fn resolution(&mut self, resolution: Resolution, agents: &[&dyn Agent]) -> CoupResult<Phase> {
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<Phase> {
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(&current_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<Phase> {
let current_player = &mut self.players.get_mut(self.turn).unwrap();
let current_agent = agents[self.turn];
match resolution.action {
@ -228,9 +267,11 @@ impl Game {
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,
@ -259,29 +300,37 @@ impl Game {
_ => return Err("Steal resolution has no target"),
}
}
let next = self.turn_iterator().next();
if let Some(next) = next {
self.turn = next;
}
self.advance();
Ok(Phase::Done)
}
}
enum Phase {
#[derive(PartialEq, Debug)]
pub enum Phase {
Action(Action),
//ActionChallenge(ActionChallenge),
//Block(Block),
//BlockChallenge(BlockChallenge),
BlockChallenge(BlockChallenge),
Resolution(Resolution),
Done,
}
#[derive(PartialEq, Debug)]
pub struct BlockChallenge {
blocker: usize,
block_card: Card,
action: Action,
target: Option<usize>,
}
#[derive(PartialEq, Debug)]
pub struct Resolution {
action: Action,
target: Option<usize>,
}
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;
}
@ -290,18 +339,102 @@ pub trait Agent : fmt::Debug {
mod test {
use super::*;
#[test]
fn test_resolution() {
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] {
[Contessa, Duke]
$e
}
#[allow(unused)]
fn choose_lost_influence(&self, cards: &[Card]) -> Card {
Captain
$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![
@ -321,7 +454,7 @@ mod test {
game.resolution(Resolution {
action: Income,
target: None,
}, &[&DummyAgent, &DummyAgent]).unwrap();
}, &[&dummy_agent, &dummy_agent]).unwrap();
assert_eq!(game.players[0].coins, 3);
}
@ -331,7 +464,7 @@ mod test {
game.resolution(Resolution {
action: ForeignAid,
target: None,
}, &[&DummyAgent, &DummyAgent]).unwrap();
}, &[&dummy_agent, &dummy_agent]).unwrap();
assert_eq!(game.players[0].coins, 4);
}
@ -341,7 +474,7 @@ mod test {
game.resolution(Resolution {
action: Coup,
target: Some(1),
}, &[&DummyAgent, &DummyAgent]).unwrap();
}, &[&dummy_agent, &dummy_agent]).unwrap();
assert!(game.players[1].cards.is_empty());
assert_eq!(game.discard, vec![Captain]);
}
@ -352,7 +485,7 @@ mod test {
game.resolution(Resolution {
action: Steal,
target: Some(1),
}, &[&DummyAgent, &DummyAgent]).unwrap();
}, &[&dummy_agent, &dummy_agent]).unwrap();
assert_eq!(game.players[0].coins, 3);
assert_eq!(game.players[1].coins, 0);
}
@ -363,7 +496,7 @@ mod test {
game.resolution(Resolution {
action: Exchange,
target: Some(1),
}, &[&DummyAgent, &DummyAgent]).unwrap();
}, &[&dummy_agent, &dummy_agent]).unwrap();
game.players[0].cards.sort();
assert_eq!(game.players[0].cards, vec![Assassin, Contessa]);
game.deck.sort();