Skip to content

Commit

Permalink
refactor: begin dropping duplicate cli code
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoDog896 committed Jun 3, 2024
1 parent 93aca2b commit b76f37b
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 94 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions crates/games-ui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ impl eframe::App for TemplateApp {

if let Some(game) = &self.selected_game {
ui.heading(game.name());
ui.collapsing("See game description", |ui| {
ui.label(game.description());
});
} else {
ui.label("To get started, select a game from the above dropdown.");
}
Expand Down
4 changes: 4 additions & 0 deletions crates/games/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ ordinal = "0.3.2"
serde = { version = "1", features = ["derive"] }
serde-big-array = "0.5.1"
once_cell = "1.19.0"
egui = { version = "0.27", optional = true }

[features]
"egui" = ["dep:egui"]
11 changes: 11 additions & 0 deletions crates/games/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,15 @@ impl Games {
&Self::Chomp(_) => "Chomp".to_string(),
}
}

pub fn description(&self) -> &str {
match self {
&Self::Reversi(_) => include_str!("./reversi/README.md"),
&Self::TicTacToe(_) => include_str!("./tic_tac_toe/README.md"),
&Self::OrderAndChaos(_) => include_str!("./order_and_chaos/README.md"),
&Self::Nim(_) => include_str!("./nim/README.md"),
&Self::Domineering(_) => include_str!("./domineering/README.md"),
&Self::Chomp(_) => include_str!("./chomp/README.md"),
}
}
}
Empty file added crates/games/src/nim/gui.rs
Empty file.
35 changes: 3 additions & 32 deletions crates/games/src/reversi/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ use std::fmt;

use crate::{
reversi::{Reversi, ReversiMove},
util::move_natural::NaturalMove,
util::{cli::play::play, move_natural::NaturalMove},
};
use clap::Args;
use game_solver::{
game::{Game, ZeroSumPlayer},
par_move_scores,
};
use game_solver::game::{Game, ZeroSumPlayer};
use serde::{Deserialize, Serialize};

use super::{HEIGHT, WIDTH};
Expand Down Expand Up @@ -69,31 +66,5 @@ pub fn main(args: ReversiArgs) {
});

print!("{}", game);
println!("Player {:?} to move", game.player());

let mut move_scores = par_move_scores(&game);

if move_scores.is_empty() {
game.winning_player().map_or_else(
|| {
println!("Game tied!");
},
|player| {
println!("Player {:?} won!", player.opponent());
},
)
} else {
move_scores.sort_by_key(|m| m.1);
move_scores.reverse();

let mut current_move_score = None;
for (game_move, score) in move_scores {
if current_move_score != Some(score) {
println!("\n\nBest moves @ score {}:", score);
current_move_score = Some(score);
}
print!("{}, ", game_move);
}
println!();
}
play(game);
}
23 changes: 16 additions & 7 deletions crates/games/src/reversi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use array2d::Array2D;
use game_solver::game::{Game, ZeroSumPlayer};
use std::hash::Hash;

use crate::util::move_natural::NaturalMove;
use crate::util::{
move_natural::NaturalMove,
state::{GameState, State},
};

pub const WIDTH: usize = 6;
pub const HEIGHT: usize = 6;
Expand Down Expand Up @@ -117,8 +120,14 @@ impl Reversi {
Some(tiles_to_flip)
}
}
}

