Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoDog896 committed Sep 23, 2024
1 parent 4801a47 commit d05f210
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 194 deletions.
16 changes: 0 additions & 16 deletions crates/game-solver/src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,6 @@ pub enum GameState<P: Player> {
Win(P),
}

/// Defines the 'partiality' of the game; whether a game's possible moves
/// are affected by the current player moving.
pub enum Partiality {
/// If a game is impartial, a game's moves
/// must not be affected by its player.
///
/// Learn more: <https://en.wikipedia.org/wiki/Impartial_game>
Impartial,
/// If a game is partizan, a game's moves
/// must be affected by its player.
///
/// Learn more: <https://en.wikipedia.org/wiki/Partisan_game>
Partizan
}

/// Defines the 'state' the game is in.
///
/// Generally used by a game solver for better optimizations.
Expand Down Expand Up @@ -84,7 +69,6 @@ pub trait Game: Clone {

type Player: Player;

const PARTIALITY: Partiality;
const STATE_TYPE: Option<StateType>;

/// Returns the amount of moves that have been played
Expand Down
12 changes: 5 additions & 7 deletions crates/game-solver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use std::hash::Hash;

/// Runs the two-player minimax variant on a zero-sum game.
/// Since it uses alpha-beta pruning, you can specify an alpha beta window.
fn negamax<T: Game<Player = TwoPlayer> + Eq + Hash>(
fn negamax<T: Game<Player = impl TwoPlayer> + Eq + Hash>(
game: &T,
transposition_table: &mut dyn TranspositionTable<T>,
mut alpha: isize,
Expand Down Expand Up @@ -108,7 +108,7 @@ fn negamax<T: Game<Player = TwoPlayer> + Eq + Hash>(
/// In 2 player games, if a score > 0, then the player whose turn it is has a winning strategy.
/// If a score < 0, then the player whose turn it is has a losing strategy.
/// Else, the game is a draw (score = 0).
pub fn solve<T: Game<Player = TwoPlayer> + Eq + Hash>(
pub fn solve<T: Game<Player = impl TwoPlayer> + Eq + Hash>(
game: &T,
transposition_table: &mut dyn TranspositionTable<T>,
) -> Result<isize, T::MoveError> {
Expand Down Expand Up @@ -140,7 +140,7 @@ pub fn solve<T: Game<Player = TwoPlayer> + Eq + Hash>(
/// # Returns
///
/// An iterator of tuples of the form `(move, score)`.
pub fn move_scores<'a, T: Game<Player = TwoPlayer> + Eq + Hash>(
pub fn move_scores<'a, T: Game<Player = impl TwoPlayer> + Eq + Hash>(
game: &'a T,
transposition_table: &'a mut dyn TranspositionTable<T>,
) -> impl Iterator<Item = Result<(T::Move, isize), T::MoveError>> + 'a {
Expand All @@ -165,9 +165,8 @@ type CollectedMoves<T> = Vec<Result<(<T as Game>::Move, isize), <T as Game>::Mov
///
/// A vector of tuples of the form `(move, score)`.
#[cfg(feature = "rayon")]
pub fn par_move_scores_with_hasher<T, S>(game: &T) -> CollectedMoves<T>
pub fn par_move_scores_with_hasher<T: Game<Player = impl TwoPlayer> + Eq + Hash + Sync + Send + 'static, S>(game: &T) -> CollectedMoves<T>
where
T: Game<Player = TwoPlayer> + Eq + Hash + Sync + Send + 'static,
T::Move: Sync + Send,
T::MoveError: Sync + Send,
S: BuildHasher + Default + Sync + Send + Clone + 'static,
Expand Down Expand Up @@ -204,9 +203,8 @@ where
///
/// A vector of tuples of the form `(move, score)`.
#[cfg(feature = "rayon")]
pub fn par_move_scores<T>(game: &T) -> CollectedMoves<T>
pub fn par_move_scores<T: Game<Player = impl TwoPlayer> + Eq + Hash + Sync + Send + 'static>(game: &T) -> CollectedMoves<T>
where
T: Game<Player = TwoPlayer> + Eq + Hash + Sync + Send + 'static,
T::Move: Sync + Send,
T::MoveError: Sync + Send,
{
Expand Down
7 changes: 5 additions & 2 deletions crates/game-solver/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ pub trait TwoPlayer: Player {
}
}

/// Represents a player in a partizan game.
/// Represents a player in a zero-sum (2-player) game,
/// where the game is partizan. That is,
/// a player can affect the `Game::possible_moves` function,
/// or players have different winning outcomes.
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
pub enum PartizanPlayer {
/// The first player.
Expand Down Expand Up @@ -60,7 +63,7 @@ impl TwoPlayer for PartizanPlayer {}

/// Represents a player in a zero-sum (2-player) game,
/// where the game is impartial. That is,
/// a player does not affect the `Game::possible_moves` function.
/// the only difference between players is who goes first.
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
pub enum ImpartialPlayer {
/// The player that will play on the current game state,
Expand Down
11 changes: 3 additions & 8 deletions crates/games/src/chomp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod gui;
use anyhow::Error;
use array2d::Array2D;
use clap::Args;
use game_solver::{game::{Game, GameState, Partiality, PartizanGame, StateType}, player::ZeroSumPlayer};
use game_solver::{game::{Game, GameState, StateType}, player::ImpartialPlayer};
use serde::{Deserialize, Serialize};
use thiserror::Error;

Expand Down Expand Up @@ -77,10 +77,9 @@ pub type ChompMove = NaturalMove<2>;
impl Game for Chomp {
type Move = ChompMove;
type Iter<'a> = std::vec::IntoIter<Self::Move>;
type Player = ZeroSumPlayer;
type Player = ImpartialPlayer;
type MoveError = ChompMoveError;

const PARTIALITY: Partiality = Partiality::Impartial;
const STATE_TYPE: Option<StateType> = Some(StateType::Misere);

fn max_moves(&self) -> Option<usize> {
Expand Down Expand Up @@ -118,11 +117,7 @@ impl Game for Chomp {
}

fn player(&self) -> Self::Player {
if self.move_count % 2 == 0 {
ZeroSumPlayer::Left
} else {
ZeroSumPlayer::Right
}
ImpartialPlayer::Next
}

fn state(&self) -> GameState<Self::Player> {
Expand Down
48 changes: 24 additions & 24 deletions crates/games/src/domineering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod gui;
use anyhow::Error;
use array2d::Array2D;
use clap::Args;
use game_solver::{game::{Game, GameState, PartizanGame}, player::{Player, ZeroSumPlayer}};
use game_solver::{game::{Game, GameState, StateType}, player::{PartizanPlayer, Player}};
use serde::{Deserialize, Serialize};
use std::{
fmt::{Debug, Display, Formatter},
Expand Down Expand Up @@ -63,9 +63,9 @@ impl<const WIDTH: usize, const HEIGHT: usize> Domineering<WIDTH, HEIGHT> {
#[derive(Error, Debug, Clone)]
pub enum DomineeringMoveError {
#[error("While no domino is present at {0}, player {1:?} can not move at {0} because a domino is in way of placement.")]
BlockingAdjacent(DomineeringMove, ZeroSumPlayer),
BlockingAdjacent(DomineeringMove, PartizanPlayer),
#[error("Player {1:?} can not move at {0} because a domino is already at {0}.")]
BlockingCurrent(DomineeringMove, ZeroSumPlayer),
BlockingCurrent(DomineeringMove, PartizanPlayer),
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -106,22 +106,14 @@ impl<const WIDTH: usize, const HEIGHT: usize> Domineering<WIDTH, HEIGHT> {
}
}

impl<const WIDTH: usize, const HEIGHT: usize> PartizanGame for Domineering<WIDTH, HEIGHT> {
fn player(&self) -> Self::Player {
if self.move_count % 2 == 0 {
ZeroSumPlayer::Left
} else {
ZeroSumPlayer::Right
}
}
}

impl<const WIDTH: usize, const HEIGHT: usize> Game for Domineering<WIDTH, HEIGHT> {
type Move = DomineeringMove;
type Iter<'a> = std::vec::IntoIter<Self::Move>;
type Player = ZeroSumPlayer;
type Player = PartizanPlayer;
type MoveError = DomineeringMoveError;

const STATE_TYPE: Option<StateType> = Some(StateType::Normal);

fn max_moves(&self) -> Option<usize> {
Some(WIDTH * HEIGHT)
}
Expand All @@ -132,7 +124,7 @@ impl<const WIDTH: usize, const HEIGHT: usize> Game for Domineering<WIDTH, HEIGHT

fn make_move(&mut self, m: &Self::Move) -> Result<(), Self::MoveError> {
if *self.board.get(m.0, m.1).unwrap() {
self.place(m, if self.player() == ZeroSumPlayer::Left {
self.place(m, if self.player() == PartizanPlayer::Left {
self.primary_orientation
} else {
self.primary_orientation.turn()
Expand All @@ -150,7 +142,7 @@ impl<const WIDTH: usize, const HEIGHT: usize> Game for Domineering<WIDTH, HEIGHT

fn possible_moves(&self) -> Self::Iter<'_> {
let mut moves = Vec::new();
let orientation = if self.player() == ZeroSumPlayer::Left {
let orientation = if self.player() == PartizanPlayer::Left {
self.primary_orientation
} else {
self.primary_orientation.turn()
Expand Down Expand Up @@ -187,6 +179,14 @@ impl<const WIDTH: usize, const HEIGHT: usize> Game for Domineering<WIDTH, HEIGHT
GameState::Playable
}
}

fn player(&self) -> Self::Player {
if self.move_count % 2 == 0 {
PartizanPlayer::Left
} else {
PartizanPlayer::Right
}
}
}

impl<const WIDTH: usize, const HEIGHT: usize> Display for Domineering<WIDTH, HEIGHT> {
Expand Down Expand Up @@ -244,7 +244,7 @@ mod tests {
use super::*;

/// Get the winner of a generic configuration of domineering
fn winner<const WIDTH: usize, const HEIGHT: usize>(orientation: Orientation) -> Option<ZeroSumPlayer> {
fn winner<const WIDTH: usize, const HEIGHT: usize>(orientation: Orientation) -> Option<PartizanPlayer> {
let game = Domineering::<WIDTH, HEIGHT>::new_orientation(orientation);
let mut move_scores = move_scores(&game, &mut HashMap::new())
.collect::<Result<Vec<_>, DomineeringMoveError>>()
Expand All @@ -256,20 +256,20 @@ mod tests {
move_scores.sort_by_key(|m| m.1);
move_scores.reverse();
if move_scores[0].1 > 0 {
Some(ZeroSumPlayer::Left)
Some(PartizanPlayer::Left)
} else {
Some(ZeroSumPlayer::Right)
Some(PartizanPlayer::Right)
}
}
}

#[test]
fn test_wins() {
assert_eq!(winner::<5, 5>(Orientation::Horizontal), Some(ZeroSumPlayer::Right));
assert_eq!(winner::<4, 4>(Orientation::Horizontal), Some(ZeroSumPlayer::Left));
assert_eq!(winner::<3, 3>(Orientation::Horizontal), Some(ZeroSumPlayer::Left));
assert_eq!(winner::<13, 2>(Orientation::Horizontal), Some(ZeroSumPlayer::Right));
assert_eq!(winner::<11, 2>(Orientation::Horizontal), Some(ZeroSumPlayer::Left));
assert_eq!(winner::<5, 5>(Orientation::Horizontal), Some(PartizanPlayer::Right));
assert_eq!(winner::<4, 4>(Orientation::Horizontal), Some(PartizanPlayer::Left));
assert_eq!(winner::<3, 3>(Orientation::Horizontal), Some(PartizanPlayer::Left));
assert_eq!(winner::<13, 2>(Orientation::Horizontal), Some(PartizanPlayer::Right));
assert_eq!(winner::<11, 2>(Orientation::Horizontal), Some(PartizanPlayer::Left));
}

#[test]
Expand Down
14 changes: 8 additions & 6 deletions crates/games/src/nim/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pub mod gui;
use anyhow::Error;
use clap::Args;
use game_solver::{game::{Game, GameState}, player::ImpartialPlayer};
use game_solver::{game::{Game, GameState, StateType}, player::ImpartialPlayer};
use serde::{Deserialize, Serialize};
use std::{fmt::Display, hash::Hash};
use thiserror::Error;
Expand Down Expand Up @@ -53,6 +53,8 @@ impl Game for Nim {
type Player = ImpartialPlayer;
type MoveError = NimMoveError;

const STATE_TYPE: Option<StateType> = Some(StateType::Normal);

fn max_moves(&self) -> Option<usize> {
Some(self.max_score)
}
Expand Down Expand Up @@ -99,11 +101,11 @@ impl Game for Nim {
}

fn state(&self) -> GameState<Self::Player> {
if self.possible_moves().len() == 0 {
GameState::Win(ImpartialPlayer::Previous)
} else {
GameState::Playable
}
Self::STATE_TYPE.unwrap().state(self)
}

fn player(&self) -> Self::Player {
ImpartialPlayer::Next
}
}

Expand Down
Loading

0 comments on commit d05f210

Please sign in to comment.