Skip to content

Commit

Permalink
feat: pretty output, better exiting tui
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoDog896 committed Sep 26, 2024
1 parent 658127b commit 806f31f
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 103 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/game-solver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ twox-hash = { version = "1.6", optional = true }
itertools = { version = "0.13", optional = true }
futures = "0.3.30"
thiserror = "1.0"
castaway = "0.2.3"

[package.metadata.docs.rs]
all-features = true
67 changes: 44 additions & 23 deletions crates/game-solver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use game::{upper_bound, GameState};
use player::TwoPlayer;
use player::{ImpartialPlayer, TwoPlayer};
use stats::Stats;

use crate::game::Game;
Expand All @@ -37,12 +37,12 @@ pub enum GameSolveError<T: Game> {

/// 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 = impl TwoPlayer> + Eq + Hash>(
fn negamax<T: Game<Player = impl TwoPlayer + 'static> + Eq + Hash>(
game: &T,
transposition_table: &mut dyn TranspositionTable<T>,
mut alpha: isize,
mut beta: isize,
stats: Option<&Stats>,
stats: Option<&Stats<T::Player>>,
cancellation_token: &Option<Arc<AtomicBool>>,
) -> Result<isize, GameSolveError<T>> {
if let Some(token) = cancellation_token {
Expand All @@ -67,12 +67,26 @@ fn negamax<T: Game<Player = impl TwoPlayer> + Eq + Hash>(
return Ok(0);
}
GameState::Win(winning_player) => {
// TODO: can we not duplicate this
if let Some(stats) = stats {
if let Ok(player) = castaway::cast!(winning_player, ImpartialPlayer) {
if ImpartialPlayer::from_move_count(stats.original_move_count, game.move_count()) == player {
stats.terminal_ends.winning.fetch_add(1, Ordering::Relaxed);
} else {
stats.terminal_ends.losing.fetch_add(1, Ordering::Relaxed);
}
} else {
if stats.original_player == winning_player {
stats.terminal_ends.winning.fetch_add(1, Ordering::Relaxed);
} else {
stats.terminal_ends.losing.fetch_add(1, Ordering::Relaxed);
}
}
}

// if the next player is the winning player,
// the score should be positive.
if game.player() == winning_player {
if let Some(stats) = stats {
stats.terminal_ends.winning.fetch_add(1, Ordering::Relaxed);
}
// we add one to make sure games that use up every move
// aren't represented by ties.
//
Expand All @@ -82,9 +96,6 @@ fn negamax<T: Game<Player = impl TwoPlayer> + Eq + Hash>(
// but we reserve 0 for ties.
return Ok(upper_bound(game) - game.move_count() as isize + 1);
} else {
if let Some(stats) = stats {
stats.terminal_ends.losing.fetch_add(1, Ordering::Relaxed);
}
return Ok(-(upper_bound(game) - game.move_count() as isize + 1));
}
}
Expand All @@ -101,15 +112,25 @@ fn negamax<T: Game<Player = impl TwoPlayer> + Eq + Hash>(
return Ok(0);
}
GameState::Win(winning_player) => {
if game.player().turn() == winning_player {
if let Some(stats) = stats {
stats.terminal_ends.winning.fetch_add(1, Ordering::Relaxed);
if let Some(stats) = stats {
if let Ok(player) = castaway::cast!(winning_player, ImpartialPlayer) {
if ImpartialPlayer::from_move_count(stats.original_move_count, game.move_count()) == player {
stats.terminal_ends.winning.fetch_add(1, Ordering::Relaxed);
} else {
stats.terminal_ends.losing.fetch_add(1, Ordering::Relaxed);
}
} else {
if stats.original_player == winning_player {
stats.terminal_ends.winning.fetch_add(1, Ordering::Relaxed);
} else {
stats.terminal_ends.losing.fetch_add(1, Ordering::Relaxed);
}
}
}

if game.player().turn() == winning_player {
return Ok(upper_bound(&board) - board.move_count() as isize + 1);
} else {
if let Some(stats) = stats {
stats.terminal_ends.losing.fetch_add(1, Ordering::Relaxed);
}
return Ok(-(upper_bound(&board) - board.move_count() as isize + 1));
}
}
Expand Down Expand Up @@ -220,10 +241,10 @@ fn negamax<T: Game<Player = impl 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 = impl TwoPlayer> + Eq + Hash>(
pub fn solve<T: Game<Player = impl TwoPlayer + 'static> + Eq + Hash>(
game: &T,
transposition_table: &mut dyn TranspositionTable<T>,
stats: Option<&Stats>,
stats: Option<&Stats<T::Player>>,
cancellation_token: &Option<Arc<AtomicBool>>,
) -> Result<isize, GameSolveError<T>> {
let mut alpha = -upper_bound(game);
Expand Down Expand Up @@ -261,10 +282,10 @@ pub fn solve<T: Game<Player = impl TwoPlayer> + Eq + Hash>(
/// # Returns
///
/// An iterator of tuples of the form `(move, score)`.
pub fn move_scores<'a, T: Game<Player = impl TwoPlayer> + Eq + Hash>(
pub fn move_scores<'a, T: Game<Player = impl TwoPlayer + 'static> + Eq + Hash>(
game: &'a T,
transposition_table: &'a mut dyn TranspositionTable<T>,
stats: Option<&'a Stats>,
stats: Option<&'a Stats<T::Player>>,
cancellation_token: &'a Option<Arc<AtomicBool>>,
) -> impl Iterator<Item = Result<(T::Move, isize), GameSolveError<T>>> + 'a {
game.possible_moves().map(move |m| {
Expand Down Expand Up @@ -294,11 +315,11 @@ pub type CollectedMoves<T> = Vec<Result<(<T as Game>::Move, isize), GameSolveErr
/// A vector of tuples of the form `(move, score)`.
#[cfg(feature = "rayon")]
pub fn par_move_scores_with_hasher<
T: Game<Player = impl TwoPlayer> + Eq + Hash + Sync + Send + 'static,
T: Game<Player = impl TwoPlayer + Sync + 'static> + Eq + Hash + Sync + Send + 'static,
S,
>(
game: &T,
stats: Option<&Stats>,
stats: Option<&Stats<T::Player>>,
cancellation_token: &Option<Arc<AtomicBool>>,
) -> CollectedMoves<T>
where
Expand Down Expand Up @@ -343,9 +364,9 @@ where
///
/// A vector of tuples of the form `(move, score)`.
#[cfg(feature = "rayon")]
pub fn par_move_scores<T: Game<Player = impl TwoPlayer> + Eq + Hash + Sync + Send + 'static>(
pub fn par_move_scores<T: Game<Player = impl TwoPlayer + Sync + 'static> + Eq + Hash + Sync + Send + 'static>(
game: &T,
stats: Option<&Stats>,
stats: Option<&Stats<T::Player>>,
cancellation_token: &Option<Arc<AtomicBool>>,
) -> CollectedMoves<T>
where
Expand Down
14 changes: 13 additions & 1 deletion crates/game-solver/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ pub trait Player: Sized + Eq {
}

/// Represents a two player player.
pub trait TwoPlayer: Player {
///
/// This player should always be representable by a byte.
pub trait TwoPlayer: Player + Copy {
/// Gets the other player
#[must_use]
fn other(self) -> Self {
Expand Down Expand Up @@ -85,6 +87,16 @@ pub enum ImpartialPlayer {
Previous,
}

impl ImpartialPlayer {
pub fn from_move_count(initial_move_count: usize, final_move_count: usize) -> ImpartialPlayer {
if (final_move_count - initial_move_count) % 2 == 0 {
ImpartialPlayer::Next
} else {
ImpartialPlayer::Previous
}
}
}

impl Player for ImpartialPlayer {
fn count() -> usize {
2
Expand Down
17 changes: 5 additions & 12 deletions crates/game-solver/src/stats.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::sync::atomic::{AtomicU64, AtomicUsize};

use crate::player::Player;

#[derive(Debug)]
pub struct TerminalEnds {
pub winning: AtomicU64,
Expand All @@ -18,22 +20,13 @@ impl Default for TerminalEnds {
}

#[derive(Debug)]
pub struct Stats {
pub struct Stats<P: Player> {
pub states_explored: AtomicU64,
pub max_depth: AtomicUsize,
pub cache_hits: AtomicU64,
pub pruning_cutoffs: AtomicU64,
pub terminal_ends: TerminalEnds,
pub original_player: P,
pub original_move_count: usize
}

impl Default for Stats {
fn default() -> Self {
Self {
states_explored: AtomicU64::new(0),
max_depth: AtomicUsize::new(0),
cache_hits: AtomicU64::new(0),
pruning_cutoffs: AtomicU64::new(0),
terminal_ends: TerminalEnds::default(),
}
}
}
1 change: 1 addition & 0 deletions crates/games/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ thiserror = "1.0.63"
petgraph = "0.6.5"
castaway = "0.2.3"
ratatui = "0.28.1"
owo-colors = "4.1.0"

[features]
"egui" = ["dep:egui", "dep:egui_commonmark"]
Loading

0 comments on commit 806f31f

Please sign in to comment.