Overhaul the board representation
Groups become more first-class objects, where the grid becomes useful for quick lookups of values.
This commit is contained in:
parent
52e27318fe
commit
78af31f6f9
|
@ -26,6 +26,15 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grid"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0634107a3a005070dd73e27e74ecb691a94e9e5ba7829f434db7fbf73a6b5c47"
|
||||||
|
dependencies = [
|
||||||
|
"no-std-compat",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
@ -39,6 +48,7 @@ dependencies = [
|
||||||
name = "kifu-core"
|
name = "kifu-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"grid",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -85,6 +95,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "no-std-compat"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.26", features = [ "full" ] }
|
tokio = { version = "1.26", features = [ "full" ] }
|
||||||
|
grid = { version = "0.9" }
|
||||||
|
|
|
@ -10,8 +10,8 @@ pub enum Request {
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct PlayStoneRequest {
|
pub struct PlayStoneRequest {
|
||||||
pub column: u8,
|
pub column: usize,
|
||||||
pub row: u8,
|
pub row: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -0,0 +1,611 @@
|
||||||
|
use crate::{Color, Size};
|
||||||
|
use grid::Grid;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
InvalidPosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Board {
|
||||||
|
pub size: Size,
|
||||||
|
pub grid: Grid<Option<Color>>,
|
||||||
|
pub groups: Vec<Group>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Board {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
|
print!(" ");
|
||||||
|
// for c in 'A'..'U' {
|
||||||
|
for c in 0..19 {
|
||||||
|
print!("{:2}", c);
|
||||||
|
}
|
||||||
|
println!("");
|
||||||
|
|
||||||
|
for row in 0..self.size.height {
|
||||||
|
print!(" {:2}", row);
|
||||||
|
for column in 0..self.size.width {
|
||||||
|
match self.stone(&Coordinate { column, row }) {
|
||||||
|
None => write!(f, " .")?,
|
||||||
|
Some(Color::Black) => write!(f, " X")?,
|
||||||
|
Some(Color::White) => write!(f, " O")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(f, "")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Board {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
size: Size {
|
||||||
|
width: 19,
|
||||||
|
height: 19,
|
||||||
|
},
|
||||||
|
grid: Grid::new(19, 19),
|
||||||
|
groups: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_coordinates(coordinates: impl Iterator<Item = (Coordinate, Color)>) -> Self {
|
||||||
|
let mut s = Self::new();
|
||||||
|
for (coordinate, color) in coordinates {
|
||||||
|
s.place_stone(coordinate, color);
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
|
||||||
|
pub struct Coordinate {
|
||||||
|
pub column: usize,
|
||||||
|
pub row: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Board {
|
||||||
|
pub fn place_stone(&mut self, coordinate: Coordinate, color: Color) -> Result<(), Error> {
|
||||||
|
if let Some(_) = self.stone(&coordinate) {
|
||||||
|
return Err(Error::InvalidPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut friendly_group = self
|
||||||
|
.adjacencies(&coordinate)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|c| self.stone(c) == Some(color))
|
||||||
|
.filter_map(|c| self.group(&c).map(|g| g.coordinates.clone()))
|
||||||
|
.fold(HashSet::new(), |acc, set| {
|
||||||
|
acc.union(&set).cloned().collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
friendly_group.insert(coordinate.clone());
|
||||||
|
|
||||||
|
self.groups
|
||||||
|
.retain(|g| g.coordinates.is_disjoint(&friendly_group));
|
||||||
|
let friendly_group = Group {
|
||||||
|
color,
|
||||||
|
coordinates: friendly_group,
|
||||||
|
};
|
||||||
|
self.groups.push(friendly_group.clone());
|
||||||
|
|
||||||
|
match self.grid.get_mut(coordinate.row, coordinate.column) {
|
||||||
|
None => return Err(Error::InvalidPosition),
|
||||||
|
Some(space) => *space = Some(color),
|
||||||
|
}
|
||||||
|
|
||||||
|
let adjacent_groups = self.adjacent_groups(&friendly_group);
|
||||||
|
for group in adjacent_groups {
|
||||||
|
if self.liberties(&group) == 0 {
|
||||||
|
self.remove_group(&group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stone(&self, coordinate: &Coordinate) -> Option<Color> {
|
||||||
|
match self.grid.get(coordinate.row, coordinate.column) {
|
||||||
|
None => None,
|
||||||
|
Some(val) => *val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn group(&self, coordinate: &Coordinate) -> Option<&Group> {
|
||||||
|
self.groups
|
||||||
|
.iter()
|
||||||
|
.find(|g| g.coordinates.contains(coordinate))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_group(&mut self, group: &Group) {
|
||||||
|
self.groups.retain(|g| g != group);
|
||||||
|
for coord in group.coordinates.iter() {
|
||||||
|
match self.grid.get_mut(coord.row, coord.column) {
|
||||||
|
None => (),
|
||||||
|
Some(v) => *v = None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn adjacent_groups(&self, group: &Group) -> Vec<Group> {
|
||||||
|
let adjacent_spaces = self.group_halo(group).into_iter();
|
||||||
|
println!("adjacent spaces: {:?}", adjacent_spaces);
|
||||||
|
let mut grps: Vec<Group> = Vec::new();
|
||||||
|
|
||||||
|
adjacent_spaces.for_each(|coord| match self.group(&coord) {
|
||||||
|
None => return,
|
||||||
|
Some(adj) => {
|
||||||
|
println!("group found: {:?}", adj.color);
|
||||||
|
if group.color == adj.color {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if grps.iter().any(|g| g.coordinates.contains(&coord)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grps.push(adj.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
grps
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn group_halo(&self, group: &Group) -> HashSet<Coordinate> {
|
||||||
|
group
|
||||||
|
.coordinates
|
||||||
|
.iter()
|
||||||
|
.map(|c| self.adjacencies(c))
|
||||||
|
.flatten()
|
||||||
|
.collect::<HashSet<Coordinate>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn liberties(&self, group: &Group) -> usize {
|
||||||
|
self.group_halo(group)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|c| self.stone(&c) == None)
|
||||||
|
.count()
|
||||||
|
|
||||||
|
// let adjacencies: HashSet<Coordinate> = self.adjacencies(group.coordinates.iter()).collect();
|
||||||
|
/*
|
||||||
|
println!("adjacencies: {:?}", adjacencies);
|
||||||
|
let opposing_spaces = adjacencies
|
||||||
|
.iter()
|
||||||
|
.filter(|coord| match self.grid.get(coord.row, coord.column) {
|
||||||
|
None => true,
|
||||||
|
Some(&None) => false,
|
||||||
|
Some(&Some(c)) => c != group.color,
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect::<HashSet<Coordinate>>();
|
||||||
|
println!("opposition: {:?}", opposing_spaces);
|
||||||
|
|
||||||
|
adjacencies.len() - opposing_spaces.len()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
group
|
||||||
|
.adjacencies(self.size.width as u8 - 1, self.size.height as u8 - 1)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|coordinate| self.stone(*coordinate).is_none())
|
||||||
|
.collect::<Vec<Coordinate>>()
|
||||||
|
.len()
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn adjacencies(&self, coordinate: &Coordinate) -> Vec<Coordinate> {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
if coordinate.column > 0 {
|
||||||
|
v.push(Coordinate {
|
||||||
|
column: coordinate.column - 1,
|
||||||
|
row: coordinate.row,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if coordinate.row > 0 {
|
||||||
|
v.push(Coordinate {
|
||||||
|
column: coordinate.column,
|
||||||
|
row: coordinate.row - 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
v.push(Coordinate {
|
||||||
|
column: coordinate.column + 1,
|
||||||
|
row: coordinate.row,
|
||||||
|
});
|
||||||
|
v.push(Coordinate {
|
||||||
|
column: coordinate.column,
|
||||||
|
row: coordinate.row + 1,
|
||||||
|
});
|
||||||
|
v.into_iter().filter(|c| self.within_board(c)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
pub fn adjacencies<'a>(
|
||||||
|
&'a self,
|
||||||
|
coordinates: impl Iterator<Item = &'a Coordinate> + 'a,
|
||||||
|
) -> impl Iterator<Item = Coordinate> + 'a {
|
||||||
|
coordinates
|
||||||
|
.map(|coordinate| {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
if coordinate.column > 0 {
|
||||||
|
v.push(Coordinate {
|
||||||
|
column: coordinate.column - 1,
|
||||||
|
row: coordinate.row,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if coordinate.row > 0 {
|
||||||
|
v.push(Coordinate {
|
||||||
|
column: coordinate.column,
|
||||||
|
row: coordinate.row - 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
v.push(Coordinate {
|
||||||
|
column: coordinate.column + 1,
|
||||||
|
row: coordinate.row,
|
||||||
|
});
|
||||||
|
v.push(Coordinate {
|
||||||
|
column: coordinate.column,
|
||||||
|
row: coordinate.row + 1,
|
||||||
|
});
|
||||||
|
v
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.filter(|coordinate| self.within_board(coordinate))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub fn within_board(&self, coordinate: &Coordinate) -> bool {
|
||||||
|
coordinate.column < self.size.width && coordinate.row < self.size.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Group {
|
||||||
|
color: Color,
|
||||||
|
coordinates: HashSet<Coordinate>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
impl Group {
|
||||||
|
pub fn adjacencies(&self, max_column: usize, max_row: usize) -> HashSet<Coordinate> {
|
||||||
|
self.coordinates
|
||||||
|
.iter()
|
||||||
|
.map(|stone| stone.adjacencies(max_column, max_row))
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/* Two players (Black and White) take turns and Black plays first
|
||||||
|
* Stones are placed on the line intersections and not moved.
|
||||||
|
* A stone with no liberties is removed from the board.
|
||||||
|
* A group of stones of the same color share liberties.
|
||||||
|
* A stone at the edge of the board has only three liberties.
|
||||||
|
* A stone at the corner of the board has only two liberties.
|
||||||
|
* A stone may not be placed in a suicidal position.
|
||||||
|
* A stone placed in a suicidal position is legal if it captures other stones first.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn with_example_board(test: impl FnOnce(Board)) {
|
||||||
|
let board = Board::from_coordinates(
|
||||||
|
vec![
|
||||||
|
(Coordinate { column: 3, row: 3 }, Color::White),
|
||||||
|
(Coordinate { column: 3, row: 4 }, Color::White),
|
||||||
|
/* */
|
||||||
|
(Coordinate { column: 8, row: 3 }, Color::Black),
|
||||||
|
(Coordinate { column: 9, row: 3 }, Color::Black),
|
||||||
|
(Coordinate { column: 9, row: 4 }, Color::Black),
|
||||||
|
/* */
|
||||||
|
(Coordinate { column: 15, row: 3 }, Color::White),
|
||||||
|
(Coordinate { column: 15, row: 4 }, Color::White),
|
||||||
|
(Coordinate { column: 15, row: 5 }, Color::White),
|
||||||
|
(Coordinate { column: 14, row: 4 }, Color::White),
|
||||||
|
/* */
|
||||||
|
(Coordinate { column: 3, row: 8 }, Color::White),
|
||||||
|
(Coordinate { column: 3, row: 9 }, Color::White),
|
||||||
|
(Coordinate { column: 4, row: 9 }, Color::White),
|
||||||
|
(Coordinate { column: 3, row: 10 }, Color::Black),
|
||||||
|
/* */
|
||||||
|
(Coordinate { column: 0, row: 0 }, Color::White),
|
||||||
|
(Coordinate { column: 1, row: 0 }, Color::White),
|
||||||
|
(Coordinate { column: 0, row: 1 }, Color::White),
|
||||||
|
/* */
|
||||||
|
(Coordinate { column: 9, row: 9 }, Color::White),
|
||||||
|
(Coordinate { column: 8, row: 9 }, Color::Black),
|
||||||
|
(Coordinate { column: 9, row: 8 }, Color::Black),
|
||||||
|
(Coordinate { column: 9, row: 10 }, Color::Black),
|
||||||
|
/* */
|
||||||
|
(Coordinate { column: 0, row: 17 }, Color::White),
|
||||||
|
(Coordinate { column: 1, row: 18 }, Color::White),
|
||||||
|
(Coordinate { column: 0, row: 18 }, Color::White),
|
||||||
|
(Coordinate { column: 0, row: 16 }, Color::Black),
|
||||||
|
(Coordinate { column: 1, row: 17 }, Color::Black),
|
||||||
|
/* */
|
||||||
|
(Coordinate { column: 4, row: 17 }, Color::Black),
|
||||||
|
(Coordinate { column: 5, row: 17 }, Color::Black),
|
||||||
|
(Coordinate { column: 6, row: 17 }, Color::Black),
|
||||||
|
(Coordinate { column: 4, row: 18 }, Color::Black),
|
||||||
|
(Coordinate { column: 6, row: 18 }, Color::Black),
|
||||||
|
(Coordinate { column: 3, row: 17 }, Color::White),
|
||||||
|
(Coordinate { column: 3, row: 18 }, Color::White),
|
||||||
|
(Coordinate { column: 4, row: 16 }, Color::White),
|
||||||
|
(Coordinate { column: 5, row: 16 }, Color::White),
|
||||||
|
(Coordinate { column: 6, row: 16 }, Color::White),
|
||||||
|
(Coordinate { column: 7, row: 17 }, Color::White),
|
||||||
|
(Coordinate { column: 7, row: 18 }, Color::White),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
);
|
||||||
|
test(board);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_gets_adjacencies_for_coordinate() {
|
||||||
|
let board = Board::new();
|
||||||
|
for column in 0..19 {
|
||||||
|
for row in 0..19 {
|
||||||
|
for coordinate in board.adjacencies(&Coordinate { column, row }) {
|
||||||
|
assert!(
|
||||||
|
board.within_board(&coordinate),
|
||||||
|
"{} {}: {:?}",
|
||||||
|
column,
|
||||||
|
row,
|
||||||
|
coordinate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_counts_individual_liberties() {
|
||||||
|
let board = Board::from_coordinates(
|
||||||
|
vec![
|
||||||
|
(Coordinate { column: 3, row: 3 }, Color::White),
|
||||||
|
(Coordinate { column: 0, row: 3 }, Color::White),
|
||||||
|
(Coordinate { column: 0, row: 0 }, Color::White),
|
||||||
|
(Coordinate { column: 18, row: 9 }, Color::Black),
|
||||||
|
(
|
||||||
|
Coordinate {
|
||||||
|
column: 18,
|
||||||
|
row: 18,
|
||||||
|
},
|
||||||
|
Color::Black,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
);
|
||||||
|
assert!(board.group(&Coordinate { column: 18, row: 3 }).is_none());
|
||||||
|
assert_eq!(
|
||||||
|
board
|
||||||
|
.group(&Coordinate { column: 3, row: 3 })
|
||||||
|
.map(|g| board.liberties(&g)),
|
||||||
|
Some(4)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
board
|
||||||
|
.group(&Coordinate { column: 0, row: 3 })
|
||||||
|
.map(|g| board.liberties(&g)),
|
||||||
|
Some(3)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
board
|
||||||
|
.group(&Coordinate { column: 0, row: 0 })
|
||||||
|
.map(|g| board.liberties(&g)),
|
||||||
|
Some(2)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
board
|
||||||
|
.group(&Coordinate { column: 18, row: 9 })
|
||||||
|
.map(|g| board.liberties(&g)),
|
||||||
|
Some(3)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
board
|
||||||
|
.group(&Coordinate {
|
||||||
|
column: 18,
|
||||||
|
row: 18
|
||||||
|
})
|
||||||
|
.map(|g| board.liberties(&g)),
|
||||||
|
Some(2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stones_share_liberties() {
|
||||||
|
with_example_board(|board: Board| {
|
||||||
|
let test_cases = vec![
|
||||||
|
(
|
||||||
|
board.clone(),
|
||||||
|
Coordinate { column: 0, row: 0 },
|
||||||
|
Some(Group {
|
||||||
|
color: Color::White,
|
||||||
|
coordinates: vec![
|
||||||
|
Coordinate { column: 0, row: 0 },
|
||||||
|
Coordinate { column: 1, row: 0 },
|
||||||
|
Coordinate { column: 0, row: 1 },
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Some(3),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
board.clone(),
|
||||||
|
Coordinate { column: 1, row: 0 },
|
||||||
|
Some(Group {
|
||||||
|
color: Color::White,
|
||||||
|
coordinates: vec![
|
||||||
|
Coordinate { column: 0, row: 0 },
|
||||||
|
Coordinate { column: 1, row: 0 },
|
||||||
|
Coordinate { column: 0, row: 1 },
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Some(3),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
board.clone(),
|
||||||
|
Coordinate { column: 9, row: 9 },
|
||||||
|
Some(Group {
|
||||||
|
color: Color::White,
|
||||||
|
coordinates: vec![Coordinate { column: 9, row: 9 }].into_iter().collect(),
|
||||||
|
}),
|
||||||
|
Some(1),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
board.clone(),
|
||||||
|
Coordinate { column: 3, row: 4 },
|
||||||
|
Some(Group {
|
||||||
|
color: Color::White,
|
||||||
|
coordinates: vec![
|
||||||
|
Coordinate { column: 3, row: 3 },
|
||||||
|
Coordinate { column: 3, row: 4 },
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Some(6),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
board.clone(),
|
||||||
|
Coordinate { column: 9, row: 3 },
|
||||||
|
Some(Group {
|
||||||
|
color: Color::Black,
|
||||||
|
coordinates: vec![
|
||||||
|
Coordinate { column: 8, row: 3 },
|
||||||
|
Coordinate { column: 9, row: 3 },
|
||||||
|
Coordinate { column: 9, row: 4 },
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Some(7),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
board.clone(),
|
||||||
|
Coordinate { column: 15, row: 4 },
|
||||||
|
Some(Group {
|
||||||
|
color: Color::White,
|
||||||
|
coordinates: vec![
|
||||||
|
Coordinate { column: 15, row: 3 },
|
||||||
|
Coordinate { column: 15, row: 4 },
|
||||||
|
Coordinate { column: 15, row: 5 },
|
||||||
|
Coordinate { column: 14, row: 4 },
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Some(8),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
board.clone(),
|
||||||
|
Coordinate { column: 3, row: 9 },
|
||||||
|
Some(Group {
|
||||||
|
color: Color::White,
|
||||||
|
coordinates: vec![
|
||||||
|
Coordinate { column: 3, row: 8 },
|
||||||
|
Coordinate { column: 3, row: 9 },
|
||||||
|
Coordinate { column: 4, row: 9 },
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Some(6),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
board.clone(),
|
||||||
|
Coordinate { column: 0, row: 18 },
|
||||||
|
Some(Group {
|
||||||
|
color: Color::White,
|
||||||
|
coordinates: vec![
|
||||||
|
Coordinate { column: 0, row: 17 },
|
||||||
|
Coordinate { column: 0, row: 18 },
|
||||||
|
Coordinate { column: 1, row: 18 },
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Some(1),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
board.clone(),
|
||||||
|
Coordinate { column: 0, row: 17 },
|
||||||
|
Some(Group {
|
||||||
|
color: Color::White,
|
||||||
|
coordinates: vec![
|
||||||
|
Coordinate { column: 0, row: 17 },
|
||||||
|
Coordinate { column: 0, row: 18 },
|
||||||
|
Coordinate { column: 1, row: 18 },
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Some(1),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
println!("{}", board);
|
||||||
|
for (board, coordinate, group, liberties) in test_cases {
|
||||||
|
assert_eq!(board.group(&coordinate), group.as_ref());
|
||||||
|
assert_eq!(
|
||||||
|
board.group(&coordinate).map(|g| board.liberties(&g)),
|
||||||
|
liberties,
|
||||||
|
"{:?}",
|
||||||
|
coordinate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_finds_adjacent_groups() {
|
||||||
|
with_example_board(|mut board| {
|
||||||
|
println!("{}", board);
|
||||||
|
let group = board
|
||||||
|
.group(&Coordinate { column: 0, row: 0 })
|
||||||
|
.cloned()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(board.adjacent_groups(&group), Vec::new());
|
||||||
|
|
||||||
|
let group = board
|
||||||
|
.group(&Coordinate { column: 3, row: 10 })
|
||||||
|
.cloned()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(board.adjacent_groups(&group).len(), 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn surrounding_a_group_removes_it() {
|
||||||
|
with_example_board(|mut board| {
|
||||||
|
board.place_stone(Coordinate { column: 10, row: 9 }, Color::Black);
|
||||||
|
assert!(board.stone(&Coordinate { column: 9, row: 9 }).is_none());
|
||||||
|
|
||||||
|
board.place_stone(Coordinate { column: 2, row: 18 }, Color::Black);
|
||||||
|
assert!(board.stone(&Coordinate { column: 0, row: 18 }).is_none());
|
||||||
|
assert!(board.stone(&Coordinate { column: 1, row: 18 }).is_none());
|
||||||
|
assert!(board.stone(&Coordinate { column: 0, row: 17 }).is_none());
|
||||||
|
assert!(board.group(&Coordinate { column: 0, row: 18 }).is_none());
|
||||||
|
|
||||||
|
board.place_stone(Coordinate { column: 5, row: 18 }, Color::White);
|
||||||
|
assert!(board.stone(&Coordinate { column: 4, row: 17 }).is_none());
|
||||||
|
assert!(board.stone(&Coordinate { column: 5, row: 17 }).is_none());
|
||||||
|
assert!(board.stone(&Coordinate { column: 6, row: 17 }).is_none());
|
||||||
|
assert!(board.stone(&Coordinate { column: 4, row: 18 }).is_none());
|
||||||
|
assert!(board.stone(&Coordinate { column: 6, row: 18 }).is_none());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn suicide_is_forbidden() {
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn captures_preceed_self_capture() {
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,3 +4,6 @@ pub use api::{CoreApp, Request, Response};
|
||||||
mod types;
|
mod types;
|
||||||
pub use types::{Color, Size};
|
pub use types::{Color, Size};
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
|
mod board;
|
||||||
|
pub use board::*;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use crate::api::PlayStoneRequest;
|
use crate::{
|
||||||
use std::{collections::HashSet, time::Duration};
|
api::PlayStoneRequest,
|
||||||
|
board::{Board, Coordinate},
|
||||||
|
};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
|
@ -7,10 +10,10 @@ pub enum Color {
|
||||||
White,
|
White,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct Size {
|
pub struct Size {
|
||||||
pub width: u8,
|
pub width: usize,
|
||||||
pub height: u8,
|
pub height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Size {
|
impl Default for Size {
|
||||||
|
@ -114,499 +117,3 @@ impl GameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
|
|
||||||
pub struct Coordinate {
|
|
||||||
pub column: u8,
|
|
||||||
pub row: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Coordinate {
|
|
||||||
fn within_board(&self, width: u8, height: u8) -> bool {
|
|
||||||
self.column < width && self.row < height
|
|
||||||
}
|
|
||||||
|
|
||||||
fn adjacencies(&self, width: u8, height: u8) -> impl Iterator<Item = Coordinate> {
|
|
||||||
vec![
|
|
||||||
if self.column > 0 {
|
|
||||||
Some(Coordinate {
|
|
||||||
column: self.column - 1,
|
|
||||||
row: self.row,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
if self.row > 0 {
|
|
||||||
Some(Coordinate {
|
|
||||||
column: self.column,
|
|
||||||
row: self.row - 1,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
if self.column < width - 1 {
|
|
||||||
Some(Coordinate {
|
|
||||||
column: self.column + 1,
|
|
||||||
row: self.row,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
if self.row < height - 1 {
|
|
||||||
Some(Coordinate {
|
|
||||||
column: self.column,
|
|
||||||
row: self.row + 1,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|v| v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Board {
|
|
||||||
pub size: Size,
|
|
||||||
pub spaces: Vec<Option<Color>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Board {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
|
||||||
print!(" ");
|
|
||||||
// for c in 'A'..'U' {
|
|
||||||
for c in 0..19 {
|
|
||||||
print!("{:2}", c);
|
|
||||||
}
|
|
||||||
println!("");
|
|
||||||
|
|
||||||
for row in 0..self.size.height {
|
|
||||||
print!(" {:2}", row);
|
|
||||||
for column in 0..self.size.width {
|
|
||||||
match self.stone(Coordinate { column, row }) {
|
|
||||||
None => write!(f, " .")?,
|
|
||||||
Some(Color::Black) => write!(f, " X")?,
|
|
||||||
Some(Color::White) => write!(f, " O")?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeln!(f, "")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Board {
|
|
||||||
fn new() -> Self {
|
|
||||||
let mut spaces = Vec::new();
|
|
||||||
for _ in 0..19 * 19 {
|
|
||||||
spaces.push(None);
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
size: Size {
|
|
||||||
width: 19,
|
|
||||||
height: 19,
|
|
||||||
},
|
|
||||||
spaces,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_coordinates(coordinates: impl Iterator<Item = (Coordinate, Color)>) -> Self {
|
|
||||||
let mut s = Self::new();
|
|
||||||
for (coordinate, color) in coordinates {
|
|
||||||
s.place_stone(coordinate, color);
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn place_stone(&mut self, coordinate: Coordinate, stone: Color) {
|
|
||||||
let addr = self.addr(coordinate.column, coordinate.row);
|
|
||||||
self.spaces[addr] = Some(stone);
|
|
||||||
|
|
||||||
let mut to_remove = HashSet::new();
|
|
||||||
let mut visited = HashSet::new();
|
|
||||||
|
|
||||||
for adjacency in coordinate.adjacencies(self.size.width, self.size.height) {
|
|
||||||
if visited.contains(&adjacency) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(group) = self.group(adjacency) {
|
|
||||||
if self.liberties(&group) == 0 {
|
|
||||||
to_remove = to_remove.union(&group.coordinates).cloned().collect()
|
|
||||||
}
|
|
||||||
visited = visited.union(&group.coordinates).cloned().collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for coordinate in to_remove {
|
|
||||||
let addr = self.addr(coordinate.column, coordinate.row);
|
|
||||||
self.spaces[addr] = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stone(&self, coordinate: Coordinate) -> Option<Color> {
|
|
||||||
let addr = self.addr(coordinate.column, coordinate.row);
|
|
||||||
self.spaces[addr]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn group(&self, coordinate: Coordinate) -> Option<Group> {
|
|
||||||
let mut visited: HashSet<Coordinate> = HashSet::new();
|
|
||||||
|
|
||||||
if let Some(color) = self.stone(coordinate) {
|
|
||||||
let mut fringes = coordinate
|
|
||||||
.adjacencies(self.size.width as u8 - 1, self.size.height as u8 - 1)
|
|
||||||
.collect::<Vec<Coordinate>>();
|
|
||||||
fringes
|
|
||||||
.iter()
|
|
||||||
.for_each(|coordinate| println!("group fringes: {:?}", coordinate));
|
|
||||||
visited.insert(coordinate);
|
|
||||||
|
|
||||||
while let Some(coordinate) = fringes.pop() {
|
|
||||||
if self.stone(coordinate) == Some(color) {
|
|
||||||
if !visited.contains(&coordinate) {
|
|
||||||
fringes.append(
|
|
||||||
&mut coordinate
|
|
||||||
.adjacencies(self.size.width as u8 - 1, self.size.height as u8 - 1)
|
|
||||||
.collect::<Vec<Coordinate>>(),
|
|
||||||
);
|
|
||||||
visited.insert(coordinate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Group {
|
|
||||||
color,
|
|
||||||
coordinates: visited,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn liberties(&self, group: &Group) -> usize {
|
|
||||||
group
|
|
||||||
.adjacencies(self.size.width as u8 - 1, self.size.height as u8 - 1)
|
|
||||||
.into_iter()
|
|
||||||
.filter(|coordinate| self.stone(*coordinate).is_none())
|
|
||||||
.collect::<Vec<Coordinate>>()
|
|
||||||
.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addr(&self, column: u8, row: u8) -> usize {
|
|
||||||
let addr = ((row as usize) * (self.size.width as usize) + (column as usize)) as usize;
|
|
||||||
if addr >= 361 {
|
|
||||||
panic!("invalid address: {}, {} {}", addr, column, row);
|
|
||||||
}
|
|
||||||
addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Group {
|
|
||||||
color: Color,
|
|
||||||
coordinates: HashSet<Coordinate>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Group {
|
|
||||||
fn adjacencies(&self, max_column: u8, max_row: u8) -> HashSet<Coordinate> {
|
|
||||||
self.coordinates
|
|
||||||
.iter()
|
|
||||||
.map(|stone| stone.adjacencies(max_column, max_row))
|
|
||||||
.flatten()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/* Two players (Black and White) take turns and Black plays first
|
|
||||||
* Stones are placed on the line intersections and not moved.
|
|
||||||
* A stone with no liberties is removed from the board.
|
|
||||||
* A group of stones of the same color share liberties.
|
|
||||||
* A stone at the edge of the board has only three liberties.
|
|
||||||
* A stone at the corner of the board has only two liberties.
|
|
||||||
* A stone may not be placed in a suicidal position.
|
|
||||||
* A stone placed in a suicidal position is legal if it captures other stones first.
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn with_example_board(test: impl FnOnce(Board)) {
|
|
||||||
let board = Board::from_coordinates(
|
|
||||||
vec![
|
|
||||||
(Coordinate { column: 3, row: 3 }, Color::White),
|
|
||||||
(Coordinate { column: 3, row: 4 }, Color::White),
|
|
||||||
/* */
|
|
||||||
(Coordinate { column: 8, row: 3 }, Color::Black),
|
|
||||||
(Coordinate { column: 9, row: 3 }, Color::Black),
|
|
||||||
(Coordinate { column: 9, row: 4 }, Color::Black),
|
|
||||||
/* */
|
|
||||||
(Coordinate { column: 15, row: 3 }, Color::White),
|
|
||||||
(Coordinate { column: 15, row: 4 }, Color::White),
|
|
||||||
(Coordinate { column: 15, row: 5 }, Color::White),
|
|
||||||
(Coordinate { column: 14, row: 4 }, Color::White),
|
|
||||||
/* */
|
|
||||||
(Coordinate { column: 3, row: 8 }, Color::White),
|
|
||||||
(Coordinate { column: 3, row: 9 }, Color::White),
|
|
||||||
(Coordinate { column: 4, row: 9 }, Color::White),
|
|
||||||
(Coordinate { column: 3, row: 10 }, Color::Black),
|
|
||||||
/* */
|
|
||||||
(Coordinate { column: 0, row: 0 }, Color::White),
|
|
||||||
(Coordinate { column: 1, row: 0 }, Color::White),
|
|
||||||
(Coordinate { column: 0, row: 1 }, Color::White),
|
|
||||||
/* */
|
|
||||||
(Coordinate { column: 9, row: 9 }, Color::White),
|
|
||||||
(Coordinate { column: 8, row: 9 }, Color::Black),
|
|
||||||
(Coordinate { column: 9, row: 8 }, Color::Black),
|
|
||||||
(Coordinate { column: 9, row: 10 }, Color::Black),
|
|
||||||
/* */
|
|
||||||
(Coordinate { column: 0, row: 17 }, Color::White),
|
|
||||||
(Coordinate { column: 1, row: 18 }, Color::White),
|
|
||||||
(Coordinate { column: 0, row: 18 }, Color::White),
|
|
||||||
(Coordinate { column: 0, row: 16 }, Color::Black),
|
|
||||||
(Coordinate { column: 1, row: 17 }, Color::Black),
|
|
||||||
]
|
|
||||||
.into_iter(),
|
|
||||||
);
|
|
||||||
test(board);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_gets_adjacencies_for_a_coordinate() {
|
|
||||||
for column in 0..19 {
|
|
||||||
for row in 0..19 {
|
|
||||||
for coordinate in (Coordinate { column, row }.adjacencies(19, 19)) {
|
|
||||||
assert!(
|
|
||||||
coordinate.within_board(19, 19),
|
|
||||||
"{} {}: {:?}",
|
|
||||||
column,
|
|
||||||
row,
|
|
||||||
coordinate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_counts_individual_liberties() {
|
|
||||||
let board = Board::from_coordinates(
|
|
||||||
vec![
|
|
||||||
(Coordinate { column: 3, row: 3 }, Color::White),
|
|
||||||
(Coordinate { column: 0, row: 3 }, Color::White),
|
|
||||||
(Coordinate { column: 0, row: 0 }, Color::White),
|
|
||||||
(Coordinate { column: 18, row: 9 }, Color::Black),
|
|
||||||
(
|
|
||||||
Coordinate {
|
|
||||||
column: 18,
|
|
||||||
row: 18,
|
|
||||||
},
|
|
||||||
Color::Black,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
.into_iter(),
|
|
||||||
);
|
|
||||||
assert!(board.group(Coordinate { column: 18, row: 3 }).is_none());
|
|
||||||
assert_eq!(
|
|
||||||
board
|
|
||||||
.group(Coordinate { column: 3, row: 3 })
|
|
||||||
.map(|g| board.liberties(&g)),
|
|
||||||
Some(4)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
board
|
|
||||||
.group(Coordinate { column: 0, row: 3 })
|
|
||||||
.map(|g| board.liberties(&g)),
|
|
||||||
Some(3)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
board
|
|
||||||
.group(Coordinate { column: 0, row: 0 })
|
|
||||||
.map(|g| board.liberties(&g)),
|
|
||||||
Some(2)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
board
|
|
||||||
.group(Coordinate { column: 18, row: 9 })
|
|
||||||
.map(|g| board.liberties(&g)),
|
|
||||||
Some(3)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
board
|
|
||||||
.group(Coordinate {
|
|
||||||
column: 18,
|
|
||||||
row: 18
|
|
||||||
})
|
|
||||||
.map(|g| board.liberties(&g)),
|
|
||||||
Some(2)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn stones_share_liberties() {
|
|
||||||
with_example_board(|board: Board| {
|
|
||||||
let test_cases = vec![
|
|
||||||
(
|
|
||||||
board.clone(),
|
|
||||||
Coordinate { column: 0, row: 0 },
|
|
||||||
Some(Group {
|
|
||||||
color: Color::White,
|
|
||||||
coordinates: vec![
|
|
||||||
Coordinate { column: 0, row: 0 },
|
|
||||||
Coordinate { column: 1, row: 0 },
|
|
||||||
Coordinate { column: 0, row: 1 },
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Some(3),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
board.clone(),
|
|
||||||
Coordinate { column: 1, row: 0 },
|
|
||||||
Some(Group {
|
|
||||||
color: Color::White,
|
|
||||||
coordinates: vec![
|
|
||||||
Coordinate { column: 0, row: 0 },
|
|
||||||
Coordinate { column: 1, row: 0 },
|
|
||||||
Coordinate { column: 0, row: 1 },
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Some(3),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
board.clone(),
|
|
||||||
Coordinate { column: 9, row: 9 },
|
|
||||||
Some(Group {
|
|
||||||
color: Color::White,
|
|
||||||
coordinates: vec![Coordinate { column: 9, row: 9 }].into_iter().collect(),
|
|
||||||
}),
|
|
||||||
Some(1),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
board.clone(),
|
|
||||||
Coordinate { column: 3, row: 4 },
|
|
||||||
Some(Group {
|
|
||||||
color: Color::White,
|
|
||||||
coordinates: vec![
|
|
||||||
Coordinate { column: 3, row: 3 },
|
|
||||||
Coordinate { column: 3, row: 4 },
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Some(6),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
board.clone(),
|
|
||||||
Coordinate { column: 9, row: 3 },
|
|
||||||
Some(Group {
|
|
||||||
color: Color::Black,
|
|
||||||
coordinates: vec![
|
|
||||||
Coordinate { column: 8, row: 3 },
|
|
||||||
Coordinate { column: 9, row: 3 },
|
|
||||||
Coordinate { column: 9, row: 4 },
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Some(7),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
board.clone(),
|
|
||||||
Coordinate { column: 15, row: 4 },
|
|
||||||
Some(Group {
|
|
||||||
color: Color::White,
|
|
||||||
coordinates: vec![
|
|
||||||
Coordinate { column: 15, row: 3 },
|
|
||||||
Coordinate { column: 15, row: 4 },
|
|
||||||
Coordinate { column: 15, row: 5 },
|
|
||||||
Coordinate { column: 14, row: 4 },
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Some(8),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
board.clone(),
|
|
||||||
Coordinate { column: 3, row: 9 },
|
|
||||||
Some(Group {
|
|
||||||
color: Color::White,
|
|
||||||
coordinates: vec![
|
|
||||||
Coordinate { column: 3, row: 8 },
|
|
||||||
Coordinate { column: 3, row: 9 },
|
|
||||||
Coordinate { column: 4, row: 9 },
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Some(6),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
board.clone(),
|
|
||||||
Coordinate { column: 0, row: 18 },
|
|
||||||
Some(Group {
|
|
||||||
color: Color::White,
|
|
||||||
coordinates: vec![
|
|
||||||
Coordinate { column: 0, row: 17 },
|
|
||||||
Coordinate { column: 0, row: 18 },
|
|
||||||
Coordinate { column: 1, row: 18 },
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Some(1),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
board.clone(),
|
|
||||||
Coordinate { column: 0, row: 17 },
|
|
||||||
Some(Group {
|
|
||||||
color: Color::White,
|
|
||||||
coordinates: vec![
|
|
||||||
Coordinate { column: 0, row: 17 },
|
|
||||||
Coordinate { column: 0, row: 18 },
|
|
||||||
Coordinate { column: 1, row: 18 },
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Some(1),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (board, coordinate, group, liberties) in test_cases {
|
|
||||||
assert_eq!(board.group(coordinate), group);
|
|
||||||
assert_eq!(
|
|
||||||
board.group(coordinate).map(|g| board.liberties(&g)),
|
|
||||||
liberties
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn surrounding_a_stone_removes_it() {
|
|
||||||
with_example_board(|mut board| {
|
|
||||||
println!("{}", board);
|
|
||||||
board.place_stone(Coordinate { column: 10, row: 9 }, Color::Black);
|
|
||||||
println!("{}", board);
|
|
||||||
assert!(board.stone(Coordinate { column: 9, row: 9 }).is_none());
|
|
||||||
assert!(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sorrounding_a_group_removes_it() {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn suicide_is_forbidden() {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn captures_preceed_self_capture() {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::types::Color;
|
use crate::{
|
||||||
use crate::{types::GameState, ui::types};
|
types::{Color, GameState},
|
||||||
|
ui::types,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PlayingFieldView {
|
pub struct PlayingFieldView {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::types::{Color, Size};
|
use crate::types::{Color, Size};
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{PlayStoneRequest, Request},
|
api::{PlayStoneRequest, Request},
|
||||||
types::{Board, Coordinate},
|
Board, Coordinate,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
@ -73,7 +73,7 @@ impl From<&Board> for BoardElement {
|
||||||
let spaces: Vec<IntersectionElement> = (0..board.size.height)
|
let spaces: Vec<IntersectionElement> = (0..board.size.height)
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
(0..board.size.width)
|
(0..board.size.width)
|
||||||
.map(|column| match board.stone(Coordinate { column, row }) {
|
.map(|column| match board.stone(&Coordinate { column, row }) {
|
||||||
Some(color) => IntersectionElement::Filled(StoneElement {
|
Some(color) => IntersectionElement::Filled(StoneElement {
|
||||||
jitter: Jitter { x: 0, y: 0 },
|
jitter: Jitter { x: 0, y: 0 },
|
||||||
liberties: Some(0),
|
liberties: Some(0),
|
||||||
|
|
|
@ -488,6 +488,15 @@ dependencies = [
|
||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grid"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0634107a3a005070dd73e27e74ecb691a94e9e5ba7829f434db7fbf73a6b5c47"
|
||||||
|
dependencies = [
|
||||||
|
"no-std-compat",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gsk4"
|
name = "gsk4"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
|
@ -657,6 +666,7 @@ dependencies = [
|
||||||
name = "kifu-core"
|
name = "kifu-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"grid",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -751,6 +761,12 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "no-std-compat"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
|
|
Loading…
Reference in New Issue