mirror of
https://github.com/Noratrieb/minmax.git
synced 2026-01-14 23:35:04 +01:00
slowly working
This commit is contained in:
parent
b666c4a0cb
commit
43d39fba30
7 changed files with 227 additions and 34 deletions
28
src/board.rs
28
src/board.rs
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
13
src/game.rs
13
src/game.rs
|
|
@ -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();
|
||||
|
|
|
|||
33
src/lib.rs
33
src/lib.rs
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
src/main.rs
44
src/main.rs
|
|
@ -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
73
src/minmax.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue