use core::convert::Infallible;
use thiserror::Error;
const BOARD_WIDTH: usize = 3;
const BOARD_HEIGHT: usize = 3;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Game {
board: [[Option<Player>; BOARD_HEIGHT]; BOARD_WIDTH],
move_count: usize,
next_player: Player,
status: Status,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Player {
Player0,
Player1,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Status {
Ongoing,
Draw,
Win(Player),
}
#[derive(Debug, Error)]
pub enum Error {
#[error("position occupied")]
PositionOccupied,
#[error("game ended")]
GameEnded,
}
struct LastMove {
player: Player,
row: usize,
col: usize,
}
impl Game {
pub const fn new() -> Result<Self, Infallible> {
Ok(Self {
board: [[None; BOARD_HEIGHT]; BOARD_WIDTH],
move_count: 0,
next_player: Player::Player0,
status: Status::Ongoing,
})
}
pub const fn get(&self, row: usize, col: usize) -> Option<Player> {
self.board[row][col]
}
pub fn put(&mut self, row: usize, col: usize) -> Result<(), Error> {
if matches!(self.status, Status::Win(_) | Status::Draw) {
return Err(Error::GameEnded);
}
if self.board[row][col].is_some() {
return Err(Error::PositionOccupied);
}
self.board[row][col] = Some(self.next_player);
let last_move = LastMove {
player: self.next_player,
row,
col,
};
self.move_count += 1;
self.next_player = self.next_player.other();
self.update_status(last_move);
Ok(())
}
pub const fn next_player(&self) -> Player {
self.next_player
}
pub const fn status(&self) -> &Status {
&self.status
}
fn update_status(&mut self, last_move: LastMove) {
if self.get(last_move.row, 0) == self.get(last_move.row, 1)
&& self.get(last_move.row, 1) == self.get(last_move.row, 2)
{
self.status = Status::Win(last_move.player);
return;
}
if self.get(0, last_move.col) == self.get(1, last_move.col)
&& self.get(1, last_move.col) == self.get(2, last_move.col)
{
self.status = Status::Win(last_move.player);
return;
}
if !((last_move.row == 1) ^ (last_move.col == 1)) {
if self.get(0, 0) == self.get(1, 1) && self.get(1, 1) == self.get(2, 2) {
self.status = Status::Win(last_move.player);
return;
}
if self.get(0, 2) == self.get(1, 1) && self.get(1, 1) == self.get(2, 0) {
self.status = Status::Win(last_move.player);
return;
}
}
if self.move_count == BOARD_HEIGHT * BOARD_WIDTH {
self.status = Status::Draw;
}
}
}
impl Player {
pub const fn other(self) -> Self {
match self {
Player::Player0 => Player::Player1,
Player::Player1 => Player::Player0,
}
}
}
#[cfg(test)]
mod tests {
use crate::tictactoe::*;
#[test]
fn test() {
let mut game = Game::new().unwrap();
game.put(1, 1).unwrap();
assert_eq!(game.next_player(), Player::Player1);
game.put(1, 0).unwrap();
assert_eq!(game.next_player(), Player::Player0);
assert!(matches!(game.put(1, 1), Err(Error::PositionOccupied)));
game.put(2, 2).unwrap();
game.put(2, 0).unwrap();
game.put(0, 0).unwrap();
assert_eq!(game.status(), &Status::Win(Player::Player0));
assert!(matches!(game.put(0, 2), Err(Error::GameEnded)));
}
}