AAAAAAAAAAAAAAAAAAAAAAAAa

This commit is contained in:
nora 2022-11-28 13:43:30 +01:00
parent 7f3a0e5ad2
commit d3d7011c43
No known key found for this signature in database
13 changed files with 280 additions and 88 deletions

BIN
first

Binary file not shown.

BIN
second

Binary file not shown.

View file

@ -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) {

View file

@ -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
View 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.")
}
}
}
}
}

View file

@ -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();
}
}
}

View file

@ -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...")
}
}
}

View file

@ -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);

View file

@ -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));

View file

@ -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 {

View file

@ -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;
}

View file

@ -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);

View file

@ -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) {