random game sampling and basic benchmark
This commit is contained in:
		
							
								
								
									
										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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user