Compare commits
4 Commits
78af31f6f9
...
06157604b9
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | 06157604b9 | |
Savanni D'Gerinel | d68e088500 | |
Savanni D'Gerinel | 27d87fcd02 | |
Savanni D'Gerinel | edcf20cc25 |
14
Makefile
14
Makefile
|
@ -35,3 +35,17 @@ ifc-dev:
|
||||||
ifc-test:
|
ifc-test:
|
||||||
cd ifc && make test
|
cd ifc && make test
|
||||||
|
|
||||||
|
kifu-core/dev:
|
||||||
|
cd kifu/kifu-core && make test
|
||||||
|
|
||||||
|
kifu-core/test:
|
||||||
|
cd kifu/kifu-core && make test
|
||||||
|
|
||||||
|
kifu-core/test-oneshot:
|
||||||
|
cd kifu/kifu-core && make test-oneshot
|
||||||
|
|
||||||
|
kifu-gtk:
|
||||||
|
cd kifu/kifu-gtk && make release
|
||||||
|
|
||||||
|
kifu-gtk/dev:
|
||||||
|
cd kifu/kifu-gtk && make dev
|
||||||
|
|
|
@ -49,6 +49,7 @@ name = "kifu-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grid",
|
"grid",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -209,6 +210,37 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.12",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.26.0"
|
version = "1.26.0"
|
||||||
|
@ -237,7 +269,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -8,3 +8,4 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.26", features = [ "full" ] }
|
tokio = { version = "1.26", features = [ "full" ] }
|
||||||
grid = { version = "0.9" }
|
grid = { version = "0.9" }
|
||||||
|
thiserror = { version = "1" }
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
test:
|
||||||
|
cargo watch -x 'nextest run'
|
||||||
|
|
||||||
|
test-oneshot:
|
||||||
|
cargo nextest run
|
|
@ -1,30 +1,23 @@
|
||||||
use crate::{Color, Size};
|
use crate::{BoardError, Color, Size};
|
||||||
use grid::Grid;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
InvalidPosition,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
pub grid: Grid<Option<Color>>,
|
|
||||||
pub groups: Vec<Group>,
|
pub groups: Vec<Group>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Board {
|
impl std::fmt::Display for Board {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
print!(" ");
|
write!(f, " ")?;
|
||||||
// for c in 'A'..'U' {
|
// for c in 'A'..'U' {
|
||||||
for c in 0..19 {
|
for c in 0..19 {
|
||||||
print!("{:2}", c);
|
write!(f, "{:2}", c)?;
|
||||||
}
|
}
|
||||||
println!("");
|
writeln!(f, "")?;
|
||||||
|
|
||||||
for row in 0..self.size.height {
|
for row in 0..self.size.height {
|
||||||
print!(" {:2}", row);
|
write!(f, " {:2}", row)?;
|
||||||
for column in 0..self.size.width {
|
for column in 0..self.size.width {
|
||||||
match self.stone(&Coordinate { column, row }) {
|
match self.stone(&Coordinate { column, row }) {
|
||||||
None => write!(f, " .")?,
|
None => write!(f, " .")?,
|
||||||
|
@ -38,6 +31,26 @@ impl std::fmt::Display for Board {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Board {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
if self.size != other.size {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for group in self.groups.iter() {
|
||||||
|
if !other.groups.contains(&group) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for group in other.groups.iter() {
|
||||||
|
if !self.groups.contains(&group) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -45,17 +58,16 @@ impl Board {
|
||||||
width: 19,
|
width: 19,
|
||||||
height: 19,
|
height: 19,
|
||||||
},
|
},
|
||||||
grid: Grid::new(19, 19),
|
|
||||||
groups: Vec::new(),
|
groups: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_coordinates(coordinates: impl Iterator<Item = (Coordinate, Color)>) -> Self {
|
pub fn from_coordinates(
|
||||||
let mut s = Self::new();
|
mut coordinates: impl Iterator<Item = (Coordinate, Color)>,
|
||||||
for (coordinate, color) in coordinates {
|
) -> Result<Self, BoardError> {
|
||||||
s.place_stone(coordinate, color);
|
coordinates.try_fold(Self::new(), |board, (coordinate, color)| {
|
||||||
}
|
board.place_stone(coordinate, color)
|
||||||
s
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,9 +78,9 @@ pub struct Coordinate {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
pub fn place_stone(&mut self, coordinate: Coordinate, color: Color) -> Result<(), Error> {
|
pub fn place_stone(mut self, coordinate: Coordinate, color: Color) -> Result<Self, BoardError> {
|
||||||
if let Some(_) = self.stone(&coordinate) {
|
if let Some(_) = self.stone(&coordinate) {
|
||||||
return Err(Error::InvalidPosition);
|
return Err(BoardError::InvalidPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut friendly_group = self
|
let mut friendly_group = self
|
||||||
|
@ -90,11 +102,6 @@ impl Board {
|
||||||
};
|
};
|
||||||
self.groups.push(friendly_group.clone());
|
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);
|
let adjacent_groups = self.adjacent_groups(&friendly_group);
|
||||||
for group in adjacent_groups {
|
for group in adjacent_groups {
|
||||||
if self.liberties(&group) == 0 {
|
if self.liberties(&group) == 0 {
|
||||||
|
@ -102,14 +109,18 @@ impl Board {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
if self.liberties(&friendly_group) == 0 {
|
||||||
|
return Err(BoardError::SelfCapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stone(&self, coordinate: &Coordinate) -> Option<Color> {
|
pub fn stone(&self, coordinate: &Coordinate) -> Option<Color> {
|
||||||
match self.grid.get(coordinate.row, coordinate.column) {
|
self.groups
|
||||||
None => None,
|
.iter()
|
||||||
Some(val) => *val,
|
.find(|g| g.contains(coordinate))
|
||||||
}
|
.map(|g| g.color)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn group(&self, coordinate: &Coordinate) -> Option<&Group> {
|
pub fn group(&self, coordinate: &Coordinate) -> Option<&Group> {
|
||||||
|
@ -120,23 +131,15 @@ impl Board {
|
||||||
|
|
||||||
pub fn remove_group(&mut self, group: &Group) {
|
pub fn remove_group(&mut self, group: &Group) {
|
||||||
self.groups.retain(|g| g != 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> {
|
pub fn adjacent_groups(&self, group: &Group) -> Vec<Group> {
|
||||||
let adjacent_spaces = self.group_halo(group).into_iter();
|
let adjacent_spaces = self.group_halo(group).into_iter();
|
||||||
println!("adjacent spaces: {:?}", adjacent_spaces);
|
|
||||||
let mut grps: Vec<Group> = Vec::new();
|
let mut grps: Vec<Group> = Vec::new();
|
||||||
|
|
||||||
adjacent_spaces.for_each(|coord| match self.group(&coord) {
|
adjacent_spaces.for_each(|coord| match self.group(&coord) {
|
||||||
None => return,
|
None => return,
|
||||||
Some(adj) => {
|
Some(adj) => {
|
||||||
println!("group found: {:?}", adj.color);
|
|
||||||
if group.color == adj.color {
|
if group.color == adj.color {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -164,32 +167,6 @@ impl Board {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|c| self.stone(&c) == None)
|
.filter(|c| self.stone(&c) == None)
|
||||||
.count()
|
.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> {
|
pub fn adjacencies(&self, coordinate: &Coordinate) -> Vec<Coordinate> {
|
||||||
|
@ -217,41 +194,6 @@ impl Board {
|
||||||
v.into_iter().filter(|c| self.within_board(c)).collect()
|
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 {
|
pub fn within_board(&self, coordinate: &Coordinate) -> bool {
|
||||||
coordinate.column < self.size.width && coordinate.row < self.size.height
|
coordinate.column < self.size.width && coordinate.row < self.size.height
|
||||||
}
|
}
|
||||||
|
@ -263,17 +205,11 @@ pub struct Group {
|
||||||
coordinates: HashSet<Coordinate>,
|
coordinates: HashSet<Coordinate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
impl Group {
|
impl Group {
|
||||||
pub fn adjacencies(&self, max_column: usize, max_row: usize) -> HashSet<Coordinate> {
|
fn contains(&self, coordinate: &Coordinate) -> bool {
|
||||||
self.coordinates
|
self.coordinates.contains(coordinate)
|
||||||
.iter()
|
|
||||||
.map(|stone| stone.adjacencies(max_column, max_row))
|
|
||||||
.flatten()
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
@ -336,9 +272,14 @@ mod test {
|
||||||
(Coordinate { column: 6, row: 16 }, Color::White),
|
(Coordinate { column: 6, row: 16 }, Color::White),
|
||||||
(Coordinate { column: 7, row: 17 }, Color::White),
|
(Coordinate { column: 7, row: 17 }, Color::White),
|
||||||
(Coordinate { column: 7, row: 18 }, Color::White),
|
(Coordinate { column: 7, row: 18 }, Color::White),
|
||||||
|
/* */
|
||||||
|
(Coordinate { column: 17, row: 0 }, Color::White),
|
||||||
|
(Coordinate { column: 17, row: 1 }, Color::White),
|
||||||
|
(Coordinate { column: 18, row: 1 }, Color::White),
|
||||||
]
|
]
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
test(board);
|
test(board);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,7 +318,8 @@ mod test {
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
assert!(board.group(&Coordinate { column: 18, row: 3 }).is_none());
|
assert!(board.group(&Coordinate { column: 18, row: 3 }).is_none());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
board
|
board
|
||||||
|
@ -564,8 +506,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_finds_adjacent_groups() {
|
fn it_finds_adjacent_groups() {
|
||||||
with_example_board(|mut board| {
|
with_example_board(|board| {
|
||||||
println!("{}", board);
|
|
||||||
let group = board
|
let group = board
|
||||||
.group(&Coordinate { column: 0, row: 0 })
|
.group(&Coordinate { column: 0, row: 0 })
|
||||||
.cloned()
|
.cloned()
|
||||||
|
@ -582,17 +523,23 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn surrounding_a_group_removes_it() {
|
fn surrounding_a_group_removes_it() {
|
||||||
with_example_board(|mut board| {
|
with_example_board(|board| {
|
||||||
board.place_stone(Coordinate { column: 10, row: 9 }, Color::Black);
|
let board = board
|
||||||
|
.place_stone(Coordinate { column: 10, row: 9 }, Color::Black)
|
||||||
|
.unwrap();
|
||||||
assert!(board.stone(&Coordinate { column: 9, row: 9 }).is_none());
|
assert!(board.stone(&Coordinate { column: 9, row: 9 }).is_none());
|
||||||
|
|
||||||
board.place_stone(Coordinate { column: 2, row: 18 }, Color::Black);
|
let board = board
|
||||||
|
.place_stone(Coordinate { column: 2, row: 18 }, Color::Black)
|
||||||
|
.unwrap();
|
||||||
assert!(board.stone(&Coordinate { column: 0, row: 18 }).is_none());
|
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: 1, row: 18 }).is_none());
|
||||||
assert!(board.stone(&Coordinate { column: 0, row: 17 }).is_none());
|
assert!(board.stone(&Coordinate { column: 0, row: 17 }).is_none());
|
||||||
assert!(board.group(&Coordinate { column: 0, row: 18 }).is_none());
|
assert!(board.group(&Coordinate { column: 0, row: 18 }).is_none());
|
||||||
|
|
||||||
board.place_stone(Coordinate { column: 5, row: 18 }, Color::White);
|
let board = board
|
||||||
|
.place_stone(Coordinate { column: 5, row: 18 }, Color::White)
|
||||||
|
.unwrap();
|
||||||
assert!(board.stone(&Coordinate { column: 4, row: 17 }).is_none());
|
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: 5, row: 17 }).is_none());
|
||||||
assert!(board.stone(&Coordinate { column: 6, row: 17 }).is_none());
|
assert!(board.stone(&Coordinate { column: 6, row: 17 }).is_none());
|
||||||
|
@ -601,11 +548,86 @@ mod test {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn suicide_is_forbidden() {
|
#[test]
|
||||||
assert!(false);
|
fn self_capture_is_forbidden() {
|
||||||
|
with_example_board(|board| {
|
||||||
|
{
|
||||||
|
let board = board.clone();
|
||||||
|
let res = board.place_stone(Coordinate { column: 18, row: 0 }, Color::Black);
|
||||||
|
assert_eq!(res, Err(BoardError::SelfCapture));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn captures_preceed_self_capture() {
|
{
|
||||||
assert!(false);
|
let board = board.clone();
|
||||||
|
let res = board.place_stone(Coordinate { column: 5, row: 18 }, Color::Black);
|
||||||
|
assert_eq!(res, Err(BoardError::SelfCapture));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_group_comparisons() {
|
||||||
|
{
|
||||||
|
let b1 = Board::from_coordinates(
|
||||||
|
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let b2 = Board::from_coordinates(
|
||||||
|
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(b1, b2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let b1 = Board::from_coordinates(
|
||||||
|
vec![
|
||||||
|
(Coordinate { column: 7, row: 9 }, Color::White),
|
||||||
|
(Coordinate { column: 8, row: 10 }, Color::White),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let b2 = Board::from_coordinates(
|
||||||
|
vec![
|
||||||
|
(Coordinate { column: 8, row: 10 }, Color::White),
|
||||||
|
(Coordinate { column: 7, row: 9 }, Color::White),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(b1, b2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_boards_can_be_compared() {
|
||||||
|
let board = Board::from_coordinates(
|
||||||
|
vec![
|
||||||
|
(Coordinate { column: 7, row: 9 }, Color::White),
|
||||||
|
(Coordinate { column: 8, row: 8 }, Color::White),
|
||||||
|
(Coordinate { column: 8, row: 10 }, Color::White),
|
||||||
|
(Coordinate { column: 9, row: 9 }, Color::White),
|
||||||
|
(Coordinate { column: 10, row: 9 }, Color::Black),
|
||||||
|
(Coordinate { column: 9, row: 8 }, Color::Black),
|
||||||
|
(Coordinate { column: 9, row: 10 }, Color::Black),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let b1 = board
|
||||||
|
.clone()
|
||||||
|
.place_stone(Coordinate { column: 8, row: 9 }, Color::Black)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let b2 = b1
|
||||||
|
.clone()
|
||||||
|
.place_stone(Coordinate { column: 9, row: 9 }, Color::White)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(board, b2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod api;
|
||||||
pub use api::{CoreApp, Request, Response};
|
pub use api::{CoreApp, Request, Response};
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
pub use types::{Color, Size};
|
pub use types::{BoardError, Color, Size};
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
mod board;
|
mod board;
|
||||||
|
|
|
@ -3,6 +3,17 @@ use crate::{
|
||||||
board::{Board, Coordinate},
|
board::{Board, Coordinate},
|
||||||
};
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Error)]
|
||||||
|
pub enum BoardError {
|
||||||
|
#[error("Position is invalid")]
|
||||||
|
InvalidPosition,
|
||||||
|
#[error("Self-capture is forbidden")]
|
||||||
|
SelfCapture,
|
||||||
|
#[error("Ko")]
|
||||||
|
Ko,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
|
@ -80,6 +91,8 @@ pub struct Player {
|
||||||
|
|
||||||
pub struct GameState {
|
pub struct GameState {
|
||||||
pub board: Board,
|
pub board: Board,
|
||||||
|
pub past_positions: Vec<Board>,
|
||||||
|
|
||||||
pub conversation: Vec<String>,
|
pub conversation: Vec<String>,
|
||||||
pub current_player: Color,
|
pub current_player: Color,
|
||||||
|
|
||||||
|
@ -94,6 +107,7 @@ impl GameState {
|
||||||
fn new() -> GameState {
|
fn new() -> GameState {
|
||||||
GameState {
|
GameState {
|
||||||
board: Board::new(),
|
board: Board::new(),
|
||||||
|
past_positions: vec![],
|
||||||
conversation: vec![],
|
conversation: vec![],
|
||||||
current_player: Color::Black,
|
current_player: Color::Black,
|
||||||
white_player: Player {
|
white_player: Player {
|
||||||
|
@ -109,11 +123,79 @@ impl GameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn place_stone(&mut self, coordinate: Coordinate) {
|
fn place_stone(&mut self, coordinate: Coordinate) -> Result<(), BoardError> {
|
||||||
self.board.place_stone(coordinate, self.current_player);
|
let board = self.board.clone();
|
||||||
|
let new_board = board.place_stone(coordinate, self.current_player)?;
|
||||||
|
|
||||||
|
println!("past positions: {}", self.past_positions.len());
|
||||||
|
if self.past_positions.contains(&new_board) {
|
||||||
|
return Err(BoardError::Ko);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.past_positions.push(self.board.clone());
|
||||||
|
self.board = new_board;
|
||||||
match self.current_player {
|
match self.current_player {
|
||||||
Color::White => self.current_player = Color::Black,
|
Color::White => self.current_player = Color::Black,
|
||||||
Color::Black => self.current_player = Color::White,
|
Color::Black => self.current_player = Color::White,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn current_player_changes_after_move() {
|
||||||
|
let mut state = GameState::new();
|
||||||
|
assert_eq!(state.current_player, Color::Black);
|
||||||
|
state.place_stone(Coordinate { column: 9, row: 9 }).unwrap();
|
||||||
|
assert_eq!(state.current_player, Color::White);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn current_player_remains_the_same_after_self_capture() {
|
||||||
|
let mut state = GameState::new();
|
||||||
|
state.board = Board::from_coordinates(
|
||||||
|
vec![
|
||||||
|
(Coordinate { column: 17, row: 0 }, Color::White),
|
||||||
|
(Coordinate { column: 17, row: 1 }, Color::White),
|
||||||
|
(Coordinate { column: 18, row: 1 }, Color::White),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
state.current_player = Color::Black;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.place_stone(Coordinate { column: 18, row: 0 }),
|
||||||
|
Err(BoardError::SelfCapture)
|
||||||
|
);
|
||||||
|
assert_eq!(state.current_player, Color::Black);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ko_rules_are_enforced() {
|
||||||
|
let mut state = GameState::new();
|
||||||
|
state.board = Board::from_coordinates(
|
||||||
|
vec![
|
||||||
|
(Coordinate { column: 7, row: 9 }, Color::White),
|
||||||
|
(Coordinate { column: 8, row: 8 }, Color::White),
|
||||||
|
(Coordinate { column: 8, row: 10 }, Color::White),
|
||||||
|
(Coordinate { column: 9, row: 9 }, Color::White),
|
||||||
|
(Coordinate { column: 10, row: 9 }, Color::Black),
|
||||||
|
(Coordinate { column: 9, row: 8 }, Color::Black),
|
||||||
|
(Coordinate { column: 9, row: 10 }, Color::Black),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
state.place_stone(Coordinate { column: 8, row: 9 }).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
state.place_stone(Coordinate { column: 9, row: 9 }),
|
||||||
|
Err(BoardError::Ko)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -667,6 +667,7 @@ name = "kifu-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grid",
|
"grid",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
release:
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
dev:
|
||||||
|
cargo watch -x 'run --bin kifu-gtk'
|
Loading…
Reference in New Issue