use core::convert::Infallible;
use thiserror::Error;
const BOARD_WIDTH: usize = 15;
const BOARD_HEIGHT: usize = 15;
#[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) {
let checking_row_range =
last_move.row.saturating_sub(4)..=(last_move.row + 4).min(BOARD_HEIGHT - 1);
let checking_col_range =
last_move.col.saturating_sub(4)..=(last_move.col + 4).min(BOARD_WIDTH - 1);
let mut consecutive_pieces = 0;
for col in checking_col_range.clone() {
if self.get(last_move.row, col) == Some(last_move.player) {
consecutive_pieces += 1;
if consecutive_pieces == 5 {
self.status = Status::Win(last_move.player);
return;
}
}
}
consecutive_pieces = 0;
for row in checking_row_range.clone() {
if self.get(row, last_move.col) == Some(last_move.player) {
consecutive_pieces += 1;
if consecutive_pieces == 5 {
self.status = Status::Win(last_move.player);
return;
}
}
}
consecutive_pieces = 0;
for (row, col) in checking_row_range.clone().zip(checking_col_range.clone()) {
if self.get(row, col) == Some(last_move.player) {
consecutive_pieces += 1;
if consecutive_pieces == 5 {
self.status = Status::Win(last_move.player);
return;
}
}
}
consecutive_pieces = 0;
for (row, col) in checking_row_range.zip(checking_col_range.rev()) {
if self.get(row, col) == Some(last_move.player) {
consecutive_pieces += 1;
if consecutive_pieces == 5 {
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,
}
}
}