Skip to content

Commit

Permalink
Add initial Amazons implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
t4ccer committed Nov 13, 2023
1 parent 118b330 commit 2c0c131
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 11 deletions.
12 changes: 6 additions & 6 deletions src/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ fn bfs<G, T>(
y: u8,
is_non_blocking: fn(T) -> bool,
blocking_tile: T,
directions: &[(i64, i64)],
directions: &[(i32, i32)],
) -> G
where
T: Copy + Default,
Expand All @@ -166,13 +166,13 @@ where
new_grid.set(qx, qy, grid.get(qx, qy));

for (dx, dy) in directions {
let lx = (qx as i64) + dx;
let ly = (qy as i64) + dy;
let lx = (qx as i32) + dx;
let ly = (qy as i32) + dy;

if lx >= 0
&& lx < (grid.width() as i64)
&& lx < (grid.width() as i32)
&& ly >= 0
&& ly < (grid.height() as i64)
&& ly < (grid.height() as i32)
&& is_non_blocking(grid.get(lx as u8, ly as u8))
&& is_non_blocking(visited.get(lx as u8, ly as u8))
{
Expand All @@ -189,7 +189,7 @@ pub fn decompositions<G, T>(
grid: &G,
is_non_blocking: fn(T) -> bool,
blocking_tile: T,
directions: &[(i64, i64)],
directions: &[(i32, i32)],
) -> Vec<G>
where
T: Copy + Default,
Expand Down
1 change: 1 addition & 0 deletions src/short/partizan/games.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Partizan games under normal play i.e. the player that cannot move in their turn loses.
pub mod amazons;
pub mod domineering;
pub mod fission;
pub mod ski_jumps;
Expand Down
192 changes: 192 additions & 0 deletions src/short/partizan/games/amazons.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//! Amazons game
use crate::{
grid::{decompositions, move_top_left, vec_grid::VecGrid, FiniteGrid, Grid},
short::partizan::partizan_game::PartizanGame,
};
use cgt_derive::Tile;
use std::{fmt::Display, hash::Hash, str::FromStr};

/// Tile in the game of Amazons
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Tile)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Tile {
/// Empty tile without stones
#[tile(char('.'), default)]
Empty,

/// Tile with Left player's Amazon - black queen
#[tile(char('x'))]
Left,

/// Tile with Right player's Amazon - white queen
#[tile(char('o'))]
Right,

/// Stone
#[tile(char('#'))]
Stone,
}

impl Tile {
#[inline]
fn is_non_blocking(self: Tile) -> bool {
self != Tile::Stone
}
}

/// Game of Amazons
#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Amazons<G = VecGrid<Tile>> {
grid: G,
}

impl<G> Display for Amazons<G>
where
G: Grid<Item = Tile> + FiniteGrid,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.grid.display(f, '|')
}
}

impl<G> FromStr for Amazons<G>
where
G: Grid<Item = Tile> + FiniteGrid,
{
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new(G::parse(s).ok_or(())?))
}
}

const DIRECTIONS: [(i32, i32); 8] = [
(-1, 0),
(-1, 1),
(0, 1),
(1, 1),
(1, 0),
(1, -1),
(0, -1),
(-1, -1),
];

impl<G> Amazons<G>
where
G: Grid<Item = Tile> + FiniteGrid,
{
/// Create new Amazons game from a grid
#[inline]
pub fn new(grid: G) -> Self {
Amazons { grid }
}

fn moves_for(&self, own_amazon: Tile) -> Vec<Self>
where
G: Clone + PartialEq,
{
let longer_side = self.grid.height().max(self.grid.width());

let mut moves = Vec::new();
for y in 0..self.grid.height() as i32 {
for x in 0..self.grid.width() as i32 {
if self.grid.get(x as u8, y as u8) == own_amazon {
for (amazon_dir_x, amazon_dir_y) in DIRECTIONS {
for k in 1..longer_side as i32 {
let new_amazon_x = x + amazon_dir_x * k;
let new_amazon_y = y + amazon_dir_y * k;

if new_amazon_x < 0
|| new_amazon_x >= self.grid.width() as i32
|| new_amazon_y < 0
|| new_amazon_y >= self.grid.height() as i32
|| self.grid.get(new_amazon_x as u8, new_amazon_y as u8)
!= Tile::Empty
{
break;
}
let mut new_grid = self.grid.clone();
new_grid.set(x as u8, y as u8, Tile::Empty);
new_grid.set(new_amazon_x as u8, new_amazon_y as u8, own_amazon);
for (arrow_dir_x, arrow_dir_y) in DIRECTIONS {
for l in 1..longer_side as i32 {
let new_arrow_x = new_amazon_x + arrow_dir_x * l;
let new_arrow_y = new_amazon_y + arrow_dir_y * l;

if new_arrow_x < 0
|| new_arrow_x >= new_grid.width() as i32
|| new_arrow_y < 0
|| new_arrow_y >= new_grid.height() as i32
|| new_grid.get(new_arrow_x as u8, new_arrow_y as u8)
!= Tile::Empty
{
break;
}
let mut new_grid = new_grid.clone();
new_grid.set(new_arrow_x as u8, new_arrow_y as u8, Tile::Stone);
let new_grid = move_top_left(&new_grid, Tile::is_non_blocking);
moves.push(Self::new(new_grid));
}
}
}
}
}
}
}

moves
}
}

