Implement the basic rules of Go #40

Merged
savanni merged 13 commits from feature/go-rules into main 2023-05-04 02:34:40 +00:00
2 changed files with 243 additions and 121 deletions
Showing only changes of commit 52e27318fe - Show all commits

View File

@ -122,7 +122,11 @@ pub struct Coordinate {
}
impl Coordinate {
fn adjacencies(&self, max_column: u8, max_row: u8) -> impl Iterator<Item = Coordinate> {
fn within_board(&self, width: u8, height: u8) -> bool {
self.column < width && self.row < height
}
fn adjacencies(&self, width: u8, height: u8) -> impl Iterator<Item = Coordinate> {
vec![
if self.column > 0 {
Some(Coordinate {
@ -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<Color> {
@ -234,6 +256,9 @@ impl Board {
let mut fringes = coordinate
.adjacencies(self.size.width as u8 - 1, self.size.height as u8 - 1)
.collect::<Vec<Coordinate>>();
fringes
.iter()
.for_each(|coordinate| println!("group fringes: {:?}", coordinate));
visited.insert(coordinate);
while let Some(coordinate) = fringes.pop() {
@ -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() {

View File

@ -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(