tictactoe-rust-foropenspiel/src/game.rs

220 lines
5.3 KiB
Rust
Raw Normal View History

2025-12-30 17:39:06 +01:00
use std::{fmt, str};
2026-01-02 18:55:44 +01:00
#[cxx::bridge]
mod ffi {
// Rust types and signatures exposed to C++.
extern "Rust" {
type Player;
type GameState;
fn init() -> Box<GameState>;
fn do_action(self: &mut GameState, action: usize);
fn legal_actions(self: &GameState) -> Vec<usize> ;
fn to_string(self: &GameState) -> String;
}
// C++ types and signatures exposed to Rust.
unsafe extern "C++" {
}
}
2025-12-30 17:39:06 +01:00
#[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>,
2026-01-02 18:55:44 +01:00
board: Board,
2025-12-30 17:39:06 +01:00
pub num_moves: usize,
}
2026-01-02 18:55:44 +01:00
// exposed to c++
fn init() -> Box<GameState> {
Box::new(GameState::default())
}
2025-12-30 17:39:06 +01:00
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 {
2026-01-02 18:55:44 +01:00
self.outcome.is_some() || self.is_full()
2025-12-30 17:39:06 +01:00
}
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) {
2026-01-02 18:55:44 +01:00
self.board.set_cell_state(action, CellState::Empty);
2025-12-30 17:39:06 +01:00
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}")
}
}