othello_agent/simulate/
game.rs

1use crate::{
2    agent::rule_based::RuleAgent,
3    agent::traits::Agent,
4    gameplay::{ constants::INITIAL_BOARD, types::IBoard, utils::board_by_playing_piece_at_index },
5};
6
7use super::history::{ GameHistoryStore, GameHistory };
8
9pub struct OthelloSimulator {
10    agent0: RuleAgent,
11    agent1: RuleAgent,
12    board: IBoard,
13    history_store: GameHistoryStore,
14}
15
16impl OthelloSimulator {
17    pub fn new() -> Self {
18        OthelloSimulator {
19            agent0: RuleAgent::new(0),
20            agent1: RuleAgent::new(1),
21            board: INITIAL_BOARD,
22            history_store: GameHistoryStore::new(),
23        }
24    }
25    pub fn play_game(&mut self) {
26        let mut current_player = 0;
27        let mut current_board: IBoard = self.board;
28        let mut num_skips: u8 = 0;
29        let mut game_history = GameHistory::new();
30        loop {
31            println!("Player {}'s turn", current_player);
32            // game is over if both players skip
33            if num_skips == 2 {
34                // add board to history
35                game_history.add_board(current_board, true);
36                break;
37            } else {
38                // add board to his
39                game_history.add_board(current_board, false);
40            }
41            // get agent based on current player
42            let agent = if current_player == 0 { &self.agent0 } else { &self.agent1 };
43            // get move from agent
44            let move_position = agent.get_move(current_board);
45            // if no move, skip turn
46            if move_position.is_none() {
47                println!("Player {} has no move", current_player);
48                current_player = 1 - current_player;
49                num_skips += 1;
50                continue;
51            }
52            // try to play move... should work as valid moves have already been filtered
53            let new_board: Option<IBoard> = board_by_playing_piece_at_index(
54                current_board,
55                move_position.unwrap(),
56                current_player
57            );
58            if new_board.is_none() {
59                panic!("Invalid move suggested by agent");
60            }
61            // update board and player
62            current_board = new_board.unwrap();
63            current_player = 1 - current_player;
64            num_skips = 0;
65        }
66        // add game history to store
67        self.history_store.add_game(game_history);
68    }
69
70    pub fn play_games(&mut self, num_games: u32) {
71        for _ in 0..num_games {
72            self.play_game();
73        }
74    }
75
76    pub fn print_summary(&self) {
77        self.history_store.print_summary();
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::{
84        simulate::{ game::OthelloSimulator, history::GameHistory },
85        gameplay::constants::DEFAULT_BOARD_WIDTH,
86    };
87
88    #[test]
89    fn test_play_game() {
90        let mut simulator = OthelloSimulator::new();
91        simulator.play_game();
92        // won't see print summary unless test fails
93        simulator.print_summary();
94        assert_eq!(simulator.history_store.total_games, 1);
95        // get last game and ensure it exists
96        let last_game: Option<&GameHistory> = simulator.history_store.last_game();
97        assert_eq!(last_game.is_some(), true);
98        // ensure more moves than zero
99        assert_eq!(last_game.unwrap().total_moves > 0, true);
100        // ensure at least one player scored
101        assert_eq!(
102            last_game.unwrap().agent0_score > 0 || last_game.unwrap().agent1_score > 0,
103            true
104        );
105        // ensure scores add up to DEFAULT_BOARD_WIDTH * DEFAULT_BOARD_HEIGHT
106        assert_eq!(
107            last_game.unwrap().agent0_score + last_game.unwrap().agent1_score,
108            (DEFAULT_BOARD_WIDTH as i16) * (DEFAULT_BOARD_WIDTH as i16)
109        );
110    }
111}