Use common structures for game phases

This commit is contained in:
Dane Johnson 2022-05-20 11:55:20 -05:00
parent 0e1381c15f
commit 1c8ffb151c
2 changed files with 132 additions and 151 deletions

View File

@ -258,22 +258,18 @@ impl Game {
} }
} }
pub fn action_challenge(&mut self, action_challenge: phase::ActionChallenge, agents: &[&dyn Agent]) -> CoupResult<Phase> { pub fn action_challenge(&mut self, move_: Move, agents: &[&dyn Agent]) -> CoupResult<Phase> {
let block = Phase::Block(phase::Block { match move_.action.challenger_mode() {
action: action_challenge.action, ResMode::None => Ok(Phase::Block(move_)),
target: action_challenge.target,
});
match action_challenge.action.challenger_mode() {
ResMode::None => Ok(block),
ResMode::Target => unreachable!(), ResMode::Target => unreachable!(),
ResMode::Anyone => { ResMode::Anyone => {
let challenger = self.turn_iterator().find(|&id| agents[id].should_action_challenge(self, &action_challenge)); let challenger = self.turn_iterator().find(|&id| agents[id].should_action_challenge(self, move_));
if let Some(challenger) = challenger{ if let Some(challenger) = challenger{
let current_player_wins = self.players[self.turn].wins_challenge(action_challenge.action); let current_player_wins = self.players[self.turn].wins_challenge(move_.action);
if current_player_wins { if current_player_wins {
// Challenger loses influence // Challenger loses influence
self.player_lose_influence(challenger, agents)?; self.player_lose_influence(challenger, agents)?;
Ok(block) Ok(Phase::Block(move_))
} else { } else {
// Player loses influence // Player loses influence
self.player_lose_influence(self.turn, agents)?; self.player_lose_influence(self.turn, agents)?;
@ -281,73 +277,57 @@ impl Game {
Ok(Phase::Done) Ok(Phase::Done)
} }
} else { } else {
Ok(block) Ok(Phase::Block(move_))
} }
} }
} }
} }
pub fn block(&mut self, block: phase::Block, agents: &[&dyn Agent]) -> CoupResult<Phase> { pub fn block(&mut self, move_: Move, agents: &[&dyn Agent]) -> CoupResult<Phase> {
match block.action.blocker_mode() { match move_.action.blocker_mode() {
ResMode::None => Ok(Phase::Resolution(phase::Resolution { ResMode::None => Ok(Phase::Resolution(move_)),
action: block.action, ResMode::Target => match agents[move_.target.unwrap()].choose_block_card(self, move_) {
target: block.target,
})),
ResMode::Target => match agents[block.target.unwrap()].choose_block_card(self, &block) {
Some(card) => { Some(card) => {
if card.blocks_action(block.action) { if card.blocks_action(move_.action) {
Ok(Phase::BlockChallenge(phase::BlockChallenge { Ok(Phase::BlockChallenge(move_, Block {
blocker: block.target.unwrap(), blocker: move_.target.unwrap(),
block_card: card, card,
action: block.action,
target: block.target,
})) }))
} else { } else {
Err("Card does not block action") Err("Card does not block action")
} }
}, },
None => Ok(Phase::Resolution(phase::Resolution { None => Ok(Phase::Resolution(move_)),
action: block.action,
target: block.target,
})),
} }
ResMode::Anyone => { ResMode::Anyone => {
for id in self.turn_iterator() { for id in self.turn_iterator() {
if let Some(card) = agents[id].choose_block_card(self, &block) { if let Some(card) = agents[id].choose_block_card(self, move_) {
if card.blocks_action(block.action) { if card.blocks_action(move_.action) {
return Ok(Phase::BlockChallenge(phase::BlockChallenge { return Ok(Phase::BlockChallenge(move_, Block {
blocker: id, blocker: id,
block_card: card, card,
action: block.action,
target: block.target,
})) }))
} else { } else {
return Err("Card does not block action") return Err("Card does not block action")
} }
} }
} }
Ok(Phase::Resolution(phase::Resolution { Ok(Phase::Resolution(move_))
action: block.action,
target: block.target,
}))
} }
} }
} }
pub fn block_challenge(&mut self, block_challenge: phase::BlockChallenge, agents: &[&dyn Agent]) -> CoupResult<Phase> { pub fn block_challenge(&mut self, move_: Move, block: Block, agents: &[&dyn Agent]) -> CoupResult<Phase> {
if agents[self.turn].should_block_challenge(self, &block_challenge) { if agents[self.turn].should_block_challenge(self, move_, block) {
if self.players[block_challenge.blocker].holds(block_challenge.block_card) { if self.players[block.blocker].holds(block.card) {
// Player challenged incorrectly, loses influence and turn is forfeit // Player challenged incorrectly, loses influence and turn is forfeit
self.player_lose_influence(self.turn, agents)?; self.player_lose_influence(self.turn, agents)?;
Ok(Phase::Done) Ok(Phase::Done)
} else { } else {
// Player challenged correctly, blocker loses a card // Player challenged correctly, blocker loses a card
self.player_lose_influence(block_challenge.blocker, agents)?; self.player_lose_influence(block.blocker, agents)?;
// Game continues // Game continues
Ok(Phase::Resolution(phase::Resolution { Ok(Phase::Resolution(move_))
action: block_challenge.action,
target: block_challenge.target,
}))
} }
} else { } else {
// Player chose not to challenge the block, turn is forfeit // Player chose not to challenge the block, turn is forfeit
@ -356,11 +336,11 @@ impl Game {
} }
} }
pub fn resolution(&mut self, resolution: phase::Resolution, agents: &[&dyn Agent]) -> CoupResult<Phase> { pub fn resolution(&mut self, move_: Move, agents: &[&dyn Agent]) -> CoupResult<Phase> {
match resolution.action { match move_.action {
Income => self.players[self.turn].coins += 1, Income => self.players[self.turn].coins += 1,
ForeignAid => self.players[self.turn].coins += 2, ForeignAid => self.players[self.turn].coins += 2,
Coup | Assassinate => match resolution.target { Coup | Assassinate => match move_.target {
Some(target) => { Some(target) => {
// Target may have died from challenge // Target may have died from challenge
let target_alive = self.players[target].is_alive(); let target_alive = self.players[target].is_alive();
@ -386,7 +366,7 @@ impl Game {
self.players[self.turn].cards = choices; self.players[self.turn].cards = choices;
self.deck.shuffle(); self.deck.shuffle();
} }
Steal => match resolution.target { Steal => match move_.target {
Some(target) => { Some(target) => {
let val = self.players[target].coins.min(2); let val = self.players[target].coins.min(2);
self.players[self.turn].coins += val; self.players[self.turn].coins += val;
@ -401,61 +381,41 @@ impl Game {
} }
} }
pub mod phase { #[derive(PartialEq, Debug, Clone, Copy)]
//! Structures relating to game phases. pub struct Move {
//! pub action: Action,
//! Coup turns have 5 phases, depending on what actions are taken each phase pub target: Option<usize>,
//! some phases might be skipped. These structures marshal information }
//! between the phases.
use super::{ Card, Action }; #[derive(PartialEq, Debug, Clone, Copy)]
pub struct Block {
pub blocker: usize,
pub card: Card,
}
/// Phase we should move to.
///
/// Coup turns have 5 phases, depending on what actions are taken each phase
/// some phases might be skipped.
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum Phase { pub enum Phase {
Action(Action), Action(Move),
ActionChallenge(ActionChallenge), ActionChallenge(Move),
Block(Block), Block(Move),
BlockChallenge(BlockChallenge), BlockChallenge(Move, Block),
Resolution(Resolution), Resolution(Move),
Done, Done,
} }
#[derive(PartialEq, Debug)]
pub struct ActionChallenge {
pub action: Action,
pub target: Option<usize>,
}
#[derive(PartialEq, Debug)]
pub struct Block {
pub action: Action,
pub target: Option<usize>,
}
#[derive(PartialEq, Debug)]
pub struct BlockChallenge {
pub blocker: usize,
pub block_card: Card,
pub action: Action,
pub target: Option<usize>,
}
#[derive(PartialEq, Debug)]
pub struct Resolution {
pub action: Action,
pub target: Option<usize>,
}
}
use phase::Phase;
/// An interface to a game to make strategic decisions. /// An interface to a game to make strategic decisions.
pub trait Agent : fmt::Debug { pub trait Agent : fmt::Debug {
/// Should the agent challenge the action? /// Should the agent challenge the action?
fn should_action_challenge(&self, game: &Game, action_challenge: &phase::ActionChallenge) -> bool; fn should_action_challenge(&self, game: &Game, move_: Move) -> bool;
/// Which [card](Card) the agent wishes to use to block the current action. /// 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] /// If the agent does not wish to block, it should return [None]
fn choose_block_card(&self, game: &Game, block: &phase::Block) -> Option<Card>; fn choose_block_card(&self, game: &Game, move_: Move) -> Option<Card>;
/// Should the agent challenge the block? /// Should the agent challenge the block?
fn should_block_challenge(&self, game: &Game, block_challenge: &phase::BlockChallenge) -> bool; fn should_block_challenge(&self, game: &Game, move_: Move, block: Block) -> bool;
/// The [Ambassador]'s exchange. /// The [Ambassador]'s exchange.
/// Given 3 or 4 [Cards](Card) the agent must return two cards to the deck. /// Given 3 or 4 [Cards](Card) the agent must return two cards to the deck.
fn exchange(&self, game: &Game, cards: &[Card]) -> [Card; 2]; fn exchange(&self, game: &Game, cards: &[Card]) -> [Card; 2];

View File

@ -3,13 +3,13 @@ use super::*;
#[derive(Debug)] #[derive(Debug)]
struct DummyAgent(Card, Option<Card>, bool); struct DummyAgent(Card, Option<Card>, bool);
impl Agent for DummyAgent { impl Agent for DummyAgent {
fn should_action_challenge(&self, _game: &Game, _action_challenge: &phase::ActionChallenge) -> bool { fn should_action_challenge(&self, _game: &Game, _move: Move) -> bool {
self.2 self.2
} }
fn choose_block_card(&self, _game: &Game, _block: &phase::Block) -> Option<Card> { fn choose_block_card(&self, _game: &Game, _move: Move) -> Option<Card> {
self.1 self.1
} }
fn should_block_challenge(&self, _game: &Game, _block_challenge: &phase::BlockChallenge) -> bool { fn should_block_challenge(&self, _game: &Game, _move: Move, _block: Block) -> bool {
self.2 self.2
} }
fn exchange(&self, _game: &Game, _cards: &[Card]) -> [Card; 2] { fn exchange(&self, _game: &Game, _cards: &[Card]) -> [Card; 2] {
@ -42,11 +42,11 @@ fn test_action_challenge() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.action_challenge(phase::ActionChallenge { game.action_challenge(Move {
action: Income, action: Income,
target: None, target: None,
}, &[&challenge_agent, &challenge_agent, &challenge_agent]), }, &[&challenge_agent, &challenge_agent, &challenge_agent]),
Ok(Phase::Block(phase::Block { Ok(Phase::Block(Move {
action: Income, action: Income,
target: None target: None
})) }))
@ -57,11 +57,11 @@ fn test_action_challenge() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.action_challenge(phase::ActionChallenge { game.action_challenge(Move {
action: Steal, action: Steal,
target: Some(2), target: Some(2),
}, &[&challenge_agent, &challenge_agent, &challenge_agent]), }, &[&challenge_agent, &challenge_agent, &challenge_agent]),
Ok(Phase::Block(phase::Block { Ok(Phase::Block(Move {
action: Steal, action: Steal,
target: Some(2) target: Some(2)
})) }))
@ -72,7 +72,7 @@ fn test_action_challenge() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.action_challenge(phase::ActionChallenge { game.action_challenge(Move {
action: Assassinate, action: Assassinate,
target: Some(2), target: Some(2),
}, &[&non_challenge_agent, &challenge_agent, &challenge_agent]), }, &[&non_challenge_agent, &challenge_agent, &challenge_agent]),
@ -106,11 +106,11 @@ fn test_block() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.block(phase::Block { game.block(Move {
action: Income, action: Income,
target: None, target: None,
}, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]), }, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]),
Ok(Phase::Resolution(phase::Resolution { Ok(Phase::Resolution(Move {
action: Income, action: Income,
target: None target: None
})) }))
@ -121,16 +121,20 @@ fn test_block() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.block(phase::Block { game.block(Move {
action: Steal, action: Steal,
target: Some(2), target: Some(2),
}, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]), }, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]),
Ok(Phase::BlockChallenge(phase::BlockChallenge { Ok(Phase::BlockChallenge(
blocker: 2, Move {
block_card: Ambassador,
action: Steal, action: Steal,
target: Some(2), target: Some(2),
})) },
Block {
blocker: 2,
card: Ambassador,
},
)),
); );
} }
@ -138,11 +142,11 @@ fn test_block() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.block(phase::Block { game.block(Move {
action: Steal, action: Steal,
target: Some(2), target: Some(2),
}, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent]), }, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent]),
Ok(Phase::Resolution(phase::Resolution { Ok(Phase::Resolution(Move {
action: Steal, action: Steal,
target: Some(2), target: Some(2),
})) }))
@ -153,16 +157,20 @@ fn test_block() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.block(phase::Block { game.block(Move {
action: ForeignAid, action: ForeignAid,
target: None, target: None,
}, &[&non_blocking_agent, &non_blocking_agent, &duke_block_agent]), }, &[&non_blocking_agent, &non_blocking_agent, &duke_block_agent]),
Ok(Phase::BlockChallenge(phase::BlockChallenge { Ok(Phase::BlockChallenge(
blocker: 2, Move {
block_card: Duke,
action: ForeignAid, action: ForeignAid,
target: None, target: None,
})) },
Block {
blocker: 2,
card: Duke,
},
)),
); );
} }
@ -170,11 +178,11 @@ fn test_block() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.block(phase::Block { game.block(Move {
action: ForeignAid, action: ForeignAid,
target: None, target: None,
}, &[&non_blocking_agent, &non_blocking_agent, &non_blocking_agent]), }, &[&non_blocking_agent, &non_blocking_agent, &non_blocking_agent]),
Ok(Phase::Resolution(phase::Resolution { Ok(Phase::Resolution(Move {
action: ForeignAid, action: ForeignAid,
target: None, target: None,
})) }))
@ -185,14 +193,13 @@ fn test_block() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert!( assert!(
game.block(phase::Block { game.block(Move {
action: ForeignAid, action: ForeignAid,
target: None, target: None,
}, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent]) }, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent])
.is_err() .is_err()
); );
} }
} }
#[test] #[test]
@ -217,12 +224,17 @@ fn test_block_challenge() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.block_challenge( phase::BlockChallenge { game.block_challenge(
blocker: 1, Move {
block_card: Contessa,
action: Assassinate, action: Assassinate,
target: Some(1), target: Some(1),
}, &[&non_challenge_agent, &block_agent]), },
Block {
blocker: 1,
card: Contessa,
},
&[&non_challenge_agent, &block_agent]
),
Ok(Phase::Done) Ok(Phase::Done)
); );
assert!(game.discard.is_empty()); assert!(game.discard.is_empty());
@ -232,12 +244,17 @@ fn test_block_challenge() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.block_challenge( phase::BlockChallenge { game.block_challenge(
blocker: 1, Move {
block_card: Contessa,
action: Assassinate, action: Assassinate,
target: Some(1), target: Some(1),
}, &[&challenge_agent, &block_agent]), },
Block {
blocker: 1,
card: Contessa,
},
&[&challenge_agent, &block_agent]
),
Ok(Phase::Done) Ok(Phase::Done)
); );
assert!(!game.discard.is_empty()); assert!(!game.discard.is_empty());
@ -247,20 +264,24 @@ fn test_block_challenge() {
{ {
let mut game = game.clone(); let mut game = game.clone();
assert_eq!( assert_eq!(
game.block_challenge( phase::BlockChallenge { game.block_challenge(
blocker: 1, Move {
block_card: Duke,
action: ForeignAid, action: ForeignAid,
target: None, target: None,
}, &[&challenge_agent, &block_agent]), },
Ok(Phase::Resolution(phase::Resolution { Block {
blocker: 1,
card: Duke,
},
&[&challenge_agent, &block_agent]
),
Ok(Phase::Resolution(Move {
action: ForeignAid, action: ForeignAid,
target: None, target: None,
})) }))
); );
assert!(!game.discard.is_empty()); assert!(!game.discard.is_empty());
} }
} }
#[test] #[test]
@ -283,7 +304,7 @@ fn test_resolution() {
// Test income // Test income
{ {
let mut game = game.clone(); let mut game = game.clone();
game.resolution(phase::Resolution { game.resolution(Move {
action: Income, action: Income,
target: None, target: None,
}, &[&dummy_agent, &dummy_agent]).unwrap(); }, &[&dummy_agent, &dummy_agent]).unwrap();
@ -293,7 +314,7 @@ fn test_resolution() {
// Test foreign aid // Test foreign aid
{ {
let mut game = game.clone(); let mut game = game.clone();
game.resolution(phase::Resolution { game.resolution(Move {
action: ForeignAid, action: ForeignAid,
target: None, target: None,
}, &[&dummy_agent, &dummy_agent]).unwrap(); }, &[&dummy_agent, &dummy_agent]).unwrap();
@ -303,7 +324,7 @@ fn test_resolution() {
// Test coup / assassinate // Test coup / assassinate
{ {
let mut game = game.clone(); let mut game = game.clone();
game.resolution(phase::Resolution { game.resolution(Move {
action: Coup, action: Coup,
target: Some(1), target: Some(1),
}, &[&dummy_agent, &loser_agent]).unwrap(); }, &[&dummy_agent, &loser_agent]).unwrap();
@ -314,7 +335,7 @@ fn test_resolution() {
// Test steal // Test steal
{ {
let mut game = game.clone(); let mut game = game.clone();
game.resolution(phase::Resolution { game.resolution(Move {
action: Steal, action: Steal,
target: Some(1), target: Some(1),
}, &[&dummy_agent, &dummy_agent]).unwrap(); }, &[&dummy_agent, &dummy_agent]).unwrap();
@ -325,7 +346,7 @@ fn test_resolution() {
// Test exchange // Test exchange
{ {
let mut game = game.clone(); let mut game = game.clone();
game.resolution(phase::Resolution { game.resolution(Move {
action: Exchange, action: Exchange,
target: Some(1), target: Some(1),
}, &[&dummy_agent, &dummy_agent]).unwrap(); }, &[&dummy_agent, &dummy_agent]).unwrap();