Add readme, action challenges, admit that I did it totally wrong

This commit is contained in:
2022-05-18 17:47:33 -05:00
parent 7801eb0dd1
commit 12d0b86e78
2 changed files with 174 additions and 40 deletions

View File

@@ -168,10 +168,15 @@ impl Player {
pub fn is_alive(&self) -> bool {
!self.cards.is_empty()
}
fn wins_challenge(&self, action: Action) -> bool {
self.cards.iter().find(|&c| c.allows_action(action)).is_some()
}
fn lose(&mut self, card: Card, deck: &mut Vec<Card>) {
self.cards.draw_first(card);
deck.push(card);
}
fn holds(&self, card: Card) -> bool {
self.cards.iter().find(|&c| *c == card).is_some()
}
@@ -201,6 +206,7 @@ macro_rules! thrice {
}
impl Game {
/// Creates a new game, with default [Players](Player) and a shuffled deck of [Cards](card)
pub fn new(num_players: usize) -> Self {
let mut deck = thrice![Duke, Assassin, Captain, Ambassador, Contessa];
deck.shuffle();
@@ -217,6 +223,7 @@ impl Game {
turn: 0,
}
}
/// If all but one [Players](Player) have lost all influence.
pub fn is_game_over(&self) -> bool {
self.players.iter().filter(|p| p.is_alive()).count() == 1
}
@@ -240,11 +247,45 @@ impl Game {
}
}
fn player_lose_influence(&mut self, id: usize, agents: &[&dyn Agent]) {
fn player_lose_influence(&mut self, id: usize, agents: &[&dyn Agent]) -> CoupResult<()>{
let player = &mut self.players[id];
let card = agents[id]
.choose_lost_influence(&player.cards);
player.lose(card, &mut self.discard);
if player.holds(card) {
player.lose(card, &mut self.discard);
Ok(())
} else {
Err("Player discarded a card they don't hold")
}
}
pub fn action_challenge(&mut self, action_challenge: phase::ActionChallenge, agents: &[&dyn Agent]) -> CoupResult<Phase> {
let block = Phase::Block(phase::Block {
action: action_challenge.action,
target: action_challenge.target,
});
match action_challenge.action.challenger_mode() {
ResMode::None => Ok(block),
ResMode::Target => unreachable!(),
ResMode::Anyone => {
let challenger = self.turn_iterator().find(|&id| agents[id].should_action_challenge(&action_challenge));
if let Some(challenger) = challenger{
let current_player_wins = self.players[self.turn].wins_challenge(action_challenge.action);
if current_player_wins {
// Challenger loses influence
self.player_lose_influence(challenger, agents)?;
Ok(block)
} else {
// Player loses influence
self.player_lose_influence(self.turn, agents)?;
// Turn is forfeit
Ok(Phase::Done)
}
} else {
Ok(block)
}
}
}
}
pub fn block(&mut self, block: phase::Block, agents: &[&dyn Agent]) -> CoupResult<Phase> {
@@ -298,11 +339,11 @@ impl Game {
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
self.player_lose_influence(self.turn, agents);
self.player_lose_influence(self.turn, agents)?;
Ok(Phase::Done)
} else {
// Player challenged correctly, blocker loses a card
self.player_lose_influence(block_challenge.blocker, agents);
self.player_lose_influence(block_challenge.blocker, agents)?;
// Game continues
Ok(Phase::Resolution(phase::Resolution {
action: block_challenge.action,
@@ -327,7 +368,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, agents)?;
}
}
_ => return Err("Coup/Assassinate resolution has no target"),
@@ -373,13 +414,19 @@ pub mod phase {
#[derive(PartialEq, Debug)]
pub enum Phase {
Action(Action),
//ActionChallenge(ActionChallenge),
ActionChallenge(ActionChallenge),
Block(Block),
BlockChallenge(BlockChallenge),
Resolution(Resolution),
Done,
}
#[derive(PartialEq, Debug)]
pub struct ActionChallenge {
pub action: Action,
pub target: Option<usize>,
}
#[derive(PartialEq, Debug)]
pub struct Block {
pub action: Action,
@@ -405,6 +452,8 @@ use phase::Phase;
/// An interface to a game to make strategic decisions.
pub trait Agent : fmt::Debug {
/// Should the agent challenge the action?
fn should_action_challenge(&self, action_challenge: &phase::ActionChallenge) -> 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, block: &phase::Block) -> Option<Card>;
@@ -422,37 +471,92 @@ pub trait Agent : fmt::Debug {
mod test {
use super::*;
macro_rules! make_dummy_agent {
($cbc: expr, $sbc: expr, $e: expr, $cli: expr) => {{
#[derive(Debug)]
struct DummyAgent;
impl Agent for DummyAgent {
#[allow(unused)]
fn choose_block_card(&self, block: &phase::Block) -> Option<Card> {
$cbc
}
#[allow(unused)]
fn should_block_challenge(&self, block_challenge: &phase::BlockChallenge) -> bool {
$sbc
}
#[allow(unused)]
fn exchange(&self, cards: &[Card]) -> [Card; 2] {
$e
}
#[allow(unused)]
fn choose_lost_influence(&self, cards: &[Card]) -> Card {
$cli
}
}
DummyAgent
}}
#[derive(Debug)]
struct DummyAgent(Card, Option<Card>, bool);
impl Agent for DummyAgent {
fn should_action_challenge(&self, _action_challenge: &phase::ActionChallenge) -> bool {
self.2
}
fn choose_block_card(&self, _block: &phase::Block) -> Option<Card> {
self.1
}
fn should_block_challenge(&self, _block_challenge: &phase::BlockChallenge) -> bool {
self.2
}
fn exchange(&self, _cards: &[Card]) -> [Card; 2] {
[self.0, self.1.unwrap()]
}
fn choose_lost_influence(&self, _cards: &[Card]) -> Card {
self.0
}
}
#[test]
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![
Player { coins: 2, cards: vec![Duke, Captain] },
Player { coins: 2, cards: vec![Contessa] },
Player { coins: 2, cards: vec![Ambassador] },
];
let game = Game {
deck,
discard,
players,
turn: 0
};
// Test non-challengable
{
let mut game = game.clone();
assert_eq!(
game.action_challenge(phase::ActionChallenge {
action: Income,
target: None,
}, &[&challenge_agent, &challenge_agent, &challenge_agent]),
Ok(Phase::Block(phase::Block {
action: Income,
target: None
}))
);
}
// Test failed challenge
{
let mut game = game.clone();
assert_eq!(
game.action_challenge(phase::ActionChallenge {
action: Steal,
target: Some(2),
}, &[&challenge_agent, &challenge_agent, &challenge_agent]),
Ok(Phase::Block(phase::Block {
action: Steal,
target: Some(2)
}))
);
}
// Test successful challenge
{
let mut game = game.clone();
assert_eq!(
game.action_challenge(phase::ActionChallenge {
action: Assassinate,
target: Some(2),
}, &[&non_challenge_agent, &challenge_agent, &challenge_agent]),
Ok(Phase::Done),
);
}
}
#[test]
fn test_block() {
let non_blocking_agent = make_dummy_agent!(None, unimplemented!(), unimplemented!(), unimplemented!());
let ambassador_block_agent = make_dummy_agent!(Some(Ambassador), unimplemented!(), unimplemented!(), unimplemented!());
let duke_block_agent = make_dummy_agent!(Some(Duke), unimplemented!(), unimplemented!(), unimplemented!());
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![];
@@ -564,9 +668,9 @@ mod test {
#[test]
fn test_block_challenge() {
let challenge_agent = make_dummy_agent!(unimplemented!(), true, unimplemented!(), Assassin);
let non_challenge_agent = make_dummy_agent!(unimplemented!(), false, unimplemented!(), Assassin);
let block_agent = make_dummy_agent!(unimplemented!(), unimplemented!(), unimplemented!(), Contessa);
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![
@@ -632,7 +736,8 @@ mod test {
#[test]
fn test_resolution() {
let dummy_agent = make_dummy_agent!(unimplemented!(), unimplemented!(), [Contessa, Duke], Captain);
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![
@@ -672,7 +777,7 @@ mod test {
game.resolution(phase::Resolution {
action: Coup,
target: Some(1),
}, &[&dummy_agent, &dummy_agent]).unwrap();
}, &[&dummy_agent, &loser_agent]).unwrap();
assert!(game.players[1].cards.is_empty());
assert_eq!(game.discard, vec![Captain]);
}
@@ -696,9 +801,9 @@ mod test {
target: Some(1),
}, &[&dummy_agent, &dummy_agent]).unwrap();
game.players[0].cards.sort();
assert_eq!(game.players[0].cards, vec![Assassin, Contessa]);
assert_eq!(game.players[0].cards, vec![Contessa, Contessa]);
game.deck.sort();
assert_eq!(game.deck, vec![Duke, Contessa]);
assert_eq!(game.deck, vec![Duke, Assassin]);
assert!(game.discard.is_empty());
}
}