slowly working

This commit is contained in:
nora 2022-10-31 15:45:56 +01:00
parent b666c4a0cb
commit 43d39fba30
No known key found for this signature in database
7 changed files with 227 additions and 34 deletions

View file

@ -6,6 +6,13 @@ pub enum Player {
O,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum State {
Winner(Player),
InProgress,
Draw,
}
impl Player {
pub fn opponent(self) -> Self {
match self {
@ -23,7 +30,7 @@ impl Player {
})
}
fn as_u8(this: Option<Player>) -> u8 {
pub fn as_u8(this: Option<Player>) -> u8 {
match this {
Some(Player::X) => 0,
Some(Player::O) => 1,
@ -95,28 +102,35 @@ impl Board {
self.validate();
}
pub fn iter(&self) -> impl Iterator<Item = Option<Player>> + '_ {
pub fn iter(&self) -> impl Iterator<Item = Option<Player>> {
let mut i = 0;
let this = self.clone();
std::iter::from_fn(move || {
let result = (i < 8).then(|| self.get(i));
let result = (i < 8).then(|| this.get(i));
i += 1;
result
})
}
pub fn result(&self) -> Option<Player> {
pub fn result(&self) -> State {
win_table::result(self)
}
}
mod win_table {
use super::{Board, Player};
use super::{Board, Player, State};
const WIN_TABLE_SIZE: usize = 2usize.pow(2 * 9);
const WIN_TABLE: &[u8; WIN_TABLE_SIZE] = include_bytes!(concat!(env!("OUT_DIR"), "/win_table"));
pub fn result(board: &Board) -> Option<Player> {
Player::from_u8(WIN_TABLE[board.0 as usize]).unwrap()
pub fn result(board: &Board) -> State {
match WIN_TABLE[board.0 as usize] {
0 => State::Winner(Player::X),
1 => State::Winner(Player::X),
2 => State::InProgress,
3 => State::Draw,
n => panic!("Invalid value {n} in table"),
}
}
}

View file

@ -1,4 +1,7 @@
use crate::{board::Player, Board, GamePlayer};
use crate::{
board::{Player, State},
Board, GamePlayer,
};
impl Board {
pub fn play<A: GamePlayer, B: GamePlayer>(&mut self, a: &mut A, b: &mut B) -> Option<Player> {
@ -11,8 +14,12 @@ impl Board {
b.next_move(self, current_player);
}
if let Some(winner) = self.result() {
return Some(winner);
match self.result() {
State::Winner(player) => return Some(player),
State::Draw => {
return None;
}
State::InProgress => {}
}
current_player = current_player.opponent();

View file

@ -1,16 +1,17 @@
mod board;
mod game;
mod minmax;
use std::io::Write;
use board::Player;
pub use board::Board;
pub use board::{Board, Player, State};
pub use minmax::PerfectPlayer;
pub trait GamePlayer {
fn next_move(&mut self, board: &mut Board, this_player: Player);
}
#[derive(Clone)]
pub struct GreedyPlayer;
impl GamePlayer for GreedyPlayer {
@ -20,6 +21,7 @@ impl GamePlayer for GreedyPlayer {
}
}
#[derive(Clone)]
pub struct HumanPlayer;
impl GamePlayer for HumanPlayer {
@ -48,3 +50,28 @@ impl GamePlayer for HumanPlayer {
}
}
}
#[derive(Clone)]
pub struct RandomPlayer;
fn fun_random() -> u64 {
use std::hash::{BuildHasher, Hasher};
std::collections::hash_map::RandomState::new()
.build_hasher()
.finish()
}
impl GamePlayer for RandomPlayer {
fn next_move(&mut self, board: &mut Board, this_player: Player) {
loop {
let next = (fun_random() % 9) as usize;
match board.get(next) {
Some(_) => {}
None => {
board.set(next, Some(this_player));
return;
}
}
}
}
}

View file

@ -1,13 +1,47 @@
use minmax::{Board, GreedyPlayer};
#![allow(unused_imports)]
use std::time::SystemTime;
use minmax::{Board, GamePlayer, GreedyPlayer, HumanPlayer, PerfectPlayer, Player, RandomPlayer};
fn main() {
let mut results = [0, 0, 0];
let start = SystemTime::now();
for _ in 0..10_000 {
let result = play_round(PerfectPlayer::new(), PerfectPlayer::new(), false);
let idx = Player::as_u8(result);
results[idx as usize] += 1;
}
println!("Winner counts");
println!(" X: {}", results[0]);
println!(" O: {}", results[1]);
println!(" Draw: {}", results[2]);
let time = start.elapsed().unwrap();
println!("Completed in {}ms", time.as_millis());
}
fn play_round<A: GamePlayer, B: GamePlayer>(mut a: A, mut b: B, print: bool) -> Option<Player> {
let mut board = Board::empty();
let result = board.play(&mut GreedyPlayer, &mut GreedyPlayer);
println!("{board}");
let result = board.play(&mut a, &mut b);
if print {
println!("{board}");
}
match result {
Some(winner) => {
println!("player {winner} won!");
if print {
println!("player {winner} won!");
}
}
None => {
if print {
println!("a draw...")
}
}
None => println!("a draw..."),
}
result
}

73
src/minmax.rs Normal file
View file

@ -0,0 +1,73 @@
use crate::{
board::{Player, State},
Board, GamePlayer,
};
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Score(isize);
#[derive(Clone)]
pub struct PerfectPlayer {
best_move: usize,
}
impl PerfectPlayer {
pub fn new() -> Self {
Self {
best_move: usize::MAX,
}
}
fn minmax(&mut self, board: &mut Board, player: Player, depth: usize) -> Score {
match board.result() {
State::Winner(winner) => {
if winner == player {
Score(1)
} else {
Score(-1)
}
}
State::Draw => Score(0),
State::InProgress => {
let mut max_value = Score(isize::MIN);
debug_assert!(
!board.iter().all(|x| x.is_some()),
"the board is full but state is InProgress"
);
for (i, pos) in board.iter().enumerate() {
if pos.is_some() {
continue;
}
board.set(i, Some(player));
let value = self.minmax(board, player.opponent(), depth + 1);
board.set(i, None);
if value > max_value {
max_value = value;
if depth == 0 {
self.best_move = i;
}
}
}
max_value
}
}
}
}
impl GamePlayer for PerfectPlayer {
fn next_move(&mut self, board: &mut Board, this_player: Player) {
self.best_move = usize::MAX;
self.minmax(board, this_player, 0);
if self.best_move == usize::MAX {
panic!("could not make a move");
}
board.set(self.best_move, Some(this_player));
}
}