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,3 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
[profile.dev]
opt-level = 3

View file

@ -1,11 +1,18 @@
use std::{fs::File, io::Write, path::PathBuf}; use std::{fs::File, io::Write, path::PathBuf};
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Player { enum Player {
X, X,
O, O,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
Winner(Player),
InProgress,
Draw,
}
impl Player { impl Player {
fn from_u8(num: u8) -> Option<Self> { fn from_u8(num: u8) -> Option<Self> {
match num { match num {
@ -15,14 +22,6 @@ impl Player {
_ => panic!("Invalid value {num}"), _ => panic!("Invalid value {num}"),
} }
} }
fn as_u8(this: Option<Player>) -> u8 {
match this {
Some(Player::X) => 0,
Some(Player::O) => 1,
None => 2,
}
}
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -42,16 +41,42 @@ impl Board {
Some(Self(num)) Some(Self(num))
} }
fn get(&self, index: usize) -> Option<Player> { fn validate(&self) {
let board = self.0;
for i in 0..16 {
let next_step = board >> (i * 2);
let mask = 0b11;
let pos = next_step & mask;
if pos >= 3 {
panic!("Invalid bits, self: {board:0X}, bits: {pos:0X}");
}
}
}
pub fn get(&self, index: usize) -> Option<Player> {
self.validate();
debug_assert!(index < 9); debug_assert!(index < 9);
let shifted = self.0 >> (index * 2); let board = self.0;
let shifted = board >> (index * 2);
let masked = shifted & 0b11; let masked = shifted & 0b11;
Player::from_u8(masked as u8) Player::from_u8(masked as u8)
} }
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(|| this.get(i));
i += 1;
result
})
}
} }
fn winner(board: Board) -> Option<Player> { fn result(board: Board) -> State {
fn won_row(a: Option<Player>, b: Option<Player>, c: Option<Player>) -> Option<Player> { fn won_row(a: Option<Player>, b: Option<Player>, c: Option<Player>) -> Option<Player> {
if a == Some(Player::X) && b == Some(Player::X) && c == Some(Player::X) { if a == Some(Player::X) && b == Some(Player::X) && c == Some(Player::X) {
Some(Player::X) Some(Player::X)
@ -65,12 +90,16 @@ fn winner(board: Board) -> Option<Player> {
macro_rules! test_row { macro_rules! test_row {
($a:literal, $b:literal, $c:literal) => { ($a:literal, $b:literal, $c:literal) => {
match won_row(board.get($a), board.get($b), board.get($c)) { match won_row(board.get($a), board.get($b), board.get($c)) {
Some(player) => return Some(player), Some(player) => return State::Winner(player),
None => {} None => {}
} }
}; };
} }
if board.iter().all(|x| x.is_some()) {
return State::Draw;
}
test_row!(0, 1, 2); test_row!(0, 1, 2);
test_row!(3, 4, 5); test_row!(3, 4, 5);
test_row!(6, 7, 8); test_row!(6, 7, 8);
@ -81,15 +110,20 @@ fn winner(board: Board) -> Option<Player> {
test_row!(0, 4, 8); test_row!(0, 4, 8);
test_row!(2, 4, 6); test_row!(2, 4, 6);
None State::InProgress
} }
fn calculate_win_table(file: &mut impl Write) { fn calculate_win_table(file: &mut impl Write) {
for board in 0..(2u32.pow(18)) { for board in 0..(2u32.pow(18)) {
let byte = match Board::new(board) { let byte = match Board::new(board) {
Some(board) => { Some(board) => {
let winner = winner(board); let winner = result(board);
Player::as_u8(winner) match winner {
State::Winner(Player::X) => 0,
State::Winner(Player::O) => 1,
State::InProgress => 2,
State::Draw => 3,
}
} }
None => 0, None => 0,
}; };

View file

@ -6,6 +6,13 @@ pub enum Player {
O, O,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum State {
Winner(Player),
InProgress,
Draw,
}
impl Player { impl Player {
pub fn opponent(self) -> Self { pub fn opponent(self) -> Self {
match 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 { match this {
Some(Player::X) => 0, Some(Player::X) => 0,
Some(Player::O) => 1, Some(Player::O) => 1,
@ -95,28 +102,35 @@ impl Board {
self.validate(); 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 mut i = 0;
let this = self.clone();
std::iter::from_fn(move || { std::iter::from_fn(move || {
let result = (i < 8).then(|| self.get(i)); let result = (i < 8).then(|| this.get(i));
i += 1; i += 1;
result result
}) })
} }
pub fn result(&self) -> Option<Player> { pub fn result(&self) -> State {
win_table::result(self) win_table::result(self)
} }
} }
mod win_table { mod win_table {
use super::{Board, Player}; use super::{Board, Player, State};
const WIN_TABLE_SIZE: usize = 2usize.pow(2 * 9); const WIN_TABLE_SIZE: usize = 2usize.pow(2 * 9);
const WIN_TABLE: &[u8; WIN_TABLE_SIZE] = include_bytes!(concat!(env!("OUT_DIR"), "/win_table")); const WIN_TABLE: &[u8; WIN_TABLE_SIZE] = include_bytes!(concat!(env!("OUT_DIR"), "/win_table"));
pub fn result(board: &Board) -> Option<Player> { pub fn result(board: &Board) -> State {
Player::from_u8(WIN_TABLE[board.0 as usize]).unwrap() 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 { impl Board {
pub fn play<A: GamePlayer, B: GamePlayer>(&mut self, a: &mut A, b: &mut B) -> Option<Player> { 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); b.next_move(self, current_player);
} }
if let Some(winner) = self.result() { match self.result() {
return Some(winner); State::Winner(player) => return Some(player),
State::Draw => {
return None;
}
State::InProgress => {}
} }
current_player = current_player.opponent(); current_player = current_player.opponent();

View file

@ -1,16 +1,17 @@
mod board; mod board;
mod game; mod game;
mod minmax;
use std::io::Write; use std::io::Write;
use board::Player; pub use board::{Board, Player, State};
pub use minmax::PerfectPlayer;
pub use board::Board;
pub trait GamePlayer { pub trait GamePlayer {
fn next_move(&mut self, board: &mut Board, this_player: Player); fn next_move(&mut self, board: &mut Board, this_player: Player);
} }
#[derive(Clone)]
pub struct GreedyPlayer; pub struct GreedyPlayer;
impl GamePlayer for GreedyPlayer { impl GamePlayer for GreedyPlayer {
@ -20,6 +21,7 @@ impl GamePlayer for GreedyPlayer {
} }
} }
#[derive(Clone)]
pub struct HumanPlayer; pub struct HumanPlayer;
impl GamePlayer for 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() { 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 mut board = Board::empty();
let result = board.play(&mut GreedyPlayer, &mut GreedyPlayer); let result = board.play(&mut a, &mut b);
if print {
println!("{board}"); println!("{board}");
}
match result { match result {
Some(winner) => { Some(winner) => {
if print {
println!("player {winner} won!"); println!("player {winner} won!");
} }
None => println!("a draw..."), }
None => {
if print {
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));
}
}