sorta working, probably very buggy
This commit is contained in:
parent
9508a20082
commit
e2eebcbe07
219
src/game.rs
219
src/game.rs
@ -13,6 +13,7 @@ const COLORS: [Color; 5] = ["blue", "red", "yellow", "white", "green"];
|
|||||||
pub type Value = u32;
|
pub type Value = u32;
|
||||||
// list of (value, count) pairs
|
// list of (value, count) pairs
|
||||||
const VALUE_COUNTS : [(Value, u32); 5] = [(1, 3), (2, 2), (3, 2), (4, 2), (5, 1)];
|
const VALUE_COUNTS : [(Value, u32); 5] = [(1, 3), (2, 2), (3, 2), (4, 2), (5, 1)];
|
||||||
|
const FINAL_VALUE : Value = 5;
|
||||||
|
|
||||||
pub struct Card {
|
pub struct Card {
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
@ -40,71 +41,116 @@ impl Pile {
|
|||||||
pub fn take(&mut self, index: usize) -> Card {
|
pub fn take(&mut self, index: usize) -> Card {
|
||||||
self.0.remove(index)
|
self.0.remove(index)
|
||||||
}
|
}
|
||||||
|
pub fn top(&self) -> Option<&Card> {
|
||||||
|
self.0.last()
|
||||||
|
}
|
||||||
pub fn shuffle(&mut self) {
|
pub fn shuffle(&mut self) {
|
||||||
rand::thread_rng().shuffle(&mut self.0[..]);
|
rand::thread_rng().shuffle(&mut self.0[..]);
|
||||||
}
|
}
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Hand = Vec<Card>;
|
|
||||||
pub type Player = u32;
|
pub type Player = u32;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Hint {
|
||||||
|
Color,
|
||||||
|
Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
// represents the choice a player made in a given turn
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TurnChoice {
|
||||||
|
Hint,
|
||||||
|
Discard(usize),
|
||||||
|
Play(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
// represents a turn taken in the game
|
||||||
|
pub struct Turn<'a> {
|
||||||
|
pub player: &'a Player,
|
||||||
|
pub choice: &'a TurnChoice,
|
||||||
|
}
|
||||||
|
|
||||||
|
// represents possible settings for the game
|
||||||
pub struct GameOptions {
|
pub struct GameOptions {
|
||||||
pub num_players: u32,
|
pub num_players: u32,
|
||||||
pub hand_size: u32,
|
pub hand_size: u32,
|
||||||
// when hits 0, you cannot hint
|
// when hits 0, you cannot hint
|
||||||
pub total_hints: u32,
|
pub num_hints: u32,
|
||||||
// when hits 0, you lose
|
// when hits 0, you lose
|
||||||
pub total_lives: u32,
|
pub num_lives: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// The state of a given player: all other players may see this
|
// The state of a given player: all other players may see this
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct PlayerState {
|
pub struct PlayerState {
|
||||||
hand: Hand,
|
// the player's actual hand
|
||||||
|
pub hand: Pile,
|
||||||
|
// represents what is common knowledge about the player's hand
|
||||||
|
// pub known: ,
|
||||||
}
|
}
|
||||||
|
|
||||||
// State of everything except the player's hands
|
// State of everything except the player's hands
|
||||||
// Is completely common knowledge
|
// Is all completely common knowledge
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct BoardState {
|
pub struct BoardState {
|
||||||
pub deck: Pile,
|
deck: Pile,
|
||||||
pub discard: Pile,
|
pub discard: Pile,
|
||||||
pub fireworks: HashMap<Color, Pile>,
|
pub fireworks: HashMap<Color, Pile>,
|
||||||
|
|
||||||
// // whose turn is it?
|
pub num_players: u32,
|
||||||
pub next: Player,
|
|
||||||
|
|
||||||
|
// which turn is it?
|
||||||
|
pub turn: u32,
|
||||||
|
// // whose turn is it?
|
||||||
|
pub player: Player,
|
||||||
|
|
||||||
|
pub hints_total: u32,
|
||||||
pub hints_remaining: u32,
|
pub hints_remaining: u32,
|
||||||
|
pub lives_total: u32,
|
||||||
pub lives_remaining: u32,
|
pub lives_remaining: u32,
|
||||||
// only relevant when deck runs out
|
// only relevant when deck runs out
|
||||||
turns_remaining: u32,
|
deckless_turns_remaining: u32,
|
||||||
}
|
|
||||||
|
|
||||||
// complete game state (known to nobody!)
|
|
||||||
pub struct GameState {
|
|
||||||
pub player_states: HashMap<Player, PlayerState>,
|
|
||||||
pub board_state: BoardState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// complete game view of a given player
|
// complete game view of a given player
|
||||||
pub struct GameStateView {
|
// state will be borrowed GameState
|
||||||
// not yet implemented
|
#[derive(Debug)]
|
||||||
pub other_player_states: HashMap<Player, PlayerState>,
|
pub struct GameStateView<'a> {
|
||||||
pub board_state: BoardState,
|
// the player whose view it is
|
||||||
|
pub player: Player,
|
||||||
|
// what is known about their own hand
|
||||||
|
// pub known:
|
||||||
|
// the cards of the other players
|
||||||
|
pub other_player_states: HashMap<Player, &'a PlayerState>,
|
||||||
|
// board state
|
||||||
|
pub board: &'a BoardState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// complete game state (known to nobody!)
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GameState {
|
||||||
|
pub player_states: HashMap<Player, PlayerState>,
|
||||||
|
pub board: BoardState,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Score = u32;
|
||||||
|
|
||||||
impl GameState {
|
impl GameState {
|
||||||
pub fn new(opts: GameOptions) -> GameState {
|
pub fn new(opts: GameOptions) -> GameState {
|
||||||
let mut deck = GameState::make_deck();
|
let mut deck = GameState::make_deck();
|
||||||
|
|
||||||
let mut player_states : HashMap<Player, PlayerState> = HashMap::new();
|
let mut player_states : HashMap<Player, PlayerState> = HashMap::new();
|
||||||
for i in 0..opts.num_players {
|
for i in 0..opts.num_players {
|
||||||
let hand : Hand = (0..opts.hand_size)
|
let raw_hand = (0..opts.hand_size).map(|_| {
|
||||||
.map(|i| {
|
|
||||||
// we can assume the deck is big enough to draw initial hands
|
// we can assume the deck is big enough to draw initial hands
|
||||||
deck.draw().unwrap()
|
deck.draw().unwrap()
|
||||||
})
|
}).collect::<Vec<_>>();
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let state = PlayerState {
|
let state = PlayerState {
|
||||||
hand: hand,
|
hand: Pile(raw_hand),
|
||||||
};
|
};
|
||||||
player_states.insert(i, state);
|
player_states.insert(i, state);
|
||||||
}
|
}
|
||||||
@ -119,15 +165,19 @@ impl GameState {
|
|||||||
|
|
||||||
GameState {
|
GameState {
|
||||||
player_states: player_states,
|
player_states: player_states,
|
||||||
board_state: BoardState {
|
board: BoardState {
|
||||||
deck: deck,
|
deck: deck,
|
||||||
fireworks: fireworks,
|
fireworks: fireworks,
|
||||||
discard: Pile::new(),
|
discard: Pile::new(),
|
||||||
next: 0,
|
num_players: opts.num_players,
|
||||||
hints_remaining: opts.total_hints,
|
player: 0,
|
||||||
lives_remaining: opts.total_lives,
|
turn: 1,
|
||||||
// only relevant when deck runs out
|
hints_total: opts.num_hints,
|
||||||
turns_remaining: opts.num_players,
|
hints_remaining: opts.num_hints,
|
||||||
|
lives_total: opts.num_lives,
|
||||||
|
lives_remaining: opts.num_lives,
|
||||||
|
// number of turns to play with deck length ran out
|
||||||
|
deckless_turns_remaining: opts.num_players + 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,8 +187,8 @@ impl GameState {
|
|||||||
|
|
||||||
for color in COLORS.iter() {
|
for color in COLORS.iter() {
|
||||||
for &(value, count) in VALUE_COUNTS.iter() {
|
for &(value, count) in VALUE_COUNTS.iter() {
|
||||||
for _ in 0..3 {
|
for _ in 0..count {
|
||||||
deck.place(Card {color: color, value: 1});
|
deck.place(Card {color: color, value: value});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -146,29 +196,104 @@ impl GameState {
|
|||||||
println!("Created deck: {:?}", deck);
|
println!("Created deck: {:?}", deck);
|
||||||
deck
|
deck
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_players(&self) -> Vec<Player> {
|
||||||
|
(0..self.board.num_players).collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Hint {
|
pub fn is_over(&self) -> bool {
|
||||||
Color,
|
// TODO: add condition that fireworks cannot be further completed?
|
||||||
Value,
|
(self.board.lives_remaining == 0) ||
|
||||||
|
(self.board.deckless_turns_remaining == 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Turn {
|
pub fn score(&self) -> Score {
|
||||||
Hint,
|
let mut score = 0;
|
||||||
Discard,
|
for (_, firework) in &self.board.fireworks {
|
||||||
Play,
|
score += firework.size();
|
||||||
|
}
|
||||||
|
score as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trait to implement for any valid Hanabi strategy
|
// get the game state view of a particular player
|
||||||
pub trait Strategy {
|
pub fn get_view(&self, player: Player) -> GameStateView {
|
||||||
fn decide(&mut self, &GameStateView) -> Turn;
|
let mut other_player_states = HashMap::new();
|
||||||
fn update(&mut self, Turn);
|
for (other_player, state) in &self.player_states {
|
||||||
|
if player != *other_player {
|
||||||
|
other_player_states.insert(player, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GameStateView {
|
||||||
|
player: player,
|
||||||
|
other_player_states: other_player_states,
|
||||||
|
board: &self.board,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simulate_symmetric(opts: GameOptions, strategy: &Strategy) {
|
// takes a card from the player's hand, and replaces it if possible
|
||||||
let strategies = (0..opts.num_players).map(|_| { Box::new(strategy) }).collect();
|
fn take_from_hand(&mut self, index: usize) -> Card {
|
||||||
simulate(opts, strategies)
|
let ref mut hand = self.player_states.get_mut(&self.board.player).unwrap().hand;
|
||||||
|
let card = hand.take(index);
|
||||||
|
if let Some(new_card) = self.board.deck.draw() {
|
||||||
|
hand.place(new_card);
|
||||||
|
}
|
||||||
|
card
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simulate(opts: GameOptions, strategies: Vec<Box<&Strategy>>) {
|
fn try_add_hint(&mut self) {
|
||||||
|
if self.board.hints_remaining < self.board.hints_total {
|
||||||
|
self.board.hints_remaining += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_choice(&mut self, choice: TurnChoice) {
|
||||||
|
match choice {
|
||||||
|
TurnChoice::Hint => {
|
||||||
|
assert!(self.board.hints_remaining > 0);
|
||||||
|
self.board.hints_remaining -= 1;
|
||||||
|
// TODO: actually inform player of values..
|
||||||
|
// nothing to update, really...
|
||||||
|
// TODO: manage common knowledge
|
||||||
|
}
|
||||||
|
TurnChoice::Discard(index) => {
|
||||||
|
let card = self.take_from_hand(index);
|
||||||
|
self.board.discard.place(card);
|
||||||
|
|
||||||
|
self.try_add_hint();
|
||||||
|
}
|
||||||
|
TurnChoice::Play(index) => {
|
||||||
|
let card = self.take_from_hand(index);
|
||||||
|
let mut firework_made = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
let ref mut firework = self.board.fireworks.get_mut(&card.color).unwrap();
|
||||||
|
|
||||||
|
let playable = {
|
||||||
|
let under_card = firework.top().unwrap();
|
||||||
|
card.value == under_card.value + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
if playable {
|
||||||
|
firework_made = card.value == FINAL_VALUE;
|
||||||
|
firework.place(card);
|
||||||
|
} else {
|
||||||
|
self.board.discard.place(card);
|
||||||
|
self.board.lives_remaining -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if firework_made {
|
||||||
|
self.try_add_hint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.board.deck.size() == 0 {
|
||||||
|
self.board.deckless_turns_remaining -= 1;
|
||||||
|
}
|
||||||
|
self.board.turn += 1;
|
||||||
|
self.board.player = (self.board.player + 1) % self.board.num_players;
|
||||||
|
assert_eq!((self.board.turn - 1) % self.board.num_players, self.board.player);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
10
src/main.rs
10
src/main.rs
@ -1,12 +1,14 @@
|
|||||||
extern crate rand;
|
extern crate rand;
|
||||||
|
|
||||||
mod game;
|
mod game;
|
||||||
|
mod strategies;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
game::GameState::new(game::GameOptions {
|
let opts = game::GameOptions {
|
||||||
num_players: 4,
|
num_players: 4,
|
||||||
hand_size: 4,
|
hand_size: 4,
|
||||||
total_hints: 8,
|
num_hints: 8,
|
||||||
total_lives: 3,
|
num_lives: 3,
|
||||||
});
|
};
|
||||||
|
strategies::simulate(opts, strategies::AlwaysPlay);
|
||||||
}
|
}
|
||||||
|
59
src/strategies.rs
Normal file
59
src/strategies.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use game::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// Trait to implement for any valid Hanabi strategy
|
||||||
|
// State management is done by the simulator, to avoid cheating
|
||||||
|
pub trait Strategy {
|
||||||
|
type InternalState;
|
||||||
|
fn initialize(&Player, &GameStateView) -> Self::InternalState;
|
||||||
|
fn decide(&mut Self::InternalState, &Player, &GameStateView) -> TurnChoice;
|
||||||
|
fn update(&mut Self::InternalState, &Turn, &GameStateView);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simulate<S: Strategy>(opts: GameOptions, strategy: S) -> Score {
|
||||||
|
let mut game = GameState::new(opts);
|
||||||
|
|
||||||
|
let mut internal_states : HashMap<Player, S::InternalState> = HashMap::new();
|
||||||
|
for player in game.get_players() {
|
||||||
|
internal_states.insert(
|
||||||
|
player,
|
||||||
|
S::initialize(&player, &game.get_view(player)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
while !game.is_over() {
|
||||||
|
let player = game.board.player;
|
||||||
|
let choice = {
|
||||||
|
let ref mut internal_state = internal_states.get_mut(&player).unwrap();
|
||||||
|
S::decide(internal_state, &player, &game.get_view(player))
|
||||||
|
};
|
||||||
|
println!("Player {:?} decided to {:?}", player, choice);
|
||||||
|
let turn = Turn {
|
||||||
|
player: &player,
|
||||||
|
choice: &choice,
|
||||||
|
};
|
||||||
|
|
||||||
|
for player in game.get_players() {
|
||||||
|
let ref mut internal_state = internal_states.get_mut(&player).unwrap();
|
||||||
|
|
||||||
|
S::update(internal_state, &turn, &game.get_view(player));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do some stuff
|
||||||
|
println!("State: {:?}", game);
|
||||||
|
}
|
||||||
|
game.score()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AlwaysPlay;
|
||||||
|
impl Strategy for AlwaysPlay {
|
||||||
|
type InternalState = ();
|
||||||
|
fn initialize(player: &Player, view: &GameStateView) -> () {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
fn decide(_: &mut (), player: &Player, view: &GameStateView) -> TurnChoice {
|
||||||
|
TurnChoice::Play(0)
|
||||||
|
}
|
||||||
|
fn update(_: &mut (), turn: &Turn, view: &GameStateView) {
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user