From 6d51ae84797446d0149234e4f33d0df83439c24e Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 13 Apr 2023 19:43:40 -0400 Subject: [PATCH 01/13] Add tests --- kifu/kifu-core/src/types.rs | 93 +++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index e9c9f32..a6f5b3c 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -112,11 +112,28 @@ impl GameState { } } +#[derive(Clone, Debug)] pub struct Board { pub size: Size, pub spaces: Vec>, } +impl std::fmt::Display for Board { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + for row in 0..self.size.height { + for column in 0..self.size.width { + match self.stone(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(); @@ -146,3 +163,79 @@ impl Board { ((row as usize) * (self.size.width as usize) + (column as usize)) as usize } } + +#[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. + */ + + #[test] + fn it_shows_stones() { + let mut board = Board::new(); + board.place_stone(3, 2, Color::White); + board.place_stone(2, 3, Color::White); + board.place_stone(4, 3, Color::White); + board.place_stone(3, 3, Color::Black); + println!("{}", board); + assert!(false); + } + + #[test] + fn it_counts_individual_liberties() { + let mut board = Board::new(); + board.place_stone(3, 3, Color::White); + board.place_stone(0, 3, Color::White); + board.place_stone(0, 0, Color::White); + println!("{}", board); + assert!(false); + } + + #[test] + fn stones_share_liberties() { + let mut board = Board::new(); + board.place_stone(3, 3, Color::White); + board.place_stone(3, 4, Color::White); + board.place_stone(3, 5, Color::White); + board.place_stone(4, 4, Color::White); + println!("{}", board); + assert!(false); + } + + #[test] + fn opposing_stones_reduce_liberties() { + let mut board = Board::new(); + board.place_stone(3, 3, Color::White); + board.place_stone(3, 4, Color::Black); + println!("{}", board); + assert!(false); + } + + #[test] + fn surrounding_a_stone_remove_it() { + assert!(false); + } + + #[test] + fn sorrounding_a_group_removes_it() { + assert!(false); + } + + #[test] + fn suicide_is_forbidden() { + assert!(false); + } + + #[test] + fn captures_preceed_self_capture() { + assert!(false); + } +} -- 2.44.1 From 037484e7b4b0786d49155350ed2ef18c9e34b074 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 13 Apr 2023 22:38:35 -0400 Subject: [PATCH 02/13] Count liberties --- kifu/kifu-core/src/types.rs | 287 ++++++++++++++++++++++++++++----- kifu/kifu-core/src/ui/types.rs | 4 +- 2 files changed, 253 insertions(+), 38 deletions(-) diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index a6f5b3c..cf7e197 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -1,7 +1,7 @@ use crate::api::PlayStoneRequest; -use std::time::Duration; +use std::{collections::HashSet, time::Duration}; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)] pub enum Color { Black, White, @@ -36,7 +36,10 @@ impl AppState { pub fn place_stone(&mut self, req: PlayStoneRequest) { match self.game { Some(ref mut game) => { - game.place_stone(req.column, req.row); + game.place_stone(Coordinate { + column: req.column, + row: req.row, + }); } None => {} } @@ -103,8 +106,8 @@ impl GameState { } } - fn place_stone(&mut self, column: u8, row: u8) { - self.board.place_stone(column, row, self.current_player); + fn place_stone(&mut self, coordinate: Coordinate) { + self.board.place_stone(coordinate, self.current_player); match self.current_player { Color::White => self.current_player = Color::Black, Color::Black => self.current_player = Color::White, @@ -112,6 +115,53 @@ impl GameState { } } +#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)] +pub struct Coordinate { + pub column: u8, + pub row: u8, +} + +impl Coordinate { + fn adjacencies(&self, max_column: u8, max_row: u8) -> impl Iterator { + 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 < max_column { + Some(Coordinate { + column: self.column + 1, + row: self.row, + }) + } else { + None + }, + if self.row < max_row { + 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, @@ -122,7 +172,7 @@ impl std::fmt::Display for Board { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { for row in 0..self.size.height { for column in 0..self.size.width { - match self.stone(column, row) { + match self.stone(Coordinate { column, row }) { None => write!(f, " . ")?, Some(Color::Black) => write!(f, " X ")?, Some(Color::White) => write!(f, " O ")?, @@ -149,21 +199,85 @@ impl Board { } } - pub fn place_stone(&mut self, column: u8, row: u8, stone: Color) { - let addr = self.addr(column, row); + fn from_coordinates(coordinates: impl Iterator) -> 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); } - pub fn stone(&self, column: u8, row: u8) -> Option { - let addr = self.addr(column, row); + pub fn stone(&self, coordinate: Coordinate) -> Option { + let addr = self.addr(coordinate.column, coordinate.row); self.spaces[addr] } + pub fn group(&self, coordinate: Coordinate) -> Option { + let mut visited: HashSet = 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::>(); + 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::>(), + ); + 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::>() + .len() + } + fn addr(&self, column: u8, row: u8) -> usize { ((row as usize) * (self.size.width as usize) + (column as usize)) as usize } } +#[derive(Clone, Debug, PartialEq)] +pub struct Group { + color: Color, + coordinates: HashSet, +} + +impl Group { + fn adjacencies(&self, max_column: u8, max_row: u8) -> HashSet { + self.coordinates + .iter() + .map(|stone| stone.adjacencies(max_column, max_row)) + .flatten() + .collect() + } +} + #[cfg(test)] mod test { use super::*; @@ -178,43 +292,144 @@ mod test { * A stone placed in a suicidal position is legal if it captures other stones first. */ - #[test] - fn it_shows_stones() { - let mut board = Board::new(); - board.place_stone(3, 2, Color::White); - board.place_stone(2, 3, Color::White); - board.place_stone(4, 3, Color::White); - board.place_stone(3, 3, Color::Black); - println!("{}", board); - assert!(false); - } - #[test] fn it_counts_individual_liberties() { - let mut board = Board::new(); - board.place_stone(3, 3, Color::White); - board.place_stone(0, 3, Color::White); - board.place_stone(0, 0, Color::White); - println!("{}", board); - assert!(false); + 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_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() { - let mut board = Board::new(); - board.place_stone(3, 3, Color::White); - board.place_stone(3, 4, Color::White); - board.place_stone(3, 5, Color::White); - board.place_stone(4, 4, Color::White); - println!("{}", board); - assert!(false); + let test_cases = vec![ + ( + Board::from_coordinates( + vec![ + (Coordinate { column: 3, row: 3 }, Color::White), + (Coordinate { column: 3, row: 4 }, Color::White), + ] + .into_iter(), + ), + 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::from_coordinates( + vec![ + (Coordinate { column: 3, row: 3 }, Color::White), + (Coordinate { column: 3, row: 4 }, Color::White), + (Coordinate { column: 4, row: 4 }, Color::White), + ] + .into_iter(), + ), + Coordinate { column: 3, row: 4 }, + Some(Group { + color: Color::White, + coordinates: vec![ + Coordinate { column: 3, row: 3 }, + Coordinate { column: 3, row: 4 }, + Coordinate { column: 4, row: 4 }, + ] + .into_iter() + .collect(), + }), + Some(7), + ), + ( + Board::from_coordinates( + vec![ + (Coordinate { column: 3, row: 3 }, Color::White), + (Coordinate { column: 3, row: 4 }, Color::White), + (Coordinate { column: 4, row: 4 }, Color::White), + (Coordinate { column: 3, row: 5 }, Color::White), + ] + .into_iter(), + ), + Coordinate { column: 3, row: 4 }, + Some(Group { + color: Color::White, + coordinates: vec![ + Coordinate { column: 3, row: 3 }, + Coordinate { column: 3, row: 4 }, + Coordinate { column: 4, row: 4 }, + Coordinate { column: 3, row: 5 }, + ] + .into_iter() + .collect(), + }), + Some(8), + ), + ]; + + 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 opposing_stones_reduce_liberties() { let mut board = Board::new(); - board.place_stone(3, 3, Color::White); - board.place_stone(3, 4, Color::Black); + board.place_stone(Coordinate { column: 3, row: 3 }, Color::White); + board.place_stone(Coordinate { column: 3, row: 4 }, Color::Black); println!("{}", board); assert!(false); } diff --git a/kifu/kifu-core/src/ui/types.rs b/kifu/kifu-core/src/ui/types.rs index 39e2904..ef9f21c 100644 --- a/kifu/kifu-core/src/ui/types.rs +++ b/kifu/kifu-core/src/ui/types.rs @@ -1,7 +1,7 @@ use crate::types::{Color, Size}; use crate::{ api::{PlayStoneRequest, Request}, - types::Board, + types::{Board, Coordinate}, }; #[derive(Clone, Copy, Debug, PartialEq)] @@ -71,7 +71,7 @@ impl From<&Board> for BoardElement { let spaces: Vec = (0..board.size.height) .map(|row| { (0..board.size.width) - .map(|column| match board.stone(column, row) { + .map(|column| match board.stone(Coordinate { column, row }) { Some(color) => IntersectionElement::Filled(StoneElement { jitter: Jitter { x: 0, y: 0 }, color, -- 2.44.1 From 3cd39af060d2e94973d8edbfbc92ce046bc35888 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 14 Apr 2023 09:06:41 -0400 Subject: [PATCH 03/13] Clean up the liberty sharing test --- kifu/kifu-core/src/types.rs | 109 ++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index cf7e197..5cb2bce 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -173,9 +173,9 @@ impl std::fmt::Display for Board { for row in 0..self.size.height { 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 ")?, + None => write!(f, " .")?, + Some(Color::Black) => write!(f, " X")?, + Some(Color::White) => write!(f, " O")?, } } writeln!(f, "")?; @@ -310,6 +310,7 @@ mod test { ] .into_iter(), ); + assert!(board.group(Coordinate { column: 18, row: 3 }).is_none()); assert_eq!( board .group(Coordinate { column: 3, row: 3 }) @@ -347,15 +348,27 @@ mod test { #[test] fn stones_share_liberties() { + 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), + ] + .into_iter(), + ); let test_cases = vec![ ( - Board::from_coordinates( - vec![ - (Coordinate { column: 3, row: 3 }, Color::White), - (Coordinate { column: 3, row: 4 }, Color::White), - ] - .into_iter(), - ), + board.clone(), Coordinate { column: 3, row: 4 }, Some(Group { color: Color::White, @@ -369,21 +382,14 @@ mod test { Some(6), ), ( - Board::from_coordinates( - vec![ - (Coordinate { column: 3, row: 3 }, Color::White), - (Coordinate { column: 3, row: 4 }, Color::White), - (Coordinate { column: 4, row: 4 }, Color::White), - ] - .into_iter(), - ), - Coordinate { column: 3, row: 4 }, + board.clone(), + Coordinate { column: 9, row: 3 }, Some(Group { - color: Color::White, + color: Color::Black, coordinates: vec![ - Coordinate { column: 3, row: 3 }, - Coordinate { column: 3, row: 4 }, - Coordinate { column: 4, row: 4 }, + Coordinate { column: 8, row: 3 }, + Coordinate { column: 9, row: 3 }, + Coordinate { column: 9, row: 4 }, ] .into_iter() .collect(), @@ -391,29 +397,36 @@ mod test { Some(7), ), ( - Board::from_coordinates( - vec![ - (Coordinate { column: 3, row: 3 }, Color::White), - (Coordinate { column: 3, row: 4 }, Color::White), - (Coordinate { column: 4, row: 4 }, Color::White), - (Coordinate { column: 3, row: 5 }, Color::White), - ] - .into_iter(), - ), - Coordinate { column: 3, row: 4 }, + board.clone(), + Coordinate { column: 15, row: 4 }, Some(Group { color: Color::White, coordinates: vec![ - Coordinate { column: 3, row: 3 }, - Coordinate { column: 3, row: 4 }, - Coordinate { column: 4, row: 4 }, - Coordinate { column: 3, row: 5 }, + 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), + ), ]; for (board, coordinate, group, liberties) in test_cases { @@ -427,10 +440,24 @@ mod test { #[test] fn opposing_stones_reduce_liberties() { - let mut board = Board::new(); - board.place_stone(Coordinate { column: 3, row: 3 }, Color::White); - board.place_stone(Coordinate { column: 3, row: 4 }, Color::Black); - println!("{}", board); + let test_cases = vec![( + Board::from_coordinates( + vec![ + (Coordinate { column: 3, row: 3 }, Color::White), + (Coordinate { column: 3, row: 4 }, Color::Black), + ] + .into_iter(), + ), + Coordinate { column: 3, row: 4 }, + Some(6), + )]; + println!("{}", test_cases[0].0); + for (board, coordinate, liberties) in test_cases { + assert_eq!( + board.group(coordinate).map(|g| board.liberties(&g)), + liberties + ); + } assert!(false); } -- 2.44.1 From 300704297faf5aa2ccd77d63ec87f6277e7b55f4 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 27 Apr 2023 18:56:59 -0400 Subject: [PATCH 04/13] Add a renderable field for liberties --- kifu/kifu-core/src/ui/types.rs | 5 ++++- kifu/kifu-gtk/src/ui/board.rs | 35 +++++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/kifu/kifu-core/src/ui/types.rs b/kifu/kifu-core/src/ui/types.rs index ef9f21c..5be1e96 100644 --- a/kifu/kifu-core/src/ui/types.rs +++ b/kifu/kifu-core/src/ui/types.rs @@ -14,12 +14,14 @@ pub struct Jitter { pub struct StoneElement { pub color: Color, pub jitter: Jitter, + pub liberties: Option, } impl StoneElement { - pub fn new(color: Color) -> Self { + pub fn new(color: Color, liberties: Option) -> Self { Self { color, + liberties, jitter: Jitter { x: 0, y: 0 }, } } @@ -74,6 +76,7 @@ impl From<&Board> for BoardElement { .map(|column| match board.stone(Coordinate { column, row }) { Some(color) => IntersectionElement::Filled(StoneElement { jitter: Jitter { x: 0, y: 0 }, + liberties: None, color, }), None => IntersectionElement::Empty(Request::PlayStoneRequest( diff --git a/kifu/kifu-gtk/src/ui/board.rs b/kifu/kifu-gtk/src/ui/board.rs index 7b442f5..2940066 100644 --- a/kifu/kifu-gtk/src/ui/board.rs +++ b/kifu/kifu-gtk/src/ui/board.rs @@ -141,7 +141,7 @@ impl ObjectImpl for BoardPrivate { (0..19).for_each(|row| { match board.stone(row, col) { IntersectionElement::Filled(stone) => { - pen.stone(&context, row, col, stone.color); + pen.stone(&context, row, col, stone.color, stone.liberties); } _ => {} }; @@ -270,12 +270,27 @@ impl Pen { let _ = context.fill(); } - fn stone(&self, context: &cairo::Context, row: u8, col: u8, color: Color) { + fn stone( + &self, + context: &cairo::Context, + row: u8, + col: u8, + color: Color, + liberties: Option, + ) { match color { Color::White => context.set_source_rgb(0.9, 0.9, 0.9), Color::Black => context.set_source_rgb(0.0, 0.0, 0.0), }; self.draw_stone(context, row, col); + + if let Some(liberties) = liberties { + let stone_location = self.stone_location(row, col); + context.set_source_rgb(1., 0., 1.); + context.set_font_size(32.); + context.move_to(stone_location.0 - 10., stone_location.1 + 10.); + let _ = context.show_text(&format!("{}", liberties)); + } } fn ghost_stone(&self, context: &cairo::Context, row: u8, col: u8, color: Color) { @@ -288,13 +303,15 @@ impl Pen { fn draw_stone(&self, context: &cairo::Context, row: u8, col: u8) { let radius = self.hspace_between / 2. - 2.; - context.arc( - self.x_offset + (col as f64) * self.hspace_between, - self.y_offset + (row as f64) * self.vspace_between, - radius, - 0.0, - 2.0 * std::f64::consts::PI, - ); + let (x_loc, y_loc) = self.stone_location(row, col); + context.arc(x_loc, y_loc, radius, 0.0, 2.0 * std::f64::consts::PI); let _ = context.fill(); } + + fn stone_location(&self, row: u8, col: u8) -> (f64, f64) { + ( + self.x_offset + (col as f64) * self.hspace_between, + self.y_offset + (row as f64) * self.vspace_between, + ) + } } -- 2.44.1 From e746190a4a20ab135904059a09401064449a14bd Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 27 Apr 2023 19:13:06 -0400 Subject: [PATCH 05/13] Add coordinates to the board printout --- flake.nix | 1 + kifu/kifu-core/src/types.rs | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/flake.nix b/flake.nix index d44619f..e223b90 100644 --- a/flake.nix +++ b/flake.nix @@ -38,6 +38,7 @@ pkgs.pkg-config pkgs.sqlite pkgs.rustup + pkgs.cargo-nextest pkgs.crate2nix ]; LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib"; diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index 5cb2bce..68b05bc 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -170,7 +170,17 @@ pub struct Board { impl std::fmt::Display for Board { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + print!(" "); + for c in 'A'..'U' { + if c == 'I' { + continue; + } + print!(" {}", 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, " .")?, @@ -366,6 +376,8 @@ mod test { ] .into_iter(), ); + println!("{}", board); + assert!(false); let test_cases = vec![ ( board.clone(), @@ -458,25 +470,20 @@ mod test { liberties ); } + } + + fn surrounding_a_stone_removes_it() { assert!(false); } - #[test] - fn surrounding_a_stone_remove_it() { - assert!(false); - } - - #[test] fn sorrounding_a_group_removes_it() { assert!(false); } - #[test] fn suicide_is_forbidden() { assert!(false); } - #[test] fn captures_preceed_self_capture() { assert!(false); } -- 2.44.1 From 52e27318fe9108e84e59f797fe4553463ef54322 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 27 Apr 2023 21:53:54 -0400 Subject: [PATCH 06/13] Add code to try to handle removing captured stones from the board --- kifu/kifu-core/src/types.rs | 362 ++++++++++++++++++++++----------- kifu/kifu-core/src/ui/types.rs | 2 +- 2 files changed, 243 insertions(+), 121 deletions(-) diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index 68b05bc..a67c802 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -122,7 +122,11 @@ pub struct Coordinate { } impl Coordinate { - fn adjacencies(&self, max_column: u8, max_row: u8) -> impl Iterator { + fn within_board(&self, width: u8, height: u8) -> bool { + self.column < width && self.row < height + } + + fn adjacencies(&self, width: u8, height: u8) -> impl Iterator { vec![ if self.column > 0 { Some(Coordinate { @@ -140,7 +144,7 @@ impl Coordinate { } else { None }, - if self.column < max_column { + if self.column < width - 1 { Some(Coordinate { column: self.column + 1, row: self.row, @@ -148,7 +152,7 @@ impl Coordinate { } else { None }, - if self.row < max_row { + if self.row < height - 1 { Some(Coordinate { column: self.column, row: self.row + 1, @@ -171,11 +175,9 @@ pub struct Board { impl std::fmt::Display for Board { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { print!(" "); - for c in 'A'..'U' { - if c == 'I' { - continue; - } - print!(" {}", c); + // for c in 'A'..'U' { + for c in 0..19 { + print!("{:2}", c); } println!(""); @@ -220,6 +222,26 @@ impl Board { 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 { @@ -234,6 +256,9 @@ impl Board { let mut fringes = coordinate .adjacencies(self.size.width as u8 - 1, self.size.height as u8 - 1) .collect::>(); + fringes + .iter() + .for_each(|coordinate| println!("group fringes: {:?}", coordinate)); visited.insert(coordinate); while let Some(coordinate) = fringes.pop() { @@ -268,7 +293,11 @@ impl Board { } fn addr(&self, column: u8, row: u8) -> usize { - ((row as usize) * (self.size.width as usize) + (column as usize)) as 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 } } @@ -302,6 +331,63 @@ mod test { * 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( @@ -358,122 +444,158 @@ mod test { #[test] fn stones_share_liberties() { - 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), - ] - .into_iter(), - ); - println!("{}", board); - assert!(false); - let test_cases = vec![ - ( - 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), - ), - ]; + 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 - ); - } + 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 opposing_stones_reduce_liberties() { - let test_cases = vec![( - Board::from_coordinates( - vec![ - (Coordinate { column: 3, row: 3 }, Color::White), - (Coordinate { column: 3, row: 4 }, Color::Black), - ] - .into_iter(), - ), - Coordinate { column: 3, row: 4 }, - Some(6), - )]; - println!("{}", test_cases[0].0); - for (board, coordinate, liberties) in test_cases { - assert_eq!( - board.group(coordinate).map(|g| board.liberties(&g)), - liberties - ); - } - } - fn surrounding_a_stone_removes_it() { - assert!(false); + 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() { diff --git a/kifu/kifu-core/src/ui/types.rs b/kifu/kifu-core/src/ui/types.rs index 5be1e96..f027ebf 100644 --- a/kifu/kifu-core/src/ui/types.rs +++ b/kifu/kifu-core/src/ui/types.rs @@ -76,7 +76,7 @@ impl From<&Board> for BoardElement { .map(|column| match board.stone(Coordinate { column, row }) { Some(color) => IntersectionElement::Filled(StoneElement { jitter: Jitter { x: 0, y: 0 }, - liberties: None, + liberties: Some(0), color, }), None => IntersectionElement::Empty(Request::PlayStoneRequest( -- 2.44.1 From 78af31f6f907d48a7a4ea74666af4bde414c72df Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 28 Apr 2023 01:21:23 -0400 Subject: [PATCH 07/13] Overhaul the board representation Groups become more first-class objects, where the grid becomes useful for quick lookups of values. --- kifu/kifu-core/Cargo.lock | 16 + kifu/kifu-core/Cargo.toml | 3 +- kifu/kifu-core/src/api.rs | 4 +- kifu/kifu-core/src/board.rs | 611 +++++++++++++++++++++++++ kifu/kifu-core/src/lib.rs | 3 + kifu/kifu-core/src/types.rs | 509 +------------------- kifu/kifu-core/src/ui/playing_field.rs | 6 +- kifu/kifu-core/src/ui/types.rs | 4 +- kifu/kifu-gtk/Cargo.lock | 16 + 9 files changed, 664 insertions(+), 508 deletions(-) create mode 100644 kifu/kifu-core/src/board.rs diff --git a/kifu/kifu-core/Cargo.lock b/kifu/kifu-core/Cargo.lock index 59b31e4..f723d62 100644 --- a/kifu/kifu-core/Cargo.lock +++ b/kifu/kifu-core/Cargo.lock @@ -26,6 +26,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "hermit-abi" version = "0.2.6" @@ -39,6 +48,7 @@ dependencies = [ name = "kifu-core" version = "0.1.0" dependencies = [ + "grid", "tokio", ] @@ -85,6 +95,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "num_cpus" version = "1.15.0" diff --git a/kifu/kifu-core/Cargo.toml b/kifu/kifu-core/Cargo.toml index ad2dec2..8287207 100644 --- a/kifu/kifu-core/Cargo.toml +++ b/kifu/kifu-core/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1.26", features = [ "full" ] } +tokio = { version = "1.26", features = [ "full" ] } +grid = { version = "0.9" } diff --git a/kifu/kifu-core/src/api.rs b/kifu/kifu-core/src/api.rs index 983a5bc..5abd9d4 100644 --- a/kifu/kifu-core/src/api.rs +++ b/kifu/kifu-core/src/api.rs @@ -10,8 +10,8 @@ pub enum Request { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PlayStoneRequest { - pub column: u8, - pub row: u8, + pub column: usize, + pub row: usize, } #[derive(Clone, Debug)] diff --git a/kifu/kifu-core/src/board.rs b/kifu/kifu-core/src/board.rs new file mode 100644 index 0000000..ac07290 --- /dev/null +++ b/kifu/kifu-core/src/board.rs @@ -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>, + pub groups: Vec, +} + +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) -> 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 { + 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 { + let adjacent_spaces = self.group_halo(group).into_iter(); + println!("adjacent spaces: {:?}", adjacent_spaces); + let mut grps: Vec = 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 { + group + .coordinates + .iter() + .map(|c| self.adjacencies(c)) + .flatten() + .collect::>() + } + + pub fn liberties(&self, group: &Group) -> usize { + self.group_halo(group) + .into_iter() + .filter(|c| self.stone(&c) == None) + .count() + + // let adjacencies: HashSet = 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::>(); + 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::>() + .len() + */ + } + + pub fn adjacencies(&self, coordinate: &Coordinate) -> Vec { + 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 + 'a, + ) -> impl Iterator + '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, +} + +/* +impl Group { + pub fn adjacencies(&self, max_column: usize, max_row: usize) -> HashSet { + 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); + } +} diff --git a/kifu/kifu-core/src/lib.rs b/kifu/kifu-core/src/lib.rs index 9c22bc8..b9ad5cc 100644 --- a/kifu/kifu-core/src/lib.rs +++ b/kifu/kifu-core/src/lib.rs @@ -4,3 +4,6 @@ pub use api::{CoreApp, Request, Response}; mod types; pub use types::{Color, Size}; pub mod ui; + +mod board; +pub use board::*; diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index a67c802..6686f54 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -1,5 +1,8 @@ -use crate::api::PlayStoneRequest; -use std::{collections::HashSet, time::Duration}; +use crate::{ + api::PlayStoneRequest, + board::{Board, Coordinate}, +}; +use std::time::Duration; #[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)] pub enum Color { @@ -7,10 +10,10 @@ pub enum Color { White, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Size { - pub width: u8, - pub height: u8, + pub width: usize, + pub height: usize, } 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 { - 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>, -} - -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) -> 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 { - let addr = self.addr(coordinate.column, coordinate.row); - self.spaces[addr] - } - - pub fn group(&self, coordinate: Coordinate) -> Option { - let mut visited: HashSet = 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::>(); - 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::>(), - ); - 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::>() - .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, -} - -impl Group { - fn adjacencies(&self, max_column: u8, max_row: u8) -> HashSet { - 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); - } -} diff --git a/kifu/kifu-core/src/ui/playing_field.rs b/kifu/kifu-core/src/ui/playing_field.rs index cdc9c12..abd5456 100644 --- a/kifu/kifu-core/src/ui/playing_field.rs +++ b/kifu/kifu-core/src/ui/playing_field.rs @@ -1,5 +1,7 @@ -use crate::types::Color; -use crate::{types::GameState, ui::types}; +use crate::{ + types::{Color, GameState}, + ui::types, +}; #[derive(Clone, Debug)] pub struct PlayingFieldView { diff --git a/kifu/kifu-core/src/ui/types.rs b/kifu/kifu-core/src/ui/types.rs index f027ebf..2257d80 100644 --- a/kifu/kifu-core/src/ui/types.rs +++ b/kifu/kifu-core/src/ui/types.rs @@ -1,7 +1,7 @@ use crate::types::{Color, Size}; use crate::{ api::{PlayStoneRequest, Request}, - types::{Board, Coordinate}, + Board, Coordinate, }; #[derive(Clone, Copy, Debug, PartialEq)] @@ -73,7 +73,7 @@ impl From<&Board> for BoardElement { let spaces: Vec = (0..board.size.height) .map(|row| { (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 { jitter: Jitter { x: 0, y: 0 }, liberties: Some(0), diff --git a/kifu/kifu-gtk/Cargo.lock b/kifu/kifu-gtk/Cargo.lock index 7e5c456..f6bc0df 100644 --- a/kifu/kifu-gtk/Cargo.lock +++ b/kifu/kifu-gtk/Cargo.lock @@ -488,6 +488,15 @@ dependencies = [ "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]] name = "gsk4" version = "0.6.3" @@ -657,6 +666,7 @@ dependencies = [ name = "kifu-core" version = "0.1.0" dependencies = [ + "grid", "tokio", ] @@ -751,6 +761,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "num-integer" version = "0.1.45" -- 2.44.1 From edcf20cc25ea7d00e0f3e8c9a56059f64bca9f57 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 3 May 2023 20:29:49 -0400 Subject: [PATCH 08/13] Set up makefiles for the Kifu app --- Makefile | 14 ++++++++++++++ kifu/kifu-core/Makefile | 6 ++++++ kifu/kifu-gtk/Makefile | 5 +++++ 3 files changed, 25 insertions(+) create mode 100644 kifu/kifu-core/Makefile create mode 100644 kifu/kifu-gtk/Makefile diff --git a/Makefile b/Makefile index 27bdf2f..87d5286 100644 --- a/Makefile +++ b/Makefile @@ -35,3 +35,17 @@ ifc-dev: ifc-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 diff --git a/kifu/kifu-core/Makefile b/kifu/kifu-core/Makefile new file mode 100644 index 0000000..bfcdb8e --- /dev/null +++ b/kifu/kifu-core/Makefile @@ -0,0 +1,6 @@ + +test: + cargo watch -x 'nextest run' + +test-oneshot: + cargo nextest run diff --git a/kifu/kifu-gtk/Makefile b/kifu/kifu-gtk/Makefile new file mode 100644 index 0000000..08b2bcb --- /dev/null +++ b/kifu/kifu-gtk/Makefile @@ -0,0 +1,5 @@ +release: + cargo build --release + +dev: + cargo watch -x run -- 2.44.1 From 27d87fcd02d3970c152b7c1f5f51a58b93f0369d Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 3 May 2023 20:42:03 -0400 Subject: [PATCH 09/13] Eliminate the explicit grid --- kifu/kifu-core/src/board.rs | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/kifu/kifu-core/src/board.rs b/kifu/kifu-core/src/board.rs index ac07290..bd89291 100644 --- a/kifu/kifu-core/src/board.rs +++ b/kifu/kifu-core/src/board.rs @@ -10,7 +10,6 @@ pub enum Error { #[derive(Clone, Debug)] pub struct Board { pub size: Size, - pub grid: Grid>, pub groups: Vec, } @@ -45,7 +44,6 @@ impl Board { width: 19, height: 19, }, - grid: Grid::new(19, 19), groups: Vec::new(), } } @@ -90,11 +88,6 @@ impl Board { }; 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 { @@ -106,10 +99,10 @@ impl Board { } pub fn stone(&self, coordinate: &Coordinate) -> Option { - match self.grid.get(coordinate.row, coordinate.column) { - None => None, - Some(val) => *val, - } + self.groups + .iter() + .find(|g| g.contains(coordinate)) + .map(|g| g.color) } pub fn group(&self, coordinate: &Coordinate) -> Option<&Group> { @@ -120,12 +113,6 @@ impl Board { 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 { @@ -263,6 +250,12 @@ pub struct Group { coordinates: HashSet, } +impl Group { + fn contains(&self, coordinate: &Coordinate) -> bool { + self.coordinates.contains(coordinate) + } +} + /* impl Group { pub fn adjacencies(&self, max_column: usize, max_row: usize) -> HashSet { -- 2.44.1 From d68e088500e5a4959f198c6d18d58b709257f505 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 3 May 2023 20:49:20 -0400 Subject: [PATCH 10/13] Implement the self-capture rule --- kifu/kifu-core/src/board.rs | 100 ++++++++---------------------------- kifu/kifu-gtk/Makefile | 2 +- 2 files changed, 23 insertions(+), 79 deletions(-) diff --git a/kifu/kifu-core/src/board.rs b/kifu/kifu-core/src/board.rs index bd89291..4390c53 100644 --- a/kifu/kifu-core/src/board.rs +++ b/kifu/kifu-core/src/board.rs @@ -2,9 +2,10 @@ use crate::{Color, Size}; use grid::Grid; use std::collections::HashSet; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum Error { InvalidPosition, + SelfCapture, } #[derive(Clone, Debug)] @@ -69,6 +70,8 @@ impl Board { return Err(Error::InvalidPosition); } + let old_groups = self.groups.clone(); + let mut friendly_group = self .adjacencies(&coordinate) .into_iter() @@ -95,6 +98,11 @@ impl Board { } } + if self.liberties(&friendly_group) == 0 { + self.groups = old_groups; + return Err(Error::SelfCapture); + } + Ok(()) } @@ -117,13 +125,11 @@ impl Board { pub fn adjacent_groups(&self, group: &Group) -> Vec { let adjacent_spaces = self.group_halo(group).into_iter(); - println!("adjacent spaces: {:?}", adjacent_spaces); let mut grps: Vec = 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; } @@ -151,32 +157,6 @@ impl Board { .into_iter() .filter(|c| self.stone(&c) == None) .count() - - // let adjacencies: HashSet = 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::>(); - 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::>() - .len() - */ } pub fn adjacencies(&self, coordinate: &Coordinate) -> Vec { @@ -204,41 +184,6 @@ impl Board { v.into_iter().filter(|c| self.within_board(c)).collect() } - /* - pub fn adjacencies<'a>( - &'a self, - coordinates: impl Iterator + 'a, - ) -> impl Iterator + '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 } @@ -256,18 +201,6 @@ impl Group { } } -/* -impl Group { - pub fn adjacencies(&self, max_column: usize, max_row: usize) -> HashSet { - self.coordinates - .iter() - .map(|stone| stone.adjacencies(max_column, max_row)) - .flatten() - .collect() - } -} -*/ - #[cfg(test)] mod test { use super::*; @@ -329,6 +262,10 @@ mod test { (Coordinate { column: 6, row: 16 }, Color::White), (Coordinate { column: 7, row: 17 }, 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(), ); @@ -594,8 +531,15 @@ mod test { }); } - fn suicide_is_forbidden() { - assert!(false); + #[test] + fn self_capture_is_forbidden() { + with_example_board(|mut board| { + let res = board.place_stone(Coordinate { column: 18, row: 0 }, Color::Black); + assert_eq!(res, Err(Error::SelfCapture)); + + let res = board.place_stone(Coordinate { column: 5, row: 18 }, Color::Black); + assert_eq!(res, Err(Error::SelfCapture)); + }); } fn captures_preceed_self_capture() { diff --git a/kifu/kifu-gtk/Makefile b/kifu/kifu-gtk/Makefile index 08b2bcb..2297add 100644 --- a/kifu/kifu-gtk/Makefile +++ b/kifu/kifu-gtk/Makefile @@ -2,4 +2,4 @@ release: cargo build --release dev: - cargo watch -x run + cargo watch -x 'run --bin kifu-gtk' -- 2.44.1 From 06157604b9b42b8c6925631f4f3de63abafd8ff1 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 3 May 2023 22:22:22 -0400 Subject: [PATCH 11/13] Implemented self_capture and ko rules --- kifu/kifu-core/Cargo.lock | 34 +++++++- kifu/kifu-core/Cargo.toml | 5 +- kifu/kifu-core/src/board.rs | 165 +++++++++++++++++++++++++++--------- kifu/kifu-core/src/lib.rs | 2 +- kifu/kifu-core/src/types.rs | 88 ++++++++++++++++++- kifu/kifu-gtk/Cargo.lock | 1 + 6 files changed, 248 insertions(+), 47 deletions(-) diff --git a/kifu/kifu-core/Cargo.lock b/kifu/kifu-core/Cargo.lock index f723d62..6880834 100644 --- a/kifu/kifu-core/Cargo.lock +++ b/kifu/kifu-core/Cargo.lock @@ -49,6 +49,7 @@ name = "kifu-core" version = "0.1.0" dependencies = [ "grid", + "thiserror", "tokio", ] @@ -209,6 +210,37 @@ dependencies = [ "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]] name = "tokio" version = "1.26.0" @@ -237,7 +269,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] diff --git a/kifu/kifu-core/Cargo.toml b/kifu/kifu-core/Cargo.toml index 8287207..f7c8c30 100644 --- a/kifu/kifu-core/Cargo.toml +++ b/kifu/kifu-core/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1.26", features = [ "full" ] } -grid = { version = "0.9" } +tokio = { version = "1.26", features = [ "full" ] } +grid = { version = "0.9" } +thiserror = { version = "1" } diff --git a/kifu/kifu-core/src/board.rs b/kifu/kifu-core/src/board.rs index 4390c53..55a3fc5 100644 --- a/kifu/kifu-core/src/board.rs +++ b/kifu/kifu-core/src/board.rs @@ -1,13 +1,6 @@ -use crate::{Color, Size}; -use grid::Grid; +use crate::{BoardError, Color, Size}; use std::collections::HashSet; -#[derive(Clone, Debug, PartialEq)] -pub enum Error { - InvalidPosition, - SelfCapture, -} - #[derive(Clone, Debug)] pub struct Board { pub size: Size, @@ -16,15 +9,15 @@ pub struct Board { impl std::fmt::Display for Board { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - print!(" "); + write!(f, " ")?; // for c in 'A'..'U' { for c in 0..19 { - print!("{:2}", c); + write!(f, "{:2}", c)?; } - println!(""); + writeln!(f, "")?; for row in 0..self.size.height { - print!(" {:2}", row); + write!(f, " {:2}", row)?; for column in 0..self.size.width { match self.stone(&Coordinate { column, row }) { 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 { pub fn new() -> Self { Self { @@ -49,12 +62,12 @@ impl Board { } } - pub fn from_coordinates(coordinates: impl Iterator) -> Self { - let mut s = Self::new(); - for (coordinate, color) in coordinates { - s.place_stone(coordinate, color); - } - s + pub fn from_coordinates( + mut coordinates: impl Iterator, + ) -> Result { + coordinates.try_fold(Self::new(), |board, (coordinate, color)| { + board.place_stone(coordinate, color) + }) } } @@ -65,13 +78,11 @@ pub struct Coordinate { } 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 { if let Some(_) = self.stone(&coordinate) { - return Err(Error::InvalidPosition); + return Err(BoardError::InvalidPosition); } - let old_groups = self.groups.clone(); - let mut friendly_group = self .adjacencies(&coordinate) .into_iter() @@ -99,11 +110,10 @@ impl Board { } if self.liberties(&friendly_group) == 0 { - self.groups = old_groups; - return Err(Error::SelfCapture); + return Err(BoardError::SelfCapture); } - Ok(()) + Ok(self) } pub fn stone(&self, coordinate: &Coordinate) -> Option { @@ -268,7 +278,8 @@ mod test { (Coordinate { column: 18, row: 1 }, Color::White), ] .into_iter(), - ); + ) + .unwrap(); test(board); } @@ -307,7 +318,8 @@ mod test { ), ] .into_iter(), - ); + ) + .unwrap(); assert!(board.group(&Coordinate { column: 18, row: 3 }).is_none()); assert_eq!( board @@ -494,8 +506,7 @@ mod test { #[test] fn it_finds_adjacent_groups() { - with_example_board(|mut board| { - println!("{}", board); + with_example_board(|board| { let group = board .group(&Coordinate { column: 0, row: 0 }) .cloned() @@ -512,17 +523,23 @@ mod test { #[test] fn surrounding_a_group_removes_it() { - with_example_board(|mut board| { - board.place_stone(Coordinate { column: 10, row: 9 }, Color::Black); + with_example_board(|board| { + let board = board + .place_stone(Coordinate { column: 10, row: 9 }, Color::Black) + .unwrap(); 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: 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); + 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: 5, row: 17 }).is_none()); assert!(board.stone(&Coordinate { column: 6, row: 17 }).is_none()); @@ -533,16 +550,84 @@ mod test { #[test] fn self_capture_is_forbidden() { - with_example_board(|mut board| { - let res = board.place_stone(Coordinate { column: 18, row: 0 }, Color::Black); - assert_eq!(res, Err(Error::SelfCapture)); + 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)); + } - let res = board.place_stone(Coordinate { column: 5, row: 18 }, Color::Black); - assert_eq!(res, Err(Error::SelfCapture)); + { + let board = board.clone(); + let res = board.place_stone(Coordinate { column: 5, row: 18 }, Color::Black); + assert_eq!(res, Err(BoardError::SelfCapture)); + } }); } - fn captures_preceed_self_capture() { - assert!(false); + #[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); } } diff --git a/kifu/kifu-core/src/lib.rs b/kifu/kifu-core/src/lib.rs index b9ad5cc..8b598bb 100644 --- a/kifu/kifu-core/src/lib.rs +++ b/kifu/kifu-core/src/lib.rs @@ -2,7 +2,7 @@ mod api; pub use api::{CoreApp, Request, Response}; mod types; -pub use types::{Color, Size}; +pub use types::{BoardError, Color, Size}; pub mod ui; mod board; diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index 6686f54..3b43d16 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -3,6 +3,17 @@ use crate::{ board::{Board, Coordinate}, }; 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)] pub enum Color { @@ -80,6 +91,8 @@ pub struct Player { pub struct GameState { pub board: Board, + pub past_positions: Vec, + pub conversation: Vec, pub current_player: Color, @@ -94,6 +107,7 @@ impl GameState { fn new() -> GameState { GameState { board: Board::new(), + past_positions: vec![], conversation: vec![], current_player: Color::Black, white_player: Player { @@ -109,11 +123,79 @@ impl GameState { } } - fn place_stone(&mut self, coordinate: Coordinate) { - self.board.place_stone(coordinate, self.current_player); + fn place_stone(&mut self, coordinate: Coordinate) -> Result<(), BoardError> { + 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 { Color::White => self.current_player = Color::Black, 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) + ); } } diff --git a/kifu/kifu-gtk/Cargo.lock b/kifu/kifu-gtk/Cargo.lock index f6bc0df..c7faf28 100644 --- a/kifu/kifu-gtk/Cargo.lock +++ b/kifu/kifu-gtk/Cargo.lock @@ -667,6 +667,7 @@ name = "kifu-core" version = "0.1.0" dependencies = [ "grid", + "thiserror", "tokio", ] -- 2.44.1 From 889c3cec94882a6d2d7aaee8d5cdfb92dbb8d584 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 3 May 2023 22:28:37 -0400 Subject: [PATCH 12/13] Don't share liberties with the UI I'm currently not able to calculate them, so I've set them back to None --- kifu/kifu-core/src/ui/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kifu/kifu-core/src/ui/types.rs b/kifu/kifu-core/src/ui/types.rs index 2257d80..027f8db 100644 --- a/kifu/kifu-core/src/ui/types.rs +++ b/kifu/kifu-core/src/ui/types.rs @@ -76,7 +76,7 @@ impl From<&Board> for BoardElement { .map(|column| match board.stone(&Coordinate { column, row }) { Some(color) => IntersectionElement::Filled(StoneElement { jitter: Jitter { x: 0, y: 0 }, - liberties: Some(0), + liberties: None, color, }), None => IntersectionElement::Empty(Request::PlayStoneRequest( -- 2.44.1 From fee2e598d3da700e788aa474ec8d972d65f4b7ea Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 3 May 2023 22:34:10 -0400 Subject: [PATCH 13/13] Remove a debug print --- kifu/kifu-core/src/types.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index 3b43d16..912632f 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -127,7 +127,6 @@ impl GameState { 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); } -- 2.44.1