random game sampling and basic benchmark
This commit is contained in:
parent
45deb93a4f
commit
310d6b7afa
179
src/game.rs
179
src/game.rs
@ -69,7 +69,7 @@ impl Default for Square {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
squares: [Square; 16],
|
squares: [Square; 16],
|
||||||
dice: EnumMap<Color, bool>,
|
dice: EnumMap<Color, bool>,
|
||||||
@ -77,12 +77,12 @@ pub struct Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
// new game with random starting positions
|
// new game with random starting positions
|
||||||
fn new_random() -> Self {
|
pub fn new_random() -> Self {
|
||||||
let mut game = Self::default();
|
let mut game = Self::default();
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let mut dice = *&COLORS;
|
let mut dice = *&COLORS;
|
||||||
@ -95,20 +95,46 @@ impl Game {
|
|||||||
game
|
game
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_state(&mut self, squares: [Square; 16], dice: EnumMap<Color, bool>) {
|
pub fn set_state(&mut self, camels: &[(Color, usize); 5], dice: &EnumMap<Color, bool>) {
|
||||||
self.squares = squares;
|
for i in 0..16 {
|
||||||
self.dice = dice;
|
self.squares[i] = match self.squares[i] {
|
||||||
for (i, square) in self.squares.iter().enumerate() {
|
Square::Camels(mut stack) => {
|
||||||
|
stack.clear();
|
||||||
|
Square::Camels(stack)
|
||||||
|
},
|
||||||
|
_ => Square::Camels(Stack::new())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for square in self.squares {
|
||||||
|
assert_eq!(square.assume_stack().len(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dice = *dice;
|
||||||
|
for &(color, sq) in camels {
|
||||||
|
self.squares[sq].assume_stack_mut().push(color);
|
||||||
|
self.camels[color] = sq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_state(&self) -> ([(Color, usize); 5], EnumMap<Color, bool>) {
|
||||||
|
let mut state = [(Color::Red, 0); 5];
|
||||||
|
|
||||||
|
let mut j = 0;
|
||||||
|
for (sq_idx, square) in self.squares.iter().enumerate() {
|
||||||
if let Square::Camels(stack) = square {
|
if let Square::Camels(stack) = square {
|
||||||
for camel in stack.iter() {
|
for camel in stack.iter() {
|
||||||
self.camels[*camel] = i
|
state[j] = (*camel, sq_idx);
|
||||||
}
|
j += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(state, self.dice)
|
||||||
|
}
|
||||||
|
|
||||||
// returns winner if there is one
|
// returns winner if there is one
|
||||||
fn advance(&mut self, die: Color, roll: usize) -> Option<Color> {
|
pub fn advance(&mut self, die: Color, roll: usize) -> Option<Color> {
|
||||||
let src_sq = self.camels[die];
|
let src_sq = self.camels[die];
|
||||||
let dst_sq = src_sq + roll;
|
let dst_sq = src_sq + roll;
|
||||||
if dst_sq >= 16 {
|
if dst_sq >= 16 {
|
||||||
@ -171,7 +197,6 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(&mut leg_dice[..]).shuffle(&mut rng);
|
(&mut leg_dice[..]).shuffle(&mut rng);
|
||||||
|
|
||||||
for color in leg_dice.iter() {
|
for color in leg_dice.iter() {
|
||||||
let roll = rng.gen_range(1..=3);
|
let roll = rng.gen_range(1..=3);
|
||||||
if let Some(winner) = self.advance(*color, roll) {
|
if let Some(winner) = self.advance(*color, roll) {
|
||||||
@ -189,8 +214,8 @@ impl Game {
|
|||||||
// we are now guaranteed to be at the start of a new leg,
|
// we are now guaranteed to be at the start of a new leg,
|
||||||
// so we don't need to check the dice state
|
// so we don't need to check the dice state
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let roll_dist = Uniform::from(0..=3);
|
let roll_dist = Uniform::from(1..=3);
|
||||||
let mut dice = *&COLORS; // makes a copy of the constant
|
let mut dice = COLORS; // makes a copy of the constant
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// easiest if we shuffle at the start of the leg
|
// easiest if we shuffle at the start of the leg
|
||||||
@ -203,34 +228,53 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn project_outcomes(&self, count: usize) -> EnumMap<Color, usize> {
|
||||||
|
let (orig_camels, orig_dice) = self.get_state();
|
||||||
|
let mut projection = *self;
|
||||||
|
|
||||||
|
let mut scores: EnumMap<Color, usize> = EnumMap::default();
|
||||||
|
for i in 0..count {
|
||||||
|
let winner = projection.finish_game_random();
|
||||||
|
scores[winner] += 1;
|
||||||
|
projection.set_state(&orig_camels, &orig_dice);
|
||||||
|
}
|
||||||
|
|
||||||
|
scores
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use Color::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_advance() {
|
fn test_advance() {
|
||||||
use Color::*;
|
|
||||||
let mut game = Game::new();
|
let mut game = Game::new();
|
||||||
// all dice are false (not rolled) to start with
|
// all dice are false (not rolled) to start with
|
||||||
assert_eq!(game.dice.values().any(|&v| v), false);
|
assert_eq!(game.dice.values().any(|&v| v), false);
|
||||||
|
|
||||||
let mut squares = [Square::Camels(Default::default()); 16];
|
let camel_state = [
|
||||||
let one = squares[0].assume_stack_mut();
|
(Blue, 0),
|
||||||
one.push(Blue);
|
(Yellow, 0),
|
||||||
one.push(Yellow);
|
(Red, 1),
|
||||||
squares[1].assume_stack_mut().push(Red);
|
(Green, 2),
|
||||||
let three = squares[2].assume_stack_mut();
|
(Purple, 2),
|
||||||
three.push(Green);
|
];
|
||||||
three.push(Purple);
|
game.set_state(&camel_state, &Default::default());
|
||||||
|
assert_eq!(game.squares[0].assume_stack(), &Stack::from([Blue, Yellow]));
|
||||||
|
assert_eq!(game.camels[Blue], 0);
|
||||||
|
assert_eq!(game.camels[Yellow], 0);
|
||||||
|
assert_eq!(game.squares[1].assume_stack(), &Stack::from([Red]));
|
||||||
|
assert_eq!(game.camels[Red], 1);
|
||||||
|
assert_eq!(game.squares[2].assume_stack(), &Stack::from([Green, Purple]));
|
||||||
|
assert_eq!(game.camels[Green], 2);
|
||||||
|
assert_eq!(game.camels[Purple], 2);
|
||||||
// BY, R, GP
|
// BY, R, GP
|
||||||
|
|
||||||
game.set_state(squares, Default::default());
|
|
||||||
|
|
||||||
game.advance(Yellow, 2);
|
game.advance(Yellow, 2);
|
||||||
println!("{:?}", game.camels);
|
|
||||||
assert_eq!(game.dice[Yellow], true);
|
assert_eq!(game.dice[Yellow], true);
|
||||||
assert_eq!(game.camels[Yellow], 2);
|
assert_eq!(game.camels[Yellow], 2);
|
||||||
assert_eq!(game.squares[2].assume_stack(), &Stack::from([Green, Purple, Yellow]));
|
assert_eq!(game.squares[2].assume_stack(), &Stack::from([Green, Purple, Yellow]));
|
||||||
@ -247,5 +291,90 @@ mod test {
|
|||||||
// B, _, G, RPY
|
// B, _, G, RPY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_random() {
|
||||||
|
for _ in 0..100 {
|
||||||
|
let game = Game::new_random();
|
||||||
|
for (camel, i) in game.camels {
|
||||||
|
assert!(i < 3); // since we've only rolled the die once for each camel
|
||||||
|
let stack = game.squares[i].assume_stack();
|
||||||
|
assert!(stack[..].contains(&camel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_finish_leg() {
|
||||||
|
let mut game = Game::new();
|
||||||
|
let camel_state = [
|
||||||
|
(Purple, 0),
|
||||||
|
(Blue, 0),
|
||||||
|
(Green, 1),
|
||||||
|
(Red, 1),
|
||||||
|
(Yellow, 2),
|
||||||
|
];
|
||||||
|
game.set_state(&camel_state, &Default::default());
|
||||||
|
// PB, G, RY
|
||||||
|
game.advance(Green, 2);
|
||||||
|
// PB, _, RY, G
|
||||||
|
game.advance(Purple, 1);
|
||||||
|
// _, PB, RY, G
|
||||||
|
|
||||||
|
// since this is randomized, we should do it a bunch of times to make sure
|
||||||
|
for _ in 0..100 {
|
||||||
|
let mut projection = game; // copy?
|
||||||
|
assert_eq!(projection.squares[1].assume_stack(), &Stack::from([Purple, Blue]));
|
||||||
|
projection.finish_leg_random();
|
||||||
|
// since we already rolled Green, it can't have moved
|
||||||
|
assert_eq!(projection.camels[Green], 3);
|
||||||
|
// likewise purple
|
||||||
|
assert_eq!(projection.camels[Purple], 1);
|
||||||
|
// blue, red,and yellow, on the other hand, *must* have moved
|
||||||
|
assert_ne!(projection.camels[Blue], 1);
|
||||||
|
assert_ne!(projection.camels[Red], 2);
|
||||||
|
assert_ne!(projection.camels[Yellow], 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_finish_leg_winner() {
|
||||||
|
let mut game = Game::new();
|
||||||
|
let camel_state = [
|
||||||
|
(Green, 13),
|
||||||
|
(Red, 14),
|
||||||
|
(Purple, 14),
|
||||||
|
(Blue, 15),
|
||||||
|
(Yellow, 15),
|
||||||
|
];
|
||||||
|
game.set_state(&camel_state, &Default::default());
|
||||||
|
|
||||||
|
// since there are no tiles involved, and multiple camels are on 15, there must be a winner
|
||||||
|
assert!(matches!(game.finish_leg_random(), Some(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_outcomes() {
|
||||||
|
let mut game = Game::new();
|
||||||
|
let camel_state = [
|
||||||
|
(Blue, 1),
|
||||||
|
(Green, 2),
|
||||||
|
(Yellow, 2),
|
||||||
|
(Purple, 4),
|
||||||
|
(Red, 10),
|
||||||
|
];
|
||||||
|
game.set_state(&camel_state, &Default::default());
|
||||||
|
// _, B, GY, _, P, _, _, _, _, _, R
|
||||||
|
|
||||||
|
let scores = game.project_outcomes(10_000);
|
||||||
|
let mut max = 0;
|
||||||
|
let mut winner = Blue; // just "anything that's not red"
|
||||||
|
for (color, score) in scores {
|
||||||
|
if score > max {
|
||||||
|
max = score;
|
||||||
|
winner = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(winner, Red);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
21
src/main.rs
21
src/main.rs
@ -1,7 +1,26 @@
|
|||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
mod stack;
|
mod stack;
|
||||||
mod game;
|
mod game;
|
||||||
|
|
||||||
|
use game::Game;
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello, world!");
|
let n_games = 10_000_000;
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let game = Game::new_random();
|
||||||
|
let _scores = game.project_outcomes(n_games);
|
||||||
|
let end = Instant::now();
|
||||||
|
|
||||||
|
let elapsed = end.duration_since(start);
|
||||||
|
let secs = elapsed.as_secs();
|
||||||
|
let hundredths = elapsed.subsec_millis() / 10; // technically not accurate but good enough for now
|
||||||
|
|
||||||
|
let rate = (10_000_000 as f64) / elapsed.as_secs_f64();
|
||||||
|
|
||||||
|
println!("Test completed:");
|
||||||
|
println!("{n_games} in {secs}.{hundredths:02} seconds", );
|
||||||
|
println!("Games per second: {rate}");
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,10 @@ impl<T, const S: usize> Stack<T, S> {
|
|||||||
len: S,
|
len: S,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> [T; S] {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user