impl<G> PartizanGame for Amazons<G>
where
G: Grid<Item = Tile> + FiniteGrid + Clone + Hash + Send + Sync + Eq,
{
fn left_moves(&self) -> Vec<Self> {
self.moves_for(Tile::Left)
}

fn right_moves(&self) -> Vec<Self> {
self.moves_for(Tile::Right)
}

fn decompositions(&self) -> Vec<Self> {
decompositions(&self.grid, Tile::is_non_blocking, Tile::Stone, &DIRECTIONS)
.into_iter()
.map(Self::new)
.collect::<Vec<_>>()
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::short::partizan::{
canonical_form::CanonicalForm, transposition_table::TranspositionTable,
};
use std::str::FromStr;

macro_rules! amazons {
($input:expr) => {
Amazons::from_str($input).expect("Could not parse the game")
};
}

macro_rules! test_canonical_form {
($input:expr, $output:expr) => {{
let tt = TranspositionTable::new();
let pos: Amazons = amazons!($input);
let cf = pos.canonical_form(&tt);
let expected = CanonicalForm::from_str($output).unwrap().to_string();
assert_eq!(cf.to_string(), expected);
}};
}

#[test]
fn canonical_form() {
// Confirmed with cgsuite
test_canonical_form!("x..#|....|.#.o", "{{6|{3|1, {3|0, {1/2|0}}}}, {6|{4*|-3, {3, {3|0, {1/2|0}}|-4}}}|-3, {0, {0|-2}, {1|-3}|-5}, {0, {0, *|0, {0, {1/2, {1|0}|v}|v}}|-5}, {{2, {2|0}|0, {0, {2|0, {2|0}}|0}}, {2, {3|0}|0, {0, {0, ^*|0}|-1}}, {{2|0}, {2|{1|1/4}, {2|0}}|v*, {1/2|{{0|-1}, {*|-1}|-1}}, {{0, ^*|0}|-1}}, {{3|0}, {3|1, {2|0}}, {3, {3|1}|1, {1|0, *}}|-1/16, {0|-1}, {*|-1}}|-5, {v, v*, {0, {0, ^*|0}|-1}|-5}, {{1/2|{-1/4, {0|-1}, {*|-1}|-1}}, {{1|1/4}|{-1/4|-1}}, {{1|{1|0}, {1|*}}|-1/2}|-5}}}");
}
}
10 changes: 5 additions & 5 deletions src/short/partizan/games/fission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ where
&& self.grid.get(prev_x, prev_y) == Tile::Empty
&& self.grid.get(next_x, next_y) == Tile::Empty
{
let mut new_grid: Self = self.clone();
new_grid.grid.set(x, y, Tile::Empty);
new_grid.grid.set(prev_x, prev_y, Tile::Stone);
new_grid.grid.set(next_x, next_y, Tile::Stone);
moves.push(new_grid);
let mut new_grid = self.clone().grid;
new_grid.set(x, y, Tile::Empty);
new_grid.set(prev_x, prev_y, Tile::Stone);
new_grid.set(next_x, next_y, Tile::Stone);
moves.push(Self::new(new_grid));
}
}
}
Expand Down

0 comments on commit 2c0c131

Please sign in to comment.