Implement the basic rules of Go #40
|
@ -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,27 +444,47 @@ 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);
|
||||
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 },
|
||||
|
@ -439,6 +545,36 @@ mod test {
|
|||
}),
|
||||
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 {
|
||||
|
@ -448,32 +584,18 @@ mod test {
|
|||
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() {
|
||||
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() {
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue