mirror of
https://github.com/Noratrieb/minmax.git
synced 2026-01-16 08:15:04 +01:00
AAAAAAAAAAAAAAAAAAAAAAAAa
This commit is contained in:
parent
7f3a0e5ad2
commit
d3d7011c43
13 changed files with 280 additions and 88 deletions
BIN
first
BIN
first
Binary file not shown.
BIN
second
BIN
second
Binary file not shown.
|
|
@ -1,6 +1,9 @@
|
||||||
use std::ops::Index;
|
use std::{
|
||||||
|
fmt::{Display, Write},
|
||||||
|
ops::{Index, IndexMut},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{Player, State};
|
use crate::{minmax::Score, Game, GamePlayer, Player, State};
|
||||||
|
|
||||||
type Position = Option<Player>;
|
type Position = Option<Player>;
|
||||||
|
|
||||||
|
|
@ -12,11 +15,12 @@ const BOARD_POSITIONS: usize = WIDTH * HEIGTH;
|
||||||
/// 7 8 9 10 11 12 13
|
/// 7 8 9 10 11 12 13
|
||||||
/// 14 15 16 17 18 19 20
|
/// 14 15 16 17 18 19 20
|
||||||
/// 21 22 23 24 25 26 27
|
/// 21 22 23 24 25 26 27
|
||||||
pub struct Board {
|
#[derive(Clone)]
|
||||||
|
pub struct Connect4 {
|
||||||
positions: [Position; BOARD_POSITIONS],
|
positions: [Position; BOARD_POSITIONS],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl Connect4 {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
positions: [None; BOARD_POSITIONS],
|
positions: [None; BOARD_POSITIONS],
|
||||||
|
|
@ -86,9 +90,30 @@ impl Board {
|
||||||
})
|
})
|
||||||
.unwrap_or(State::InProgress)
|
.unwrap_or(State::InProgress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rate(&self, player: Player) -> Score {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const WIN_COUNT_TABLE: [i32; BOARD_POSITIONS] = [
|
||||||
|
3, 4, 6, 7, 6, 4, 3,
|
||||||
|
2, 4, 6, 7, 6, 4, 2,
|
||||||
|
2, 4, 6, 7, 6, 4, 2,
|
||||||
|
3, 4, 6, 7, 6, 4, 2,
|
||||||
|
];
|
||||||
|
|
||||||
|
let score_player = |player: Player| {
|
||||||
|
self.positions
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, state)| **state == Some(player))
|
||||||
|
.map(|(pos, _)| WIN_COUNT_TABLE[pos])
|
||||||
|
.sum::<i32>()
|
||||||
|
};
|
||||||
|
|
||||||
|
Score::new(score_player(player) - score_player(player.opponent()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<usize> for Board {
|
impl Index<usize> for Connect4 {
|
||||||
type Output = Position;
|
type Output = Position;
|
||||||
|
|
||||||
fn index(&self, index: usize) -> &Self::Output {
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
|
@ -96,13 +121,87 @@ impl Index<usize> for Board {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IndexMut<usize> for Connect4 {
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
&mut self.positions[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Game for Connect4 {
|
||||||
|
type Move = usize;
|
||||||
|
|
||||||
|
const REASONABLE_SEARCH_DEPTH: Option<usize> = Some(5);
|
||||||
|
|
||||||
|
fn empty() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn possible_moves(&self) -> impl Iterator<Item = Self::Move> {
|
||||||
|
let board = self.clone();
|
||||||
|
(0..WIDTH).filter(move |col| board[*col].is_none())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn result(&self) -> State {
|
||||||
|
Connect4::result(&self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_move(&mut self, position: Self::Move, player: Player) {
|
||||||
|
for i in 0..3 {
|
||||||
|
let prev = position + (i * WIDTH);
|
||||||
|
let next = position + ((i + 1) * WIDTH);
|
||||||
|
|
||||||
|
if self[next].is_some() {
|
||||||
|
self[prev] = Some(player);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bottom = position + (3 * WIDTH);
|
||||||
|
self[bottom] = Some(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn undo_move(&mut self, position: Self::Move) {
|
||||||
|
for i in 0..4 {
|
||||||
|
let pos = position + (i * WIDTH);
|
||||||
|
|
||||||
|
if self[pos].is_some() {
|
||||||
|
self[pos] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rate(&self, player: Player) -> Score {
|
||||||
|
Connect4::rate(&self, player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Connect4 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for i in 0..HEIGTH {
|
||||||
|
for j in 0..WIDTH {
|
||||||
|
let index = (i * WIDTH) + j;
|
||||||
|
match self[index] {
|
||||||
|
Some(player) => {
|
||||||
|
write!(f, "\x1B[33m {player}\x1B[0m ")?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
write!(f, "\x1B[35m{index:3 }\x1B[0m ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_char('\n')?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Player, State};
|
use crate::{Player, State};
|
||||||
|
|
||||||
use super::Board;
|
use super::Connect4;
|
||||||
|
|
||||||
fn parse_board(board: &str) -> Board {
|
fn parse_board(board: &str) -> Connect4 {
|
||||||
let positions = board
|
let positions = board
|
||||||
.chars()
|
.chars()
|
||||||
.filter(|char| !char.is_whitespace())
|
.filter(|char| !char.is_whitespace())
|
||||||
|
|
@ -119,7 +218,7 @@ mod tests {
|
||||||
board.chars().filter(|c| !c.is_whitespace()).count()
|
board.chars().filter(|c| !c.is_whitespace()).count()
|
||||||
));
|
));
|
||||||
|
|
||||||
Board { positions }
|
Connect4 { positions }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test(board: &str, state: State) {
|
fn test(board: &str, state: State) {
|
||||||
|
|
|
||||||
|
|
@ -1 +1,6 @@
|
||||||
|
use self::board::Connect4;
|
||||||
|
|
||||||
|
pub use player::HumanPlayer;
|
||||||
|
|
||||||
pub mod board;
|
pub mod board;
|
||||||
|
pub mod player;
|
||||||
|
|
|
||||||
35
src/connect4/player.rs
Normal file
35
src/connect4/player.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::{Game, GamePlayer, Player};
|
||||||
|
|
||||||
|
use super::Connect4;
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct HumanPlayer;
|
||||||
|
|
||||||
|
impl GamePlayer<Connect4> for HumanPlayer {
|
||||||
|
fn next_move(&mut self, board: &mut Connect4, this_player: Player) {
|
||||||
|
loop {
|
||||||
|
print!("{board}where to put the next {this_player}? (0-7): ");
|
||||||
|
|
||||||
|
std::io::stdout().flush().unwrap();
|
||||||
|
let mut buf = String::new();
|
||||||
|
std::io::stdin().read_line(&mut buf).unwrap();
|
||||||
|
|
||||||
|
match buf.trim().parse() {
|
||||||
|
Ok(number) if number < 7 => match board[number] {
|
||||||
|
None => {
|
||||||
|
board.make_move(number, this_player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
println!("Field is occupied already.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(_) | Err(_) => {
|
||||||
|
println!("Invalid input.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/lib.rs
60
src/lib.rs
|
|
@ -1,4 +1,9 @@
|
||||||
#![feature(never_type, try_trait_v2, return_position_impl_trait_in_trait)]
|
#![feature(
|
||||||
|
never_type,
|
||||||
|
try_trait_v2,
|
||||||
|
return_position_impl_trait_in_trait,
|
||||||
|
let_chains
|
||||||
|
)]
|
||||||
#![allow(incomplete_features)]
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
pub mod connect4;
|
pub mod connect4;
|
||||||
|
|
@ -7,13 +12,56 @@ pub mod tic_tac_toe;
|
||||||
|
|
||||||
mod player;
|
mod player;
|
||||||
|
|
||||||
use self::minmax::GameBoard;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use minmax::Score;
|
||||||
pub use player::{Player, State};
|
pub use player::{Player, State};
|
||||||
|
|
||||||
pub trait Game {
|
pub trait GamePlayer<G: ?Sized + Game>: Default {
|
||||||
type Board: GameBoard;
|
fn next_move(&mut self, board: &mut G, this_player: Player);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait GamePlayer<G: Game>: Default {
|
pub trait Game: Display {
|
||||||
fn next_move(&mut self, board: &mut G::Board, this_player: Player);
|
type Move: Copy;
|
||||||
|
|
||||||
|
const REASONABLE_SEARCH_DEPTH: Option<usize>;
|
||||||
|
|
||||||
|
fn empty() -> Self;
|
||||||
|
|
||||||
|
fn possible_moves(&self) -> impl Iterator<Item = Self::Move>;
|
||||||
|
|
||||||
|
fn result(&self) -> State;
|
||||||
|
|
||||||
|
/// Only called if [`GameBoard::REASONABLE_SEARCH_DEPTH`] is `Some`.
|
||||||
|
fn rate(&self, player: Player) -> Score;
|
||||||
|
|
||||||
|
fn make_move(&mut self, position: Self::Move, player: Player);
|
||||||
|
|
||||||
|
fn undo_move(&mut self, position: Self::Move);
|
||||||
|
|
||||||
|
fn play<A: GamePlayer<Self>, B: GamePlayer<Self>>(
|
||||||
|
&mut self,
|
||||||
|
x: &mut A,
|
||||||
|
o: &mut B,
|
||||||
|
) -> Option<Player> {
|
||||||
|
let mut current_player = Player::X;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if current_player == Player::X {
|
||||||
|
x.next_move(self, current_player);
|
||||||
|
} else {
|
||||||
|
o.next_move(self, current_player);
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.result() {
|
||||||
|
State::Winner(player) => return Some(player),
|
||||||
|
State::Draw => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
State::InProgress => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_player = current_player.opponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
45
src/main.rs
45
src/main.rs
|
|
@ -1,19 +1,25 @@
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
use std::time::SystemTime;
|
use std::{fmt::Display, time::SystemTime};
|
||||||
|
|
||||||
use minmax::{
|
use minmax::{
|
||||||
tic_tac_toe::{Board, GreedyPlayer, HumanPlayer, PerfectPlayer, TicTacToe},
|
connect4::{self, board::Connect4},
|
||||||
GamePlayer, Player,
|
tic_tac_toe::{GreedyPlayer, HumanPlayer, PerfectPlayer, TicTacToe},
|
||||||
|
Game, GamePlayer, Player,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
play::<connect4::HumanPlayer, connect4::HumanPlayer, _>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn tic_tac_toe_stats() {
|
||||||
let mut results = [0, 0, 0];
|
let mut results = [0, 0, 0];
|
||||||
|
|
||||||
let start = SystemTime::now();
|
let start = SystemTime::now();
|
||||||
|
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
let result = play_round::<PerfectPlayer, GreedyPlayer>(false);
|
let result = play::<PerfectPlayer, GreedyPlayer, _>(false);
|
||||||
let idx = Player::as_u8(result);
|
let idx = Player::as_u8(result);
|
||||||
results[idx as usize] += 1;
|
results[idx as usize] += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -28,23 +34,24 @@ fn main() {
|
||||||
println!("Completed in {}ms", time.as_millis());
|
println!("Completed in {}ms", time.as_millis());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn play_round<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>(print: bool) -> Option<Player> {
|
fn play<X: GamePlayer<G>, O: GamePlayer<G>, G: Game>(print: bool) -> Option<Player> {
|
||||||
let mut board = Board::empty();
|
let mut board = G::empty();
|
||||||
let result = board.play(&mut X::default(), &mut O::default());
|
let result = board.play(&mut X::default(), &mut O::default());
|
||||||
if print {
|
if print {
|
||||||
println!("{board}");
|
print_result(result, board);
|
||||||
}
|
|
||||||
match result {
|
|
||||||
Some(winner) => {
|
|
||||||
if print {
|
|
||||||
println!("player {winner} won!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if print {
|
|
||||||
println!("a draw...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_result(result: Option<Player>, board: impl Display) {
|
||||||
|
println!("{board}");
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Some(winner) => {
|
||||||
|
println!("player {winner} won!");
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("a draw...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,17 @@ use std::ops::Neg;
|
||||||
|
|
||||||
use crate::{Game, GamePlayer, Player, State};
|
use crate::{Game, GamePlayer, Player, State};
|
||||||
|
|
||||||
pub trait GameBoard {
|
|
||||||
type Move: Copy;
|
|
||||||
|
|
||||||
fn possible_moves(&self) -> impl Iterator<Item = Self::Move>;
|
|
||||||
|
|
||||||
fn result(&self) -> State;
|
|
||||||
|
|
||||||
fn make_move(&mut self, position: Self::Move, player: Player);
|
|
||||||
|
|
||||||
fn undo_move(&mut self, position: Self::Move);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Score(i8);
|
pub struct Score(i32);
|
||||||
|
|
||||||
impl Score {
|
impl Score {
|
||||||
const LOST: Self = Self(i8::MIN);
|
const LOST: Self = Self(i32::MIN);
|
||||||
const TIE: Self = Self(0);
|
const TIE: Self = Self(0);
|
||||||
const WON: Self = Self(i8::MAX);
|
const WON: Self = Self(i32::MAX);
|
||||||
|
|
||||||
|
pub fn new(int: i32) -> Self {
|
||||||
|
Self(int)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Neg for Score {
|
impl Neg for Score {
|
||||||
|
|
@ -32,25 +24,26 @@ impl Neg for Score {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PerfectPlayer<B: GameBoard> {
|
pub struct PerfectPlayer<B: Game> {
|
||||||
best_move: Option<B::Move>,
|
best_move: Option<B::Move>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: GameBoard> Default for PerfectPlayer<B> {
|
impl<B: Game> Default for PerfectPlayer<B> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: GameBoard> PerfectPlayer<B> {
|
impl<B: Game> PerfectPlayer<B> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { best_move: None }
|
Self { best_move: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn minmax(&mut self, board: &mut B, player: Player, depth: usize) -> Score {
|
fn minmax(&mut self, board: &mut B, player: Player, depth: usize) -> Score {
|
||||||
if depth < 2 {
|
if let Some(max_depth) = B::REASONABLE_SEARCH_DEPTH && depth >= max_depth {
|
||||||
//print!("{board}{}| playing {player}: ", " ".repeat(depth));
|
return board.rate(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
match board.result() {
|
match board.result() {
|
||||||
State::Winner(winner) => {
|
State::Winner(winner) => {
|
||||||
if winner == player {
|
if winner == player {
|
||||||
|
|
@ -83,8 +76,8 @@ impl<B: GameBoard> PerfectPlayer<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<G: Game> GamePlayer<G> for PerfectPlayer<G::Board> {
|
impl<G: Game> GamePlayer<G> for PerfectPlayer<G> {
|
||||||
fn next_move(&mut self, board: &mut G::Board, this_player: Player) {
|
fn next_move(&mut self, board: &mut G, this_player: Player) {
|
||||||
self.best_move = None;
|
self.best_move = None;
|
||||||
self.minmax(board, this_player, 0);
|
self.minmax(board, this_player, 0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use std::fmt::{Display, Write};
|
use std::fmt::{Display, Write};
|
||||||
|
|
||||||
use crate::{minmax::GameBoard, Player, State};
|
use crate::{minmax::Score, Player, State, Game};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Board(u32);
|
pub struct TicTacToe(u32);
|
||||||
|
|
||||||
impl Board {
|
impl TicTacToe {
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
// A = 1010
|
// A = 1010
|
||||||
// 18 bits - 9 * 2 bits - 4.5 nibbles
|
// 18 bits - 9 * 2 bits - 4.5 nibbles
|
||||||
|
|
@ -75,14 +75,14 @@ impl Board {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod win_table {
|
mod win_table {
|
||||||
use super::Board;
|
use super::TicTacToe;
|
||||||
use crate::{Player, State};
|
use crate::{Player, State};
|
||||||
|
|
||||||
const WIN_TABLE_SIZE: usize = 2usize.pow(2 * 9);
|
const WIN_TABLE_SIZE: usize = 2usize.pow(2 * 9);
|
||||||
static WIN_TABLE: &[u8; WIN_TABLE_SIZE] =
|
static WIN_TABLE: &[u8; WIN_TABLE_SIZE] =
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/win_table"));
|
include_bytes!(concat!(env!("OUT_DIR"), "/win_table"));
|
||||||
|
|
||||||
pub fn result(board: &Board) -> State {
|
pub fn result(board: &TicTacToe) -> State {
|
||||||
match WIN_TABLE[board.0 as usize] {
|
match WIN_TABLE[board.0 as usize] {
|
||||||
0 => State::Winner(Player::X),
|
0 => State::Winner(Player::X),
|
||||||
1 => State::Winner(Player::X),
|
1 => State::Winner(Player::X),
|
||||||
|
|
@ -93,7 +93,7 @@ mod win_table {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Board {
|
impl Display for TicTacToe {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
for j in 0..3 {
|
for j in 0..3 {
|
||||||
|
|
@ -113,9 +113,15 @@ impl Display for Board {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameBoard for Board {
|
impl Game for TicTacToe {
|
||||||
type Move = usize;
|
type Move = usize;
|
||||||
|
|
||||||
|
const REASONABLE_SEARCH_DEPTH: Option<usize> = None;
|
||||||
|
|
||||||
|
fn empty() -> Self {
|
||||||
|
Self::empty()
|
||||||
|
}
|
||||||
|
|
||||||
fn possible_moves(&self) -> impl Iterator<Item = Self::Move> {
|
fn possible_moves(&self) -> impl Iterator<Item = Self::Move> {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
!self.iter().all(|x| x.is_some()),
|
!self.iter().all(|x| x.is_some()),
|
||||||
|
|
@ -129,7 +135,11 @@ impl GameBoard for Board {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn result(&self) -> State {
|
fn result(&self) -> State {
|
||||||
Board::result(self)
|
TicTacToe::result(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rate(&self, _: Player) -> Score {
|
||||||
|
unimplemented!("we always finish the board")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_move(&mut self, position: Self::Move, player: Player) {
|
fn make_move(&mut self, position: Self::Move, player: Player) {
|
||||||
|
|
@ -143,11 +153,11 @@ impl GameBoard for Board {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Board, Player};
|
use super::{Player, TicTacToe};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn board_field() {
|
fn board_field() {
|
||||||
let mut board = Board::empty();
|
let mut board = TicTacToe::empty();
|
||||||
board.set(0, None);
|
board.set(0, None);
|
||||||
board.set(8, Some(Player::X));
|
board.set(8, Some(Player::X));
|
||||||
board.set(4, Some(Player::O));
|
board.set(4, Some(Player::O));
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
use crate::{GamePlayer, Player, State};
|
use crate::{GamePlayer, Player, State};
|
||||||
|
|
||||||
use super::{board::Board, TicTacToe};
|
use super::TicTacToe;
|
||||||
|
|
||||||
impl Board {
|
impl TicTacToe {
|
||||||
pub fn default_play<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>() -> Option<Player> {
|
pub fn default_play<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>() -> Option<Player> {
|
||||||
Self::empty().play(&mut X::default(), &mut O::default())
|
Self::empty().play(&mut X::default(), &mut O::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play<A: GamePlayer<TicTacToe>, B: GamePlayer<TicTacToe>>(&mut self, x: &mut A, o: &mut B) -> Option<Player> {
|
pub fn play<A: GamePlayer<TicTacToe>, B: GamePlayer<TicTacToe>>(
|
||||||
|
&mut self,
|
||||||
|
x: &mut A,
|
||||||
|
o: &mut B,
|
||||||
|
) -> Option<Player> {
|
||||||
let mut current_player = Player::X;
|
let mut current_player = Player::X;
|
||||||
|
|
||||||
for _ in 0..9 {
|
for _ in 0..9 {
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,17 @@
|
||||||
use crate::Game;
|
|
||||||
|
|
||||||
mod board;
|
mod board;
|
||||||
mod game;
|
mod game;
|
||||||
mod perfect;
|
mod perfect;
|
||||||
mod player;
|
mod player;
|
||||||
|
|
||||||
pub use {board::Board, perfect::PerfectPlayer, player::*};
|
pub use {board::TicTacToe, perfect::PerfectPlayer, player::*};
|
||||||
|
|
||||||
pub struct TicTacToe;
|
|
||||||
|
|
||||||
impl Game for TicTacToe {
|
|
||||||
type Board = board::Board;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{tic_tac_toe::board::Board, GamePlayer, Player};
|
use crate::{tic_tac_toe::board::TicTacToe, GamePlayer, Player};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
perfect::PerfectPlayer,
|
perfect::PerfectPlayer,
|
||||||
player::{GreedyPlayer, RandomPlayer},
|
player::{GreedyPlayer, RandomPlayer},
|
||||||
TicTacToe,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn assert_win_ratio<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>(
|
fn assert_win_ratio<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>(
|
||||||
|
|
@ -30,7 +21,7 @@ mod tests {
|
||||||
let mut results = [0u64, 0, 0];
|
let mut results = [0u64, 0, 0];
|
||||||
|
|
||||||
for _ in 0..runs {
|
for _ in 0..runs {
|
||||||
let result = Board::default_play::<X, O>();
|
let result = TicTacToe::default_play::<X, O>();
|
||||||
let idx = Player::as_u8(result);
|
let idx = Player::as_u8(result);
|
||||||
results[idx as usize] += 1;
|
results[idx as usize] += 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::ops::Neg;
|
||||||
|
|
||||||
use crate::{GamePlayer, Player, State};
|
use crate::{GamePlayer, Player, State};
|
||||||
|
|
||||||
use super::{board::Board, TicTacToe};
|
use super::TicTacToe;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum Score {
|
enum Score {
|
||||||
|
|
@ -41,7 +41,7 @@ impl PerfectPlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn minmax(&mut self, board: &mut Board, player: Player, depth: usize) -> Score {
|
fn minmax(&mut self, board: &mut TicTacToe, player: Player, depth: usize) -> Score {
|
||||||
if depth < 2 {
|
if depth < 2 {
|
||||||
//print!("{board}{}| playing {player}: ", " ".repeat(depth));
|
//print!("{board}{}| playing {player}: ", " ".repeat(depth));
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +105,7 @@ impl PerfectPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GamePlayer<TicTacToe> for PerfectPlayer {
|
impl GamePlayer<TicTacToe> for PerfectPlayer {
|
||||||
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
fn next_move(&mut self, board: &mut TicTacToe, this_player: Player) {
|
||||||
self.best_move = usize::MAX;
|
self.best_move = usize::MAX;
|
||||||
self.minmax(board, this_player, 0);
|
self.minmax(board, this_player, 0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ use std::io::Write;
|
||||||
|
|
||||||
use crate::{GamePlayer, Player};
|
use crate::{GamePlayer, Player};
|
||||||
|
|
||||||
use super::{board::Board, TicTacToe};
|
use super::TicTacToe;
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct GreedyPlayer;
|
pub struct GreedyPlayer;
|
||||||
|
|
||||||
impl GamePlayer<TicTacToe> for GreedyPlayer {
|
impl GamePlayer<TicTacToe> for GreedyPlayer {
|
||||||
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
fn next_move(&mut self, board: &mut TicTacToe, this_player: Player) {
|
||||||
let first_free = board.iter().position(|p| p.is_none()).unwrap();
|
let first_free = board.iter().position(|p| p.is_none()).unwrap();
|
||||||
board.set(first_free, Some(this_player));
|
board.set(first_free, Some(this_player));
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ impl GamePlayer<TicTacToe> for GreedyPlayer {
|
||||||
pub struct HumanPlayer;
|
pub struct HumanPlayer;
|
||||||
|
|
||||||
impl GamePlayer<TicTacToe> for HumanPlayer {
|
impl GamePlayer<TicTacToe> for HumanPlayer {
|
||||||
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
fn next_move(&mut self, board: &mut TicTacToe, this_player: Player) {
|
||||||
loop {
|
loop {
|
||||||
print!("{board}where to put the next {this_player}? (0-8): ");
|
print!("{board}where to put the next {this_player}? (0-8): ");
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ fn fun_random() -> u64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GamePlayer<TicTacToe> for RandomPlayer {
|
impl GamePlayer<TicTacToe> for RandomPlayer {
|
||||||
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
fn next_move(&mut self, board: &mut TicTacToe, this_player: Player) {
|
||||||
loop {
|
loop {
|
||||||
let next = (fun_random() % 9) as usize;
|
let next = (fun_random() % 9) as usize;
|
||||||
match board.get(next) {
|
match board.get(next) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue