mirror of
https://github.com/Noratrieb/minmax.git
synced 2026-01-14 15:25:08 +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>;
|
||||
|
||||
|
|
@ -12,11 +15,12 @@ const BOARD_POSITIONS: usize = WIDTH * HEIGTH;
|
|||
/// 7 8 9 10 11 12 13
|
||||
/// 14 15 16 17 18 19 20
|
||||
/// 21 22 23 24 25 26 27
|
||||
pub struct Board {
|
||||
#[derive(Clone)]
|
||||
pub struct Connect4 {
|
||||
positions: [Position; BOARD_POSITIONS],
|
||||
}
|
||||
|
||||
impl Board {
|
||||
impl Connect4 {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
positions: [None; BOARD_POSITIONS],
|
||||
|
|
@ -86,9 +90,30 @@ impl Board {
|
|||
})
|
||||
.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;
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
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
|
||||
.chars()
|
||||
.filter(|char| !char.is_whitespace())
|
||||
|
|
@ -119,7 +218,7 @@ mod tests {
|
|||
board.chars().filter(|c| !c.is_whitespace()).count()
|
||||
));
|
||||
|
||||
Board { positions }
|
||||
Connect4 { positions }
|
||||
}
|
||||
|
||||
fn test(board: &str, state: State) {
|
||||
|
|
|
|||
|
|
@ -1 +1,6 @@
|
|||
pub mod board;
|
||||
use self::board::Connect4;
|
||||
|
||||
pub use player::HumanPlayer;
|
||||
|
||||
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)]
|
||||
|
||||
pub mod connect4;
|
||||
|
|
@ -7,13 +12,56 @@ pub mod tic_tac_toe;
|
|||
|
||||
mod player;
|
||||
|
||||
use self::minmax::GameBoard;
|
||||
use std::fmt::Display;
|
||||
|
||||
use minmax::Score;
|
||||
pub use player::{Player, State};
|
||||
|
||||
pub trait Game {
|
||||
type Board: GameBoard;
|
||||
pub trait GamePlayer<G: ?Sized + Game>: Default {
|
||||
fn next_move(&mut self, board: &mut G, this_player: Player);
|
||||
}
|
||||
|
||||
pub trait GamePlayer<G: Game>: Default {
|
||||
fn next_move(&mut self, board: &mut G::Board, this_player: Player);
|
||||
pub trait Game: Display {
|
||||
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)]
|
||||
|
||||
use std::time::SystemTime;
|
||||
use std::{fmt::Display, time::SystemTime};
|
||||
|
||||
use minmax::{
|
||||
tic_tac_toe::{Board, GreedyPlayer, HumanPlayer, PerfectPlayer, TicTacToe},
|
||||
GamePlayer, Player,
|
||||
connect4::{self, board::Connect4},
|
||||
tic_tac_toe::{GreedyPlayer, HumanPlayer, PerfectPlayer, TicTacToe},
|
||||
Game, GamePlayer, Player,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
play::<connect4::HumanPlayer, connect4::HumanPlayer, _>(true);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn tic_tac_toe_stats() {
|
||||
let mut results = [0, 0, 0];
|
||||
|
||||
let start = SystemTime::now();
|
||||
|
||||
for _ in 0..100 {
|
||||
let result = play_round::<PerfectPlayer, GreedyPlayer>(false);
|
||||
let result = play::<PerfectPlayer, GreedyPlayer, _>(false);
|
||||
let idx = Player::as_u8(result);
|
||||
results[idx as usize] += 1;
|
||||
}
|
||||
|
|
@ -28,23 +34,24 @@ fn main() {
|
|||
println!("Completed in {}ms", time.as_millis());
|
||||
}
|
||||
|
||||
fn play_round<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>(print: bool) -> Option<Player> {
|
||||
let mut board = Board::empty();
|
||||
fn play<X: GamePlayer<G>, O: GamePlayer<G>, G: Game>(print: bool) -> Option<Player> {
|
||||
let mut board = G::empty();
|
||||
let result = board.play(&mut X::default(), &mut O::default());
|
||||
if print {
|
||||
println!("{board}");
|
||||
}
|
||||
match result {
|
||||
Some(winner) => {
|
||||
if print {
|
||||
println!("player {winner} won!");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if print {
|
||||
println!("a draw...")
|
||||
}
|
||||
}
|
||||
print_result(result, board);
|
||||
}
|
||||
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};
|
||||
|
||||
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)]
|
||||
pub struct Score(i8);
|
||||
pub struct Score(i32);
|
||||
|
||||
impl Score {
|
||||
const LOST: Self = Self(i8::MIN);
|
||||
const LOST: Self = Self(i32::MIN);
|
||||
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 {
|
||||
|
|
@ -32,25 +24,26 @@ impl Neg for Score {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PerfectPlayer<B: GameBoard> {
|
||||
pub struct PerfectPlayer<B: Game> {
|
||||
best_move: Option<B::Move>,
|
||||
}
|
||||
|
||||
impl<B: GameBoard> Default for PerfectPlayer<B> {
|
||||
impl<B: Game> Default for PerfectPlayer<B> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: GameBoard> PerfectPlayer<B> {
|
||||
impl<B: Game> PerfectPlayer<B> {
|
||||
pub fn new() -> Self {
|
||||
Self { best_move: None }
|
||||
}
|
||||
|
||||
fn minmax(&mut self, board: &mut B, player: Player, depth: usize) -> Score {
|
||||
if depth < 2 {
|
||||
//print!("{board}{}| playing {player}: ", " ".repeat(depth));
|
||||
if let Some(max_depth) = B::REASONABLE_SEARCH_DEPTH && depth >= max_depth {
|
||||
return board.rate(player);
|
||||
}
|
||||
|
||||
match board.result() {
|
||||
State::Winner(winner) => {
|
||||
if winner == player {
|
||||
|
|
@ -83,8 +76,8 @@ impl<B: GameBoard> PerfectPlayer<B> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<G: Game> GamePlayer<G> for PerfectPlayer<G::Board> {
|
||||
fn next_move(&mut self, board: &mut G::Board, this_player: Player) {
|
||||
impl<G: Game> GamePlayer<G> for PerfectPlayer<G> {
|
||||
fn next_move(&mut self, board: &mut G, this_player: Player) {
|
||||
self.best_move = None;
|
||||
self.minmax(board, this_player, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::fmt::{Display, Write};
|
||||
|
||||
use crate::{minmax::GameBoard, Player, State};
|
||||
use crate::{minmax::Score, Player, State, Game};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Board(u32);
|
||||
pub struct TicTacToe(u32);
|
||||
|
||||
impl Board {
|
||||
impl TicTacToe {
|
||||
pub fn empty() -> Self {
|
||||
// A = 1010
|
||||
// 18 bits - 9 * 2 bits - 4.5 nibbles
|
||||
|
|
@ -75,14 +75,14 @@ impl Board {
|
|||
}
|
||||
|
||||
mod win_table {
|
||||
use super::Board;
|
||||
use super::TicTacToe;
|
||||
use crate::{Player, State};
|
||||
|
||||
const WIN_TABLE_SIZE: usize = 2usize.pow(2 * 9);
|
||||
static WIN_TABLE: &[u8; WIN_TABLE_SIZE] =
|
||||
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] {
|
||||
0 => 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 {
|
||||
for i 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;
|
||||
|
||||
const REASONABLE_SEARCH_DEPTH: Option<usize> = None;
|
||||
|
||||
fn empty() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
|
||||
fn possible_moves(&self) -> impl Iterator<Item = Self::Move> {
|
||||
debug_assert!(
|
||||
!self.iter().all(|x| x.is_some()),
|
||||
|
|
@ -129,7 +135,11 @@ impl GameBoard for Board {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -143,11 +153,11 @@ impl GameBoard for Board {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Board, Player};
|
||||
use super::{Player, TicTacToe};
|
||||
|
||||
#[test]
|
||||
fn board_field() {
|
||||
let mut board = Board::empty();
|
||||
let mut board = TicTacToe::empty();
|
||||
board.set(0, None);
|
||||
board.set(8, Some(Player::X));
|
||||
board.set(4, Some(Player::O));
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
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> {
|
||||
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;
|
||||
|
||||
for _ in 0..9 {
|
||||
|
|
|
|||
|
|
@ -1,26 +1,17 @@
|
|||
use crate::Game;
|
||||
|
||||
mod board;
|
||||
mod game;
|
||||
mod perfect;
|
||||
mod player;
|
||||
|
||||
pub use {board::Board, perfect::PerfectPlayer, player::*};
|
||||
|
||||
pub struct TicTacToe;
|
||||
|
||||
impl Game for TicTacToe {
|
||||
type Board = board::Board;
|
||||
}
|
||||
pub use {board::TicTacToe, perfect::PerfectPlayer, player::*};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{tic_tac_toe::board::Board, GamePlayer, Player};
|
||||
use crate::{tic_tac_toe::board::TicTacToe, GamePlayer, Player};
|
||||
|
||||
use super::{
|
||||
perfect::PerfectPlayer,
|
||||
player::{GreedyPlayer, RandomPlayer},
|
||||
TicTacToe,
|
||||
};
|
||||
|
||||
fn assert_win_ratio<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>(
|
||||
|
|
@ -30,7 +21,7 @@ mod tests {
|
|||
let mut results = [0u64, 0, 0];
|
||||
|
||||
for _ in 0..runs {
|
||||
let result = Board::default_play::<X, O>();
|
||||
let result = TicTacToe::default_play::<X, O>();
|
||||
let idx = Player::as_u8(result);
|
||||
results[idx as usize] += 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::ops::Neg;
|
|||
|
||||
use crate::{GamePlayer, Player, State};
|
||||
|
||||
use super::{board::Board, TicTacToe};
|
||||
use super::TicTacToe;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
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 {
|
||||
//print!("{board}{}| playing {player}: ", " ".repeat(depth));
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ impl 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.minmax(board, this_player, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ use std::io::Write;
|
|||
|
||||
use crate::{GamePlayer, Player};
|
||||
|
||||
use super::{board::Board, TicTacToe};
|
||||
use super::TicTacToe;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct 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();
|
||||
board.set(first_free, Some(this_player));
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ impl GamePlayer<TicTacToe> for GreedyPlayer {
|
|||
pub struct 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 {
|
||||
print!("{board}where to put the next {this_player}? (0-8): ");
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ fn fun_random() -> u64 {
|
|||
}
|
||||
|
||||
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 {
|
||||
let next = (fun_random() % 9) as usize;
|
||||
match board.get(next) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue