use core::{
convert::Infallible,
fmt::{Debug, Formatter, Result as FmtResult},
};
use thiserror::Error;
const BOARD_WIDTH: usize = 7;
const BOARD_HEIGHT: usize = 6;
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Game {
columns: [Column; 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("column filled")]
ColumnFilled,
#[error("game ended")]
GameEnded,
}
#[derive(Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct Column {
cells: [Player; BOARD_HEIGHT],
filled: usize,
}
struct LastMove {
player: Player,
row: usize,
col: usize,
}
impl Game {
pub const fn new() -> Result<Self, Infallible> {
Ok(Self {
columns: [Column {
cells: [Player::Player0; BOARD_HEIGHT],
filled: 0,
}; BOARD_WIDTH],
move_count: 0,
next_player: Player::Player0,
status: Status::Ongoing,
})
}
pub const fn get(&self, row: usize, col: usize) -> Option<Player> {
let column = &self.columns[col];
if row >= BOARD_HEIGHT - column.filled {
Some(column.cells[row])
} else {
None
}
}
pub fn put(&mut self, col: usize) -> Result<(), Error> {
if matches!(self.status, Status::Win(_) | Status::Draw) {
return Err(Error::GameEnded);
}
if self.columns[col].filled == BOARD_HEIGHT {
return Err(Error::ColumnFilled);
}
let column = &mut self.columns[col];
let row = BOARD_HEIGHT - 1 - column.filled;
column.cells[row] = self.next_player;
column.filled += 1;
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 row_range = last_move.row.saturating_sub(3)..=(last_move.row + 3).min(BOARD_HEIGHT - 1);
let col_range = last_move.col.saturating_sub(3)..=(last_move.col + 3).min(BOARD_WIDTH - 1);
let mut consecutive_pieces = 0;
for col in col_range.clone() {
if self.get(last_move.row, col) == Some(last_move.player) {
consecutive_pieces += 1;
if consecutive_pieces == 4 {
self.status = Status::Win(last_move.player);
return;
}
} else {
consecutive_pieces = 0;
}
}
consecutive_pieces = 0;
for row in row_range.clone() {
if self.get(row, last_move.col) == Some(last_move.player) {
consecutive_pieces += 1;
if consecutive_pieces == 4 {
self.status = Status::Win(last_move.player);
return;
}
} else {
consecutive_pieces = 0;
}
}
consecutive_pieces = 0;
for (row, col) in row_range.clone().zip(col_range.clone()) {
if self.get(row, col) == Some(last_move.player) {
consecutive_pieces += 1;
if consecutive_pieces == 4 {
self.status = Status::Win(last_move.player);
return;
}
} else {
consecutive_pieces = 0;
}
}
consecutive_pieces = 0;
for (row, col) in row_range.zip(col_range.rev()) {
if self.get(row, col) == Some(last_move.player) {
consecutive_pieces += 1;
if consecutive_pieces == 4 {
self.status = Status::Win(last_move.player);
return;
}
} else {
consecutive_pieces = 0;
}
}
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,
}
}
}
impl Debug for Game {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let mut board = [[None; BOARD_HEIGHT]; BOARD_WIDTH];
for col in 0..BOARD_WIDTH {
let column = &self.columns[col];
for row in 0..column.filled {
board[col][row] = Some(column.cells[row]);
}
}
f.debug_struct("Game")
.field("board", &board)
.field("move_count", &self.move_count)
.field("next_player", &self.next_player)
.field("status", &self.status)
.finish()
}
}
#[cfg(test)]
mod tests {
use crate::connect_four::*;
#[test]
fn test() {
let mut game = Game::new().unwrap();
game.put(3).unwrap();
game.put(2).unwrap();
game.put(2).unwrap();
game.put(1).unwrap();
game.put(1).unwrap();
game.put(0).unwrap();
game.put(3).unwrap();
game.put(0).unwrap();
game.put(1).unwrap();
game.put(6).unwrap();
game.put(2).unwrap();
game.put(6).unwrap();
game.put(3).unwrap();
game.put(5).unwrap();
game.put(0).unwrap();
assert_eq!(game.status(), &Status::Win(Player::Player0));
}
}