Skip to content

Commit

Permalink
style: fmt
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoDog896 committed Sep 26, 2024
1 parent d8d9080 commit 5c001e6
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 64 deletions.
34 changes: 20 additions & 14 deletions crates/game-solver/src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,31 +94,33 @@ pub trait Game: Clone {
///
/// Rather, this function asks if there exists some game in the possible games set
/// which has a resolvable, positive or negative, outcome.
///
///
/// This function must act in the Next player's best interest.
/// Positive games should have highest priority, then tied games, then lost games.
/// Exact order of what game is returned doesn't matter past its outcome equivalency,
/// as the score is dependent on move count.
///
///
/// (If this function returns a losing game when a positive game exists
/// in the set of immediately resolvable games, that is a violation of this
/// function's contract).
///
///
/// This function's default implementation is quite slow,
/// and it's encouraged to use a custom implementation.
fn find_immediately_resolvable_game(&self) -> Result<Option<Self>, Self::MoveError> {
let mut best_non_winning_game: Option<Self> = None;
let mut best_non_winning_game: Option<Self> = None;

for m in &mut self.possible_moves() {
let mut new_self = self.clone();
new_self.make_move(&m)?;
match new_self.state() {
GameState::Playable => continue,
GameState::Tie => best_non_winning_game = Some(new_self),
GameState::Win(winning_player) => if winning_player == self.player().turn() {
return Ok(Some(new_self))
} else if best_non_winning_game.is_none() {
best_non_winning_game = Some(new_self)
GameState::Win(winning_player) => {
if winning_player == self.player().turn() {
return Ok(Some(new_self));
} else if best_non_winning_game.is_none() {
best_non_winning_game = Some(new_self)
}
}
};
}
Expand Down Expand Up @@ -173,11 +175,11 @@ pub trait Game: Clone {
}

/// Utility function to get the upper score bound of a game.
///
///
/// Essentially, score computation generally gives some max (usually max moves),
/// and penalizes the score by the amount of moves that have been made, as we're
/// trying to encourage winning in the shortest amount of time - God's algorithm.
///
///
/// Note: Despite this returning isize, this function will always be positive.
pub fn upper_bound<T: Game>(game: &T) -> isize {
game.max_moves().map_or(isize::MAX, |m| m as isize)
Expand All @@ -190,15 +192,19 @@ pub enum GameScoreOutcome {
Win(usize),
/// The inner field represents the amount of moves till a loss.
Loss(usize),
Tie
Tie,
}

/// Utility function to convert a score to the
/// amount of moves to a win or loss, or a tie.
pub fn score_to_outcome<T: Game>(game: &T, score: isize) -> GameScoreOutcome {
match score.cmp(&0) {
Ordering::Greater => GameScoreOutcome::Win((-score + upper_bound(game) - game.move_count() as isize) as usize),
Ordering::Greater => GameScoreOutcome::Win(
(-score + upper_bound(game) - game.move_count() as isize) as usize,
),
Ordering::Equal => GameScoreOutcome::Tie,
Ordering::Less => GameScoreOutcome::Loss((score + upper_bound(game) - game.move_count() as isize) as usize)
Ordering::Less => GameScoreOutcome::Loss(
(score + upper_bound(game) - game.move_count() as isize) as usize,
),
}
}
16 changes: 9 additions & 7 deletions crates/game-solver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,24 @@ fn negamax<T: Game<Player = impl TwoPlayer> + Eq + Hash>(
GameState::Win(winning_player) => {
// The next player is the winning player - the score should be positive.
if game.player() == winning_player {
return Ok(upper_bound(game) - game.move_count() as isize)
return Ok(upper_bound(game) - game.move_count() as isize);
} else {
return Ok(-(upper_bound(game) - game.move_count() as isize))
return Ok(-(upper_bound(game) - game.move_count() as isize));
}
},
}
};

// check if this is a winning configuration
if let Ok(Some(board)) = game.find_immediately_resolvable_game() {
match board.state() {
GameState::Playable => panic!("A resolvable game should not be playable."),
GameState::Tie => return Ok(0),
GameState::Win(winning_player) => if game.player().turn() == winning_player {
return Ok(upper_bound(&board) - board.move_count() as isize);
} else {
return Ok(-(upper_bound(&board) - board.move_count() as isize));
GameState::Win(winning_player) => {
if game.player().turn() == winning_player {
return Ok(upper_bound(&board) - board.move_count() as isize);
} else {
return Ok(-(upper_bound(&board) - board.move_count() as isize));
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions crates/game-solver/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ pub trait Player: Sized + Eq {
#[must_use]
fn previous(self) -> Self;
/// How the player instance 'changes' on the next move.
///
///
/// For partizan games, the player doesn't change:
/// Left stays left; right stays right.
///
///
/// For impartial games, the player does change:
/// Next turns into previous, and previous turns into next
fn turn(self) -> Self;
Expand Down Expand Up @@ -152,7 +152,7 @@ impl<const N: usize> Player for NPlayerPartizanConst<N> {
Self::new_unchecked(self.0 - 1)
}
}

fn turn(self) -> Self {
self
}
Expand Down
2 changes: 1 addition & 1 deletion crates/games/src/nim/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl Game for Nim {
/// where Move is a tuple of the heap index and the number of objects to remove
type Move = NimMove;
type Iter<'a> = std::vec::IntoIter<Self::Move>;

/// Define Nim as a zero-sum impartial game
type Player = ImpartialPlayer;
type MoveError = NimMoveError;
Expand Down
Loading

0 comments on commit 5c001e6

Please sign in to comment.