199 lines
4.9 KiB
Rust
199 lines
4.9 KiB
Rust
|
|
use std::{fmt, str};
|
||
|
|
|
||
|
|
// ------------- Player
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
|
|
pub enum Player {
|
||
|
|
Player0,
|
||
|
|
Player1,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl From<u8> for Player {
|
||
|
|
fn from(item: u8) -> Self {
|
||
|
|
match item {
|
||
|
|
0 => Player::Player0,
|
||
|
|
1 => Player::Player1,
|
||
|
|
_ => Player::Player0,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl From<Player> for u8 {
|
||
|
|
fn from(player: Player) -> u8 {
|
||
|
|
match player {
|
||
|
|
Player::Player0 => 0,
|
||
|
|
Player::Player1 => 1,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl fmt::Display for Player {
|
||
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
|
|
let repr = match self {
|
||
|
|
Player::Player0 => "x",
|
||
|
|
Player::Player1 => "o",
|
||
|
|
};
|
||
|
|
let mut s = String::new();
|
||
|
|
s.push_str(repr);
|
||
|
|
write!(f, "{s}")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ------------- CellState
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
|
|
pub enum CellState {
|
||
|
|
Cross,
|
||
|
|
Nought,
|
||
|
|
Empty,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl From<Player> for CellState {
|
||
|
|
fn from(player: Player) -> CellState {
|
||
|
|
match player {
|
||
|
|
Player::Player0 => CellState::Cross,
|
||
|
|
Player::Player1 => CellState::Nought,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl fmt::Display for CellState {
|
||
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
|
|
let repr = match self {
|
||
|
|
CellState::Cross => "x",
|
||
|
|
CellState::Nought => "o",
|
||
|
|
CellState::Empty => ".",
|
||
|
|
};
|
||
|
|
let mut s = String::new();
|
||
|
|
s.push_str(repr);
|
||
|
|
write!(f, "{s}")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ------------ Board
|
||
|
|
|
||
|
|
const NUM_CELLS: usize = 9;
|
||
|
|
const NUM_ROWS: usize = 3;
|
||
|
|
const NUM_COLS: usize = 3;
|
||
|
|
|
||
|
|
struct Board {
|
||
|
|
cells: Vec<CellState>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Default for Board {
|
||
|
|
fn default() -> Self {
|
||
|
|
Board {
|
||
|
|
cells: vec![CellState::Empty; NUM_CELLS],
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Board {
|
||
|
|
fn has_line(&self, player: Player) -> bool {
|
||
|
|
let c: CellState = player.into();
|
||
|
|
(self.cells[0] == c && self.cells[1] == c && self.cells[2] == c)
|
||
|
|
|| (self.cells[3] == c && self.cells[4] == c && self.cells[5] == c)
|
||
|
|
|| (self.cells[6] == c && self.cells[7] == c && self.cells[8] == c)
|
||
|
|
|| (self.cells[0] == c && self.cells[3] == c && self.cells[6] == c)
|
||
|
|
|| (self.cells[1] == c && self.cells[4] == c && self.cells[7] == c)
|
||
|
|
|| (self.cells[2] == c && self.cells[5] == c && self.cells[8] == c)
|
||
|
|
|| (self.cells[0] == c && self.cells[4] == c && self.cells[8] == c)
|
||
|
|
|| (self.cells[2] == c && self.cells[4] == c && self.cells[6] == c)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn set_cell_state(&mut self, position: usize, state: CellState) {
|
||
|
|
self.cells[position] = state;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
fn cell_at(&self, row: usize, col: usize) -> CellState {
|
||
|
|
self.cells[row * NUM_COLS + col]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// -------------- GameState
|
||
|
|
|
||
|
|
type Action = usize;
|
||
|
|
|
||
|
|
pub struct GameState {
|
||
|
|
pub current_player: Option<Player>,
|
||
|
|
pub outcome: Option<Player>,
|
||
|
|
pub board: Board,
|
||
|
|
pub num_moves: usize,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Default for GameState {
|
||
|
|
fn default() -> Self {
|
||
|
|
GameState {
|
||
|
|
current_player: Some(Player::Player0),
|
||
|
|
outcome: None,
|
||
|
|
board: Board::default(),
|
||
|
|
num_moves: 0,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl GameState {
|
||
|
|
fn is_terminal(&self) -> bool {
|
||
|
|
return self.outcome != None || self.is_full();
|
||
|
|
}
|
||
|
|
|
||
|
|
fn is_full(&self) -> bool {
|
||
|
|
self.num_moves == NUM_CELLS
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn do_action(&mut self, action: Action) {
|
||
|
|
if self.board.cells[action] != CellState::Empty {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if let Some(player) = self.current_player {
|
||
|
|
self.board.cells[action] = player.into();
|
||
|
|
if self.board.has_line(player) {
|
||
|
|
self.outcome = self.current_player;
|
||
|
|
}
|
||
|
|
self.change_player();
|
||
|
|
self.num_moves += 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn undo_action(&mut self, player: Player, action: Action) {
|
||
|
|
self.board.set_cell_state(action.into(), CellState::Empty);
|
||
|
|
self.current_player = Some(player);
|
||
|
|
self.outcome = None;
|
||
|
|
self.num_moves -= 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn legal_actions(&self) -> Vec<Action> {
|
||
|
|
// if (self.is_terminal()) return vec![];
|
||
|
|
// Can move in any empty cell.
|
||
|
|
self.board
|
||
|
|
.cells
|
||
|
|
.iter()
|
||
|
|
.enumerate()
|
||
|
|
.filter(|(_, cs)| **cs == CellState::Empty)
|
||
|
|
.map(|(pos, _)| pos)
|
||
|
|
.collect()
|
||
|
|
}
|
||
|
|
|
||
|
|
fn change_player(&mut self) {
|
||
|
|
self.current_player = if self.current_player == Some(Player::Player0) {
|
||
|
|
Some(Player::Player1)
|
||
|
|
} else {
|
||
|
|
Some(Player::Player0)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl fmt::Display for GameState {
|
||
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
|
|
let mut s = String::new();
|
||
|
|
for row in 0..NUM_ROWS {
|
||
|
|
for col in 0..NUM_COLS {
|
||
|
|
s.push_str(self.board.cell_at(row, col).to_string().as_str());
|
||
|
|
}
|
||
|
|
s.push('\n');
|
||
|
|
}
|
||
|
|
write!(f, "{s}")
|
||
|
|
}
|
||
|
|
}
|