1use std::collections::{HashMap, HashSet, VecDeque};
2use std::fmt::Debug;
3use std::hash::{Hash, Hasher};
4
5use itertools::Itertools;
6use rand::seq::IteratorRandom;
7use rand::Rng;
8
9use crate::board::Player;
10use crate::games::go::link::{LinkHead, LinkNode};
11use crate::games::go::stack_vec::StackVec4;
12use crate::games::go::tile::Tile;
13use crate::games::go::util::OptionU16;
14use crate::games::go::{FlatTile, Linked, Score, Zobrist, GO_MAX_SIZE};
15use crate::symmetry::{D4Symmetry, Symmetry};
16use crate::util::iter::IterExt;
17
18#[derive(Clone)]
26pub struct Chains {
27 size: u8,
28
29 tiles: Vec<TileContent>,
31 groups: Vec<Group>,
32
33 stones_a: u16,
35 empty_list: LinkHead,
36 dead_groups: LinkHead,
37 zobrist: Zobrist,
38}
39
40#[derive(Debug, Clone)]
43pub struct TileContent {
44 pub group_id: OptionU16,
45 pub link: LinkNode,
46}
47
48#[derive(Debug, Clone)]
51pub struct Group {
52 pub color: Player,
53 pub liberty_edge_count: u16,
57 pub zobrist: Zobrist,
62
63 pub stones: LinkHead,
65 pub dead_link: LinkNode,
67}
68
69#[derive(Debug, Clone, Eq, PartialEq)]
70pub struct MinimalPlacement {
71 pub kind: PlacementKind,
72 pub next_zobrist: Zobrist,
73}
74
75#[derive(Debug, Clone, Eq, PartialEq)]
80pub struct SimulatedPlacement {
81 pub tile: FlatTile,
82 pub color: Player,
83 pub kind: PlacementKind,
84
85 pub new_group_stone_count: u16,
87 pub new_group_initial_liberty_edge_count: u16,
88
89 pub merge_friendly: StackVec4,
91 pub clear_enemy: StackVec4,
92
93 pub next_zobrist: Zobrist,
95 pub next_stone_count: u16,
96}
97
98#[derive(Debug, Copy, Clone, Eq, PartialEq)]
99pub enum PlacementKind {
100 Normal,
101 Capture,
102 SuicideSingle,
104 SuicideMulti,
105}
106
107#[derive(Debug, Copy, Clone, Eq, PartialEq)]
108pub struct TileOccupied;
109
110#[derive(Debug, Copy, Clone, Eq, PartialEq)]
111pub enum Territory {
112 Stone(Player),
113 Territory(Player),
114 Both,
115 Neither,
116}
117
118impl Chains {
119 pub fn new(size: u8) -> Self {
120 assert!(size <= GO_MAX_SIZE);
121
122 let area = size as u16 * size as u16;
123 let tiles = (0..area)
124 .map(|i| TileContent {
125 group_id: OptionU16::None,
126 link: LinkNode::full(area, i),
127 })
128 .collect_vec();
129
130 Chains {
131 size,
132 tiles,
133 groups: vec![],
134 stones_a: 0,
135 empty_list: LinkHead::full(area),
136 zobrist: Zobrist::default(),
137 dead_groups: LinkHead::empty(),
138 }
139 }
140
141 pub fn size(&self) -> u8 {
142 self.size
143 }
144
145 pub fn area(&self) -> u16 {
146 self.size as u16 * self.size as u16
147 }
148
149 pub fn stone_count(&self) -> u16 {
150 self.area() - self.empty_count()
151 }
152
153 pub fn stone_count_from(&self, player: Player) -> u16 {
154 match player {
155 Player::A => self.stones_a,
156 Player::B => self.stone_count() - self.stones_a,
157 }
158 }
159
160 pub fn empty_count(&self) -> u16 {
161 self.empty_list.len()
162 }
163
164 pub fn content_at(&self, tile: FlatTile) -> &TileContent {
168 &self.tiles[tile.index() as usize]
169 }
170
171 pub fn group_at(&self, tile: FlatTile) -> Option<&Group> {
172 self.content_at(tile)
173 .group_id
174 .to_option()
175 .map(|id| &self.groups[id as usize])
176 }
177
178 pub fn stone_at(&self, tile: FlatTile) -> Option<Player> {
179 self.group_at(tile).map(|group| group.color)
180 }
181
182 pub fn zobrist(&self) -> Zobrist {
183 self.zobrist
184 }
185
186 pub fn groups(&self) -> impl Iterator<Item = (u16, &Group)> + '_ {
189 self.groups
191 .iter()
192 .enumerate()
193 .filter(|(_, group)| !group.is_dead())
194 .pure_map(|(id, group)| (id as u16, group))
195 }
196
197 pub fn tiles(&self) -> &[TileContent] {
198 &self.tiles
199 }
200
201 pub fn empty_tiles(&self) -> impl ExactSizeIterator<Item = FlatTile> + '_ {
202 self.empty_list.iter(&self.tiles).pure_map(FlatTile::new)
203 }
204
205 pub fn random_empty_tile(&self, rng: &mut impl Rng) -> Option<FlatTile> {
206 self.random_empty_tile_where(rng, |_| true)
207 }
208
209 pub fn random_empty_tile_where(&self, rng: &mut impl Rng, mut f: impl FnMut(FlatTile) -> bool) -> Option<FlatTile> {
213 if self.empty_list.is_empty() {
214 return None;
215 }
216
217 const FULL_SAMPLE_MIN_EMPTY: u16 = 8;
219 const FULL_SAMPLE_MIN_EMPTY_FRAC: f32 = 0.2;
220 const FULL_SAMPLE_TRIES: u32 = 16;
221 const EMPTY_SAMPLE_TRIES: u32 = 16;
222
223 const FRAC_DENOM: u32 = 128;
224 const FRAC_NUMER: u32 = (FULL_SAMPLE_MIN_EMPTY_FRAC * FRAC_DENOM as f32) as u32;
225
226 let empty_count = self.empty_count();
228 let empty_per_64 = empty_count as u32 * FRAC_DENOM / self.area() as u32;
229 if empty_count >= FULL_SAMPLE_MIN_EMPTY && empty_per_64 > FRAC_NUMER {
230 for _ in 0..EMPTY_SAMPLE_TRIES {
231 let tile = FlatTile::new(rng.gen_range(0..self.area()));
232 if self.stone_at(tile).is_none() && f(tile) {
233 return Some(tile);
234 }
235 }
236 }
237
238 for _ in 0..FULL_SAMPLE_TRIES {
240 let tile = self.empty_tiles().choose(rng).unwrap();
241 if f(tile) {
242 return Some(tile);
243 }
244 }
245
246 self.empty_tiles().filter(|&tile| f(tile)).choose(rng)
249 }
250
251 pub fn territory(&self) -> Vec<Territory> {
252 self.compute_territory(true)
253 }
254
255 pub fn score(&self) -> Score {
256 let territory = self.compute_territory(false);
257
258 let mut score_a = self.stone_count_from(Player::A) as u32;
259 let mut score_b = self.stone_count_from(Player::B) as u32;
260
261 for tile in self.empty_tiles() {
262 match territory[tile.index() as usize] {
263 Territory::Stone(_) => unreachable!(),
264 Territory::Territory(Player::A) => score_a += 1,
265 Territory::Territory(Player::B) => score_b += 1,
266 Territory::Both | Territory::Neither => {}
267 }
268 }
269
270 Score { a: score_a, b: score_b }
271 }
272
273 fn compute_territory(&self, include_stones: bool) -> Vec<Territory> {
274 let size = self.size;
275 let area = self.area();
276
277 let mut todo = VecDeque::new();
279
280 let mut state = vec![Territory::Neither; area as usize];
282 if include_stones {
283 for tile in FlatTile::all(size) {
284 if let Some(player) = self.stone_at(tile) {
285 state[tile.index() as usize] = Territory::Stone(player);
286 }
287 }
288 }
289
290 for tile in self.empty_tiles() {
292 let mut reach_a = false;
293 let mut reach_b = false;
294 tile.all_adjacent(size).for_each(|adj| {
295 let stone = self.stone_at(adj);
296 reach_a |= stone == Some(Player::A);
297 reach_b |= stone == Some(Player::B);
298 });
299 if reach_a || reach_b {
300 state[tile.index() as usize] = Territory::from_reach(reach_a, reach_b);
301 todo.push_back(tile)
302 }
303 }
304
305 while let Some(curr) = todo.pop_front() {
307 let curr_state = state[curr.index() as usize];
308
309 for adj in curr.all_adjacent(size) {
310 if self.stone_at(adj).is_none() {
311 let old_state = state[adj.index() as usize];
312 let new_state = old_state.merge_empty(curr_state);
313 if new_state != old_state {
314 state[adj.index() as usize] = new_state;
315 todo.push_back(adj);
316 }
317 }
318 }
319 }
320
321 state
322 }
323
324 pub fn place_stone(&mut self, tile: FlatTile, color: Player) -> Result<SimulatedPlacement, TileOccupied> {
325 self.place_stone_if(tile, color, |_| true).map(|(sim, _)| sim)
326 }
327
328 pub fn place_stone_if(
329 &mut self,
330 tile: FlatTile,
331 color: Player,
332 mut f: impl FnMut(&SimulatedPlacement) -> bool,
333 ) -> Result<(SimulatedPlacement, bool), TileOccupied> {
334 let simulated = self.simulate_place_stone(tile, color)?;
335
336 let cond = f(&simulated);
337 if cond {
338 self.apply_simulated_placement(&simulated);
339 }
340
341 Ok((simulated, cond))
342 }
343
344 pub fn simulate_place_stone_minimal(
345 &self,
346 tile: FlatTile,
347 color: Player,
348 ) -> Result<MinimalPlacement, TileOccupied> {
349 let size = self.size;
350
351 let content = &self.tiles[tile.index() as usize];
352 if content.group_id.is_some() {
353 return Err(TileOccupied);
354 }
355
356 let mut merged_group_initial_liberty_edge_count = 0;
357 let mut any_merged = false;
358 let mut any_captured = false;
359
360 let mut zobrist_with_captures = self.zobrist;
361 let mut zobrist_merged = Zobrist::default();
362
363 let mut adjacent_groups = StackVec4::new();
364 for (adj_i, adj) in tile.all_adjacent(size).enumerate() {
365 let content = self.content_at(adj);
366
367 match content.group_id.to_option() {
368 None => merged_group_initial_liberty_edge_count += 1,
369 Some(group_id) => {
370 let group = &self.groups[group_id as usize];
371
372 adjacent_groups[adj_i] = group_id;
373 let group_adjacency_count = adjacent_groups.count(group_id);
374
375 if group.color == color {
376 any_merged = true;
377 if group_adjacency_count == 1 {
378 zobrist_merged ^= group.zobrist;
379 merged_group_initial_liberty_edge_count += group.liberty_edge_count;
380 }
381 merged_group_initial_liberty_edge_count -= 1;
382 } else {
383 debug_assert!(group.liberty_edge_count as usize >= group_adjacency_count);
384 if group.liberty_edge_count as usize == group_adjacency_count {
385 zobrist_with_captures ^= group.zobrist;
386 any_captured = true;
387 }
388 }
389 }
390 }
391 }
392
393 let kind = if any_captured {
395 PlacementKind::Capture
396 } else if merged_group_initial_liberty_edge_count == 0 {
397 if any_merged {
398 PlacementKind::SuicideMulti
399 } else {
400 PlacementKind::SuicideSingle
401 }
402 } else {
403 PlacementKind::Normal
404 };
405
406 let next_zobrist = if kind.is_suicide() {
407 zobrist_with_captures ^ zobrist_merged
409 } else {
410 let zobrist_stone = Zobrist::for_color_tile(color, tile);
412 zobrist_with_captures ^ zobrist_stone
413 };
414
415 Ok(MinimalPlacement { kind, next_zobrist })
416 }
417
418 #[allow(clippy::collapsible_else_if)]
423 pub fn simulate_place_stone(&self, tile: FlatTile, color: Player) -> Result<SimulatedPlacement, TileOccupied> {
424 let size = self.size;
425 let content = &self.tiles[tile.index() as usize];
426
427 if content.group_id.is_some() {
428 return Err(TileOccupied);
429 }
430
431 let mut new_group_initial_liberty_edge_count = 0;
433 let mut merged_zobrist = Zobrist::default();
434 let mut merged_count = 0;
435 let mut captured_zobrist = Zobrist::default();
436 let mut captured_stone_count = 0;
437
438 let mut adjacent_groups = StackVec4::new();
440 let mut clear_enemy = StackVec4::new();
441 let mut merge_friendly = StackVec4::new();
442
443 for (adj_i, adj) in tile.all_adjacent(size).enumerate() {
445 let content = self.content_at(adj);
446
447 match content.group_id.to_option() {
448 None => new_group_initial_liberty_edge_count += 1,
449 Some(group_id) => {
450 let group = &self.groups[group_id as usize];
451
452 adjacent_groups[adj_i] = group_id;
453 let group_adjacency_count = adjacent_groups.count(group_id);
454
455 if group.color == color {
456 if group_adjacency_count == 1 {
457 merged_count += group.stones.len();
458 new_group_initial_liberty_edge_count += group.liberty_edge_count;
459 merged_zobrist ^= group.zobrist;
460 merge_friendly[adj_i] = group_id;
461 }
462 new_group_initial_liberty_edge_count -= 1;
463 } else {
464 debug_assert!(group.liberty_edge_count as usize >= group_adjacency_count);
465 if group.liberty_edge_count as usize == group_adjacency_count {
466 clear_enemy[adj_i] = group_id;
467 captured_stone_count += group.stones.len();
468 captured_zobrist ^= group.zobrist;
469 }
470 }
471 }
472 }
473 }
474
475 debug_assert!(!merge_friendly.contains_duplicates());
476 debug_assert!(!clear_enemy.contains_duplicates());
477
478 let kind = if !clear_enemy.is_empty() {
480 PlacementKind::Capture
481 } else if new_group_initial_liberty_edge_count == 0 {
482 if merged_count == 0 {
483 PlacementKind::SuicideSingle
484 } else {
485 PlacementKind::SuicideMulti
486 }
487 } else {
488 PlacementKind::Normal
489 };
490
491 let mut next_zobrist = self.zobrist ^ captured_zobrist;
493 let mut next_stone_count = self.stone_count() - captured_stone_count;
494 match kind {
495 PlacementKind::Normal | PlacementKind::Capture => {
496 next_zobrist ^= Zobrist::for_color_tile(color, tile);
497 next_stone_count += 1;
498 }
499 PlacementKind::SuicideSingle => {}
500 PlacementKind::SuicideMulti => {
501 next_zobrist ^= merged_zobrist;
502 next_stone_count -= merged_count;
503 }
504 }
505
506 Ok(SimulatedPlacement {
508 tile,
509 color,
510 kind,
511 new_group_stone_count: 1 + merged_count,
512 new_group_initial_liberty_edge_count,
513 merge_friendly,
514 clear_enemy,
515 next_zobrist,
516 next_stone_count,
517 })
518 }
519
520 pub fn apply_simulated_placement(&mut self, simulated: &SimulatedPlacement) {
521 let &SimulatedPlacement {
522 tile,
523 color,
524 kind,
525 new_group_stone_count,
526 new_group_initial_liberty_edge_count,
527 ref merge_friendly,
528 ref clear_enemy,
529 next_zobrist,
530 next_stone_count,
531 } = simulated;
532
533 let size = self.size();
534 let tile_index = tile.index();
535
536 match kind {
537 PlacementKind::Normal | PlacementKind::Capture => {
538 let tile_zobrist = Zobrist::for_color_tile(color, tile);
541 self.zobrist ^= tile_zobrist;
542 self.empty_list.remove(tile_index, &mut self.tiles);
543 if color == Player::A {
544 self.stones_a += 1;
545 }
546
547 if merge_friendly.len() <= 1 && clear_enemy.is_empty() {
549 let group_id = if let Some(group_id) = merge_friendly.first() {
550 let group = &mut self.groups[group_id as usize];
552
553 group.zobrist ^= tile_zobrist;
554 group.stones.insert_front(tile_index, &mut self.tiles);
555 group.liberty_edge_count = new_group_initial_liberty_edge_count;
556
557 group_id
558 } else {
559 self.allocate_group(Group {
561 color,
562 stones: LinkHead::single(tile_index),
563 liberty_edge_count: new_group_initial_liberty_edge_count,
564 zobrist: tile_zobrist,
565 dead_link: LinkNode::single(),
566 })
567 };
568
569 self.tiles[tile_index as usize].group_id = OptionU16::Some(group_id);
571
572 change_liberty_edges_at(
574 size,
575 &mut self.tiles,
576 &mut self.groups,
577 tile,
578 -1,
579 OptionU16::Some(group_id),
580 );
581 } else {
582 let new_group_id =
585 self.build_merged_group(tile, color, merge_friendly, new_group_initial_liberty_edge_count);
586 debug_assert_eq!(new_group_stone_count, self.groups[new_group_id as usize].stones.len());
587
588 change_liberty_edges_at(
590 size,
591 &mut self.tiles,
592 &mut self.groups,
593 tile,
594 -1,
595 OptionU16::Some(new_group_id),
596 );
597
598 clear_enemy.for_each(|clear_group_id| {
600 self.clear_group(clear_group_id);
601 });
602 }
603 }
604 PlacementKind::SuicideSingle => {
605 }
607 PlacementKind::SuicideMulti => {
608 merge_friendly.for_each(|clear_group_id| {
609 self.clear_group(clear_group_id);
610 });
611 }
612 }
613
614 debug_assert_eq!(self.zobrist, next_zobrist);
615 debug_assert_eq!(self.stone_count(), next_stone_count);
616 }
617
618 fn build_merged_group(
620 &mut self,
621 tile: FlatTile,
622 color: Player,
623 merge_friendly: &StackVec4,
624 new_group_initial_liberty_edge_count: u16,
625 ) -> u16 {
626 let tile_index = tile.index();
627
628 let mut new_group_stones = LinkHead::single(tile_index);
630 let mut new_group_zobrist = Zobrist::for_color_tile(color, tile);
631
632 merge_friendly.for_each(|merge_group_id| {
634 let merge_group = &mut self.groups[merge_group_id as usize];
635
636 new_group_stones.splice_front_take(&mut merge_group.stones, &mut self.tiles);
637 new_group_zobrist ^= merge_group.zobrist;
638
639 self.free_group(merge_group_id);
640 });
641
642 let new_group_id = self.allocate_group(Group {
644 color,
645 stones: new_group_stones.clone(),
646 liberty_edge_count: new_group_initial_liberty_edge_count,
647 zobrist: new_group_zobrist,
648 dead_link: LinkNode::single(),
649 });
650
651 new_group_stones.for_each_mut(&mut self.tiles, |tiles, tile_index| {
653 tiles[tile_index as usize].group_id = OptionU16::Some(new_group_id);
654 });
655
656 new_group_id
657 }
658
659 fn clear_group(&mut self, group_id: u16) {
660 let size = self.size();
661 let clear_group = &mut self.groups[group_id as usize];
662
663 self.zobrist ^= clear_group.zobrist;
665 if clear_group.color == Player::A {
666 self.stones_a -= clear_group.stones.len();
667 }
668
669 {
672 let stones = clear_group.stones.clone();
673 let tiles = &mut self.tiles;
674 let groups = &mut self.groups;
675
676 stones.for_each_mut(tiles, |tiles, tile_index| {
677 let tile = FlatTile::new(tile_index);
679
680 tiles[tile_index as usize].group_id = OptionU16::None;
682
683 change_liberty_edges_at(size, tiles, groups, tile, 1, OptionU16::None);
686 });
687 }
688
689 let clear_group = &mut self.groups[group_id as usize];
690
691 self.empty_list
693 .splice_front_take(&mut clear_group.stones, &mut self.tiles);
694
695 self.free_group(group_id);
697 }
698
699 fn allocate_group(&mut self, new: Group) -> u16 {
700 match self.dead_groups.pop_front(&mut self.groups).to_option() {
701 Some(id) => {
702 self.groups[id as usize] = new;
703 id
704 }
705 None => {
706 let id = self.groups.len() as u16;
707 self.groups.push(new);
708 id
709 }
710 }
711 }
712
713 fn free_group(&mut self, id: u16) {
714 let group = &mut self.groups[id as usize];
715 debug_assert!(group.stones.is_empty());
716 debug_assert!(group.dead_link.is_unconnected_or_single());
717
718 self.groups[id as usize] = Group {
720 color: group.color,
721 stones: LinkHead::empty(),
722 liberty_edge_count: 0,
723 zobrist: Default::default(),
724 dead_link: LinkNode::single(),
725 };
726
727 self.dead_groups.insert_front(id, &mut self.groups);
729 }
730
731 pub fn map_symmetry(&self, sym: D4Symmetry) -> Chains {
732 let size = self.size();
733 let map_tile_index = |index: u16| {
734 FlatTile::new(index)
735 .to_tile(size)
736 .map_symmetry(sym, size)
737 .to_flat(size)
738 .index()
739 };
740
741 let new_tiles = Tile::all(size)
742 .map(|new_tile| {
743 let old_tile = new_tile.map_symmetry(sym.inverse(), size);
744 let old_flat = old_tile.to_flat(size);
745 let old_content = self.tiles[old_flat.index() as usize].clone();
746
747 TileContent {
748 group_id: old_content.group_id,
749 link: old_content.link.map_index(map_tile_index),
750 }
751 })
752 .collect_vec();
753
754 let mut new_zobrist = Zobrist::default();
755
756 let new_groups = self
757 .groups
758 .iter()
759 .map(|old_group| {
760 let new_stones = old_group.stones.map_index(map_tile_index);
761
762 let new_group_zobrist = new_stones
763 .iter(&new_tiles)
764 .map(|tile| Zobrist::for_color_tile(old_group.color, FlatTile::new(tile)))
765 .fold(Zobrist::default(), |a, b| a ^ b);
766
767 new_zobrist ^= new_group_zobrist;
768
769 Group {
770 color: old_group.color,
771 liberty_edge_count: old_group.liberty_edge_count,
772 zobrist: new_group_zobrist,
773 stones: new_stones,
774 dead_link: old_group.dead_link.clone(),
775 }
776 })
777 .collect_vec();
778
779 Chains {
780 size: self.size,
781 tiles: new_tiles,
782 groups: new_groups,
783 stones_a: self.stones_a,
784 empty_list: self.empty_list.map_index(map_tile_index),
785 dead_groups: self.dead_groups.clone(),
786 zobrist: new_zobrist,
787 }
788 }
789
790 pub fn assert_valid(&self) {
791 let size = self.size();
792
793 let mut group_info = HashMap::new();
795 let mut empty_tiles = HashSet::new();
796 let mut stone_count = 0;
797 let mut stone_count_a = 0;
798 let mut stone_count_b = 0;
799
800 for tile in FlatTile::all(size) {
801 let content = self.content_at(tile);
802
803 if let Some(id) = content.group_id.to_option() {
804 assert!((id as usize) < self.groups.len());
806 let group = &self.groups[id as usize];
807
808 assert!(!group.is_dead());
810
811 let group_zobrist = group_info.entry(id).or_insert((Zobrist::default(), HashSet::default()));
813 group_zobrist.0 ^= Zobrist::for_color_tile(group.color, tile);
814 group_zobrist.1.insert(tile.index());
815
816 stone_count += 1;
817 match group.color {
818 Player::A => stone_count_a += 1,
819 Player::B => stone_count_b += 1,
820 }
821 } else {
822 empty_tiles.insert(tile.index());
823 }
824 }
825
826 assert_eq!(self.stone_count(), stone_count);
827 assert_eq!(self.stone_count_from(Player::A), stone_count_a);
828 assert_eq!(self.stone_count_from(Player::B), stone_count_b);
829
830 let mut expected_dead_groups = HashSet::new();
831
832 for (id, group) in self.groups.iter().enumerate() {
834 assert_eq!(group.stones.is_empty(), (group.liberty_edge_count == 0));
836
837 let is_dead = group.stones.is_empty();
839 assert_ne!(group_info.contains_key(&(id as u16)), is_dead);
840
841 let linked_stones = group.stones.assert_valid_and_collect(&self.tiles);
842
843 if let Some(&(zobrist, ref stones)) = group_info.get(&(id as u16)) {
845 assert_eq!(zobrist, group.zobrist);
846 assert_eq!(stones, &linked_stones);
847 } else {
848 assert_eq!(Zobrist::default(), group.zobrist);
849 assert!(linked_stones.is_empty());
850 }
851
852 if group.is_dead() {
853 expected_dead_groups.insert(id as u16);
854 }
855 }
856
857 let linked_dead_groups = self.dead_groups.assert_valid_and_collect(&self.groups);
859 assert_eq!(expected_dead_groups, linked_dead_groups);
860
861 let mut new_zobrist = Zobrist::default();
863 for tile in FlatTile::all(size) {
864 if let Some(player) = self.stone_at(tile) {
865 let value = Zobrist::for_color_tile(player, tile);
866 new_zobrist ^= value;
867 }
868 }
869 assert_eq!(self.zobrist, new_zobrist, "Invalid zobrist hash");
870
871 let linked_empty_tiles = self.empty_list.assert_valid_and_collect(&self.tiles);
873 assert_eq!(empty_tiles, linked_empty_tiles);
874 }
875}
876
877impl Territory {
878 pub fn player(self) -> Option<Player> {
879 match self {
880 Territory::Stone(player) => Some(player),
881 Territory::Territory(player) => Some(player),
882 Territory::Neither | Territory::Both => None,
883 }
884 }
885
886 fn merge_empty(self, other: Territory) -> Territory {
887 let (left_a, left_b) = self.to_reach();
888 let (right_a, right_b) = other.to_reach();
889 Territory::from_reach(left_a || right_a, left_b || right_b)
890 }
891
892 fn to_reach(self) -> (bool, bool) {
893 match self {
894 Territory::Stone(Player::A) => (true, false),
895 Territory::Stone(Player::B) => (false, true),
896 Territory::Territory(Player::A) => (true, false),
897 Territory::Territory(Player::B) => (false, true),
898 Territory::Both => (true, true),
899 Territory::Neither => (false, false),
900 }
901 }
902
903 fn from_reach(reach_a: bool, reach_b: bool) -> Territory {
904 match (reach_a, reach_b) {
905 (false, false) => Territory::Neither,
906 (true, true) => Territory::Both,
907 (true, false) => Territory::Territory(Player::A),
908 (false, true) => Territory::Territory(Player::B),
909 }
910 }
911}
912
913fn change_liberty_edges_at(
915 size: u8,
916 tiles: &mut [TileContent],
917 groups: &mut [Group],
918 tile: FlatTile,
919 delta: i16,
920 skip_group_id: OptionU16,
921) {
922 for adj in tile.all_adjacent(size) {
923 if let Some(group_id) = tiles[adj.index() as usize].group_id.to_option() {
924 if OptionU16::Some(group_id) != skip_group_id {
925 let count = &mut groups[group_id as usize].liberty_edge_count;
926 *count = count.wrapping_add_signed(delta);
927 }
928 }
929 }
930}
931
932impl Group {
933 fn is_dead(&self) -> bool {
934 self.stones.is_empty()
935 }
936}
937
938impl Eq for Chains {}
939
940impl PartialEq for Chains {
941 fn eq(&self, other: &Self) -> bool {
942 self.tiles.len() == other.tiles.len()
944 && self.zobrist() == other.zobrist()
945 && FlatTile::all(self.size).all(|tile| self.stone_at(tile) == other.stone_at(tile))
946 }
947}
948
949impl Hash for Chains {
950 fn hash<H: Hasher>(&self, state: &mut H) {
951 self.zobrist().hash(state);
952 }
953}
954
955impl PlacementKind {
956 pub fn is_suicide(self) -> bool {
957 match self {
958 PlacementKind::Normal | PlacementKind::Capture => false,
959 PlacementKind::SuicideSingle | PlacementKind::SuicideMulti => true,
960 }
961 }
962
963 pub fn removes_existing_stones(self) -> bool {
964 match self {
965 PlacementKind::Normal | PlacementKind::SuicideSingle => false,
966 PlacementKind::Capture | PlacementKind::SuicideMulti => true,
967 }
968 }
969}
970
971impl Linked for TileContent {
972 fn link(&self) -> &LinkNode {
973 &self.link
974 }
975
976 fn link_mut(&mut self) -> &mut LinkNode {
977 &mut self.link
978 }
979}
980
981impl Linked for Group {
982 fn link(&self) -> &LinkNode {
983 &self.dead_link
984 }
985
986 fn link_mut(&mut self) -> &mut LinkNode {
987 &mut self.dead_link
988 }
989}