impl GameState for Reversi {
fn state(&self) -> State {
if self.possible_moves().len() > 0 {
return State::Continuing;
}

fn winning_player(&self) -> Option<ZeroSumPlayer> {
let mut player_one_count = 0;
let mut player_two_count = 0;

Expand All @@ -133,9 +142,9 @@ impl Reversi {
}

match player_one_count.cmp(&player_two_count) {
std::cmp::Ordering::Greater => Some(ZeroSumPlayer::One),
std::cmp::Ordering::Less => Some(ZeroSumPlayer::Two),
std::cmp::Ordering::Equal => None,
std::cmp::Ordering::Greater => State::Player(ZeroSumPlayer::One),
std::cmp::Ordering::Less => State::Player(ZeroSumPlayer::Two),
std::cmp::Ordering::Equal => State::Tie,
}
}
}
Expand Down Expand Up @@ -193,7 +202,7 @@ impl Game for Reversi {
let mut board = self.clone();
board.make_move(m);
if board.possible_moves().next().is_none() {
if board.winning_player() == Some(self.player()) {
if board.state() == State::Player(self.player()) {
Some(self.player())
} else {
None
Expand All @@ -204,6 +213,6 @@ impl Game for Reversi {
}

fn is_draw(&self) -> bool {
self.winning_player().is_none() && self.possible_moves().next().is_none()
self.state() == State::Tie
}
}
32 changes: 7 additions & 25 deletions crates/games/src/tic_tac_toe/cli.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use clap::Args;
use game_solver::{game::Game, par_move_scores};
use game_solver::game::Game;
use ndarray::IntoDimension;
use serde::{Deserialize, Serialize};

use crate::tic_tac_toe::{format_dim, TicTacToe};
use crate::{
tic_tac_toe::{TicTacToe, TicTacToeMove},
util::cli::play::play,
};

/// Analyzes Tic Tac Toe.
///
Expand Down Expand Up @@ -45,30 +48,9 @@ pub fn main(args: TicTacToeArgs) {
.map(|num| num.parse::<usize>().expect("Not a number!"))
.collect();

game.make_move(&numbers.into_dimension());
game.make_move(&TicTacToeMove(numbers.into_dimension()));
});

print!("{}", game);
println!("Player {:?} to move", game.player());

let mut move_scores = par_move_scores(&game);

if game.won() {
println!("Player {:?} won!", game.player().opponent());
} else if move_scores.is_empty() {
println!("No moves left! Game tied!");
} else {
move_scores.sort_by_key(|m| m.1);
move_scores.reverse();

let mut current_move_score = None;
for (game_move, score) in move_scores {
if current_move_score != Some(score) {
println!("\n\nBest moves @ score {}:", score);
current_move_score = Some(score);
}
print!("{}, ", format_dim(&game_move));
}
println!();
}
play(game);
}
86 changes: 56 additions & 30 deletions crates/games/src/tic_tac_toe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use std::{
iter::FilterMap,
};

use crate::util::state::{GameState, State};

#[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)]
enum Square {
Empty,
Expand All @@ -28,6 +30,9 @@ struct TicTacToe {
move_count: usize,
}

#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct TicTacToeMove(pub Dim<IxDynImpl>);

fn add_checked(a: Dim<IxDynImpl>, b: Vec<i32>) -> Option<Dim<IxDynImpl>> {
let mut result = a;
for (i, j) in result.as_array_view_mut().iter_mut().zip(b.iter()) {
Expand Down Expand Up @@ -87,8 +92,10 @@ impl TicTacToe {

n >= self.size
}
}

fn won(&self) -> bool {
impl GameState for TicTacToe {
fn state(&self) -> State {
// check every move
for (index, square) in self.board.indexed_iter() {
if square == &Square::Empty {
Expand All @@ -98,20 +105,25 @@ impl TicTacToe {
let point = index.into_dimension();
for offset in offsets(&point, self.size) {
if self.winning_line(&point, &offset) {
return true;
return State::Player(self.player().opponent());
}
}
}

false
// check if tie
if Some(self.move_count()) == self.max_moves() {
return State::Tie;
}

State::Continuing
}
}

impl Game for TicTacToe {
type Move = Dim<IxDynImpl>;
type Move = TicTacToeMove;
type Iter<'a> = FilterMap<
IndexedIter<'a, Square, Self::Move>,
fn((Self::Move, &Square)) -> Option<Self::Move>,
IndexedIter<'a, Square, Dim<IxDynImpl>>,
fn((Dim<IxDynImpl>, &Square)) -> Option<Self::Move>,
>;
type Player = ZeroSumPlayer;

Expand All @@ -132,14 +144,14 @@ impl Game for TicTacToe {
}

fn make_move(&mut self, m: &Self::Move) -> bool {
if *self.board.get(m.clone()).unwrap() == Square::Empty {
if *self.board.get(m.0.clone()).unwrap() == Square::Empty {
let square = if self.player() == ZeroSumPlayer::One {
Square::X
} else {
Square::O
};

*self.board.get_mut(m).unwrap() = square;
*self.board.get_mut(m.0.clone()).unwrap() = square;
self.move_count += 1;
true
} else {
Expand All @@ -152,7 +164,7 @@ impl Game for TicTacToe {
.indexed_iter()
.filter_map(move |(index, square)| {
if square == &Square::Empty {
Some(index)
Some(TicTacToeMove(index))
} else {
None
}
Expand All @@ -178,8 +190,8 @@ impl Game for TicTacToe {
// - it increases
// - it decreases
// e.g. (0, 0, 2), (0, 1, 1), (0, 2, 0) wins
for offset in offsets(m, self.size) {
if board.winning_line(m, &offset) {
for offset in offsets(&m.0, self.size) {
if board.winning_line(&m.0, &offset) {
return Some(self.player());
}
}
Expand Down Expand Up @@ -215,14 +227,16 @@ fn offsets(dim: &Dim<IxDynImpl>, size: usize) -> Vec<Vec<i32>> {
.collect()
}

fn format_dim(dim: &Dim<IxDynImpl>) -> String {
format!("{:?}", dim.as_array_view().as_slice().unwrap())
impl Display for TicTacToeMove {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0.as_array_view().as_slice().unwrap())
}
}

impl Display for TicTacToe {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
for (index, square) in self.board.indexed_iter() {
writeln!(f, "{:?} @ {}", square, format_dim(&index))?;
writeln!(f, "{:?} @ {}", square, TicTacToeMove(index))?;
}
Ok(())
}
Expand All @@ -237,13 +251,13 @@ mod tests {
fn best_moves(game: &TicTacToe) -> Option<Dim<IxDynImpl>> {
move_scores(game, &mut HashMap::new())
.max_by(|(_, a), (_, b)| a.cmp(b))
.map(|(m, _)| m)
.map(|(m, _)| m.0)
}

#[test]
fn test_middle_move() {
let mut game = TicTacToe::new(2, 3);
game.make_move(&vec![0, 0].into_dimension());
game.make_move(&TicTacToeMove(vec![0, 0].into_dimension()));

let best_move = best_moves(&game).unwrap();

Expand All @@ -261,28 +275,40 @@ mod tests {
fn test_win() {
let mut game = TicTacToe::new(2, 3);

game.make_move(&vec![0, 2].into_dimension()); // X
game.make_move(&vec![0, 1].into_dimension()); // O
game.make_move(&vec![1, 1].into_dimension()); // X
game.make_move(&vec![0, 0].into_dimension()); // O
game.make_move(&vec![2, 0].into_dimension()); // X
game.make_move(&TicTacToeMove(vec![0, 2].into_dimension())); // X
game.make_move(&TicTacToeMove(vec![0, 1].into_dimension())); // O
game.make_move(&TicTacToeMove(vec![1, 1].into_dimension())); // X
game.make_move(&TicTacToeMove(vec![0, 0].into_dimension())); // O
game.make_move(&TicTacToeMove(vec![2, 0].into_dimension())); // X

assert!(game.state() == State::Player(ZeroSumPlayer::One));
}

#[test]
fn test_no_win() {
let mut game = TicTacToe::new(2, 3);

game.make_move(&TicTacToeMove(vec![0, 2].into_dimension())); // X
game.make_move(&TicTacToeMove(vec![0, 1].into_dimension())); // O
game.make_move(&TicTacToeMove(vec![1, 1].into_dimension())); // X
game.make_move(&TicTacToeMove(vec![0, 0].into_dimension())); // O

assert!(game.won());
assert!(game.state() == State::Continuing);
}

#[test]
fn test_win_3d() {
let mut game = TicTacToe::new(3, 3);

game.make_move(&vec![0, 0, 0].into_dimension()); // X
game.make_move(&vec![0, 0, 1].into_dimension()); // O
game.make_move(&vec![0, 1, 1].into_dimension()); // X
game.make_move(&vec![0, 0, 2].into_dimension()); // O
game.make_move(&vec![0, 2, 2].into_dimension()); // X
game.make_move(&vec![0, 1, 0].into_dimension()); // O
game.make_move(&vec![0, 2, 0].into_dimension()); // X
game.make_move(&TicTacToeMove(vec![0, 0, 0].into_dimension())); // X
game.make_move(&TicTacToeMove(vec![0, 0, 1].into_dimension())); // O
game.make_move(&TicTacToeMove(vec![0, 1, 1].into_dimension())); // X
game.make_move(&TicTacToeMove(vec![0, 0, 2].into_dimension())); // O
game.make_move(&TicTacToeMove(vec![0, 2, 2].into_dimension())); // X
game.make_move(&TicTacToeMove(vec![0, 1, 0].into_dimension())); // O
game.make_move(&TicTacToeMove(vec![0, 2, 0].into_dimension())); // X

assert!(game.won());
assert!(game.state() == State::Player(ZeroSumPlayer::One));
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions crates/games/src/util/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod play;
Loading

0 comments on commit b76f37b

Please sign in to comment.