From 52e27318fe9108e84e59f797fe4553463ef54322 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 27 Apr 2023 21:53:54 -0400 Subject: [PATCH] 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(