use crate::bits::BitBoard; use pyo3::prelude::*; use std::cmp::Ordering::*; const PLAYERS: [char; 2] = ['B', 'W']; #[pyclass] #[derive(Clone)] pub struct Game { pub board: BitBoard, pub current_player: usize, // 0 or 1 #[pyo3(get)] pub state: State, } #[pyclass(get_all)] #[derive(Clone, Debug, PartialEq)] pub struct State { pub player: char, pub ended: bool, pub black_score: i32, pub white_score: i32, pub cells: Vec, pub can_move: bool, pub last_move: i32, } #[pymethods] impl State { fn __repr__(&self) -> String { format!("{:?}", self) } } #[pymethods] impl Game { #[staticmethod] pub fn default() -> Self { let board = BitBoard::default(); let state = Self::compute_state(&board, 0, -1); Self { board, current_player: 0, state, } } pub fn available_moves(&self) -> Vec { let mask = self.board.available_moves(); (0..64).filter(|i| mask >> i & 1 == 1).collect() } pub fn pass_move(&mut self) -> State { self.board = self.board.pass_move(); self.current_player = 1 - self.current_player; self.state = Self::compute_state(&self.board, self.current_player, -1); self.state.clone() } pub fn make_move(&mut self, place: usize) -> State { let next = self.board.make_move(place).unwrap(); self.current_player = 1 - self.current_player; self.board = next; self.state = Self::compute_state(&self.board, self.current_player, place as i32); self.state.clone() } pub fn __repr__(&self) -> String { let state = &self.state; let mut s = String::new(); for (i, &c) in state.cells.iter().enumerate() { if i % 8 == 0 { s.push('\n') } s.push(c); } if state.ended { s.push_str(match state.black_score.cmp(&state.white_score) { Equal => "\nGame draw!", Less => "\nWhite won!", Greater => "\nBlack won!", }); } else { s.push_str(&format!( "\n{} to play. Available moves: {:?}", PLAYERS[self.current_player], self.available_moves() )); } s } } impl Game { fn compute_state(board: &BitBoard, current_player: usize, last_move: i32) -> State { let (cnt0, cnt1) = board.count(); let moves = board.available_moves(); let cells: Vec<_> = (0..64) .map( |i| match (board.0 >> i & 1, board.1 >> i & 1, moves >> i & 1) { (1, 0, 0) => PLAYERS[current_player], (0, 1, 0) => PLAYERS[1 - current_player], (0, 0, 1) => '?', (0, 0, 0) => '.', (_, _, _) => unreachable!(), }, ) .collect(); let ended = moves == 0 && board.pass_move().available_moves() == 0; let player = PLAYERS[current_player]; State { player, ended, black_score: if player == 'B' { cnt0 } else { cnt1 }, white_score: if player == 'W' { cnt0 } else { cnt1 }, cells, can_move: moves != 0, last_move, } } } #[cfg(test)] mod tests { use super::Game; use crate::bits::BitBoard; #[test] fn test_1() { let mut b = Game::default(); assert_eq!(b.available_moves(), &[19, 26, 37, 44]); b.make_move(44); // assert_eq!(b.make_move(44), new(44, 'B', vec![36], 4, 1)); assert_eq!(b.current_player, 1); assert_eq!(b.available_moves(), &[29, 43, 45]); b.make_move(29); // assert_eq!(b.make_move(29), new(29, 'W', vec![28], 3, 3)); assert_eq!(b.current_player, 0); assert_eq!(b.__repr__(), "\n........\n........\n..?????.\n...WWW..\n...BB...\n....B...\n........\n........\nB to play. Available moves: [18, 19, 20, 21, 22]"); } #[test] fn test_2() { let b = BitBoard(2, 1); let mut g = Game { current_player: 0, state: Game::compute_state(&b, 0, -1), board: b, }; assert_eq!(g.state.can_move, false); g.pass_move(); assert_eq!(g.state.can_move, true); g.make_move(2); assert_eq!(g.state.ended, true); assert_eq!(g.state.white_score, 3); } }