1use std::cmp::Ordering;
2use std::fmt::Debug;
3use std::hash::{Hash, Hasher};
4use std::ops::ControlFlow;
5
6use internal_iterator::InternalIterator;
7use nohash_hasher::IntSet;
8use rand::Rng;
9
10use crate::board::{
11 AllMovesIterator, AvailableMovesIterator, Board, BoardDone, BoardMoves, BoardSymmetry, Outcome, PlayError, Player,
12};
13use crate::games::go::chains::Chains;
14use crate::games::go::tile::Tile;
15use crate::games::go::{PlacementKind, Rules, Territory, TileOccupied, Zobrist, GO_MAX_SIZE};
16use crate::symmetry::D4Symmetry;
17use crate::util::iter::IterExt;
18
19#[derive(Clone, Eq, PartialEq)]
21pub struct GoBoard {
22 rules: Rules,
23 chains: Chains,
24 next_player: Player,
25 state: State,
26 history: IntSet<Zobrist>,
27 komi: Komi,
28}
29
30#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
31pub enum Move {
32 Pass,
33 Place(Tile),
34}
35
36#[derive(Debug, Copy, Clone, Eq, PartialEq)]
37pub struct Score {
38 pub a: u32,
39 pub b: u32,
40}
41
42impl Score {
43 pub fn to_outcome(self, komi: Komi) -> Outcome {
45 let score_a = self.a as i32 * 2;
46 let total_b = self.b as i32 * 2 + komi.as_int() as i32;
47 match score_a.cmp(&total_b) {
48 Ordering::Less => Outcome::WonBy(Player::B),
49 Ordering::Equal => Outcome::Draw,
50 Ordering::Greater => Outcome::WonBy(Player::A),
51 }
52 }
53}
54
55#[derive(Debug, Copy, Clone, Eq, PartialEq)]
57pub struct Komi {
58 komi_2: i16,
59}
60
61#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
62pub enum State {
63 Normal,
64 Passed,
65 Done(Outcome),
66}
67
68impl GoBoard {
69 pub fn new(size: u8, komi: Komi, rules: Rules) -> GoBoard {
70 GoBoard {
71 rules,
72 chains: Chains::new(size),
73 next_player: Player::A,
74 state: State::Normal,
75 history: Default::default(),
76 komi,
77 }
78 }
79
80 pub fn from_parts(
81 rules: Rules,
82 chains: Chains,
83 next_player: Player,
84 state: State,
85 history: IntSet<Zobrist>,
86 komi: Komi,
87 ) -> GoBoard {
88 GoBoard {
89 rules,
90 chains,
91 next_player,
92 state,
93 history,
94 komi,
95 }
96 }
97
98 pub fn size(&self) -> u8 {
99 self.chains().size()
100 }
101
102 pub fn area(&self) -> u16 {
103 self.chains().area()
104 }
105
106 pub fn rules(&self) -> Rules {
107 self.rules
108 }
109
110 pub fn komi(&self) -> Komi {
111 self.komi
112 }
113
114 pub fn chains(&self) -> &Chains {
115 &self.chains
116 }
117
118 pub fn state(&self) -> State {
119 self.state
120 }
121
122 pub fn history(&self) -> &IntSet<Zobrist> {
123 &self.history
124 }
125
126 pub fn replace_history(&mut self, history: IntSet<Zobrist>) -> IntSet<Zobrist> {
128 std::mem::replace(&mut self.history, history)
129 }
130
131 pub fn clear_history(&mut self) {
132 self.history.clear();
133 }
134
135 pub fn clone_without_history(&self) -> Self {
136 Self::from_parts(
137 self.rules,
138 self.chains.clone(),
139 self.next_player,
140 self.state,
141 Default::default(),
142 self.komi,
143 )
144 }
145
146 pub fn stone_count(&self) -> u16 {
147 self.chains.stone_count()
148 }
149
150 pub fn stone_count_from(&self, player: Player) -> u16 {
151 self.chains.stone_count_from(player)
152 }
153
154 pub fn empty_count(&self) -> u16 {
155 self.chains.empty_count()
156 }
157
158 pub fn stone_at(&self, tile: Tile) -> Option<Player> {
159 self.chains().stone_at(tile.to_flat(self.size()))
160 }
161
162 pub fn empty_tiles(&self) -> impl ExactSizeIterator<Item = Tile> + '_ {
163 self.chains()
164 .empty_tiles()
165 .pure_map(move |tile| tile.to_tile(self.size()))
166 }
167
168 pub fn current_score(&self) -> Score {
169 self.chains().score()
170 }
171
172 pub fn territory(&self) -> Vec<Territory> {
175 self.chains().territory()
176 }
177
178 pub fn zobrist(&self) -> Zobrist {
183 let mut result = self.chains().zobrist();
185 result ^= Zobrist::for_color_turn(self.next_player);
186 result ^= Zobrist::for_pass_state(self.state);
187 result
188 }
189
190 pub fn random_available_place_move(&self, rng: &mut impl Rng) -> Result<Option<Move>, BoardDone> {
191 self.check_done()?;
192
193 let tile = self.chains.random_empty_tile_where(rng, |tile| {
194 let mv = Move::Place(tile.to_tile(self.size()));
195 self.is_available_move(mv).unwrap()
196 });
197 let mv = tile.map(|tile| Move::Place(tile.to_tile(self.size())));
198
199 Ok(mv)
200 }
201
202 pub fn assert_valid(&self) {
203 self.chains().assert_valid();
205
206 if !self.rules().needs_history() {
207 assert!(self.history.is_empty())
208 }
209 }
210}
211
212fn is_available_move_sim(rules: &Rules, history: &IntSet<Zobrist>, kind: PlacementKind, next_zobrist: Zobrist) -> bool {
213 match kind {
215 PlacementKind::Normal => {}
216 PlacementKind::Capture => {}
217 PlacementKind::SuicideSingle => return false,
218 PlacementKind::SuicideMulti => {
219 if !rules.allow_multi_stone_suicide {
220 return false;
221 }
222 }
223 }
224
225 if !rules.allow_repeating_tiles() && history.contains(&next_zobrist) {
228 return false;
229 }
230
231 true
232}
233
234impl Board for GoBoard {
235 type Move = Move;
236
237 fn next_player(&self) -> Player {
238 self.next_player
239 }
240
241 fn is_available_move(&self, mv: Self::Move) -> Result<bool, BoardDone> {
242 self.check_done()?;
243
244 let result = match mv {
245 Move::Pass => true,
246 Move::Place(tile) => {
247 if !tile.exists(self.size()) {
248 false
249 } else {
250 let tile = tile.to_flat(self.size());
251 match self.chains.simulate_place_stone_minimal(tile, self.next_player) {
252 Ok(sim) => is_available_move_sim(&self.rules, &self.history, sim.kind, sim.next_zobrist),
253 Err(TileOccupied) => false,
254 }
255 }
256 }
257 };
258
259 Ok(result)
260 }
261
262 fn play(&mut self, mv: Self::Move) -> Result<(), PlayError> {
263 self.check_done()?;
265
266 let curr = self.next_player;
267 let other = curr.other();
268
269 match mv {
270 Move::Pass => {
271 self.next_player = other;
276 self.state = match self.state {
277 State::Normal => State::Passed,
278 State::Passed => State::Done(self.current_score().to_outcome(self.komi)),
279 State::Done(_) => unreachable!(),
280 };
281 }
282 Move::Place(tile) => {
283 let prev_zobrist = self.chains.zobrist();
284
285 {
287 let tile = tile.to_flat(self.size());
288 let rules = &self.rules;
289 let history = &self.history;
290 let place_result = self.chains.place_stone_if(tile, curr, |sim| {
291 is_available_move_sim(rules, history, sim.kind, sim.next_zobrist)
292 });
293 match place_result {
294 Ok((_, true)) => {}
295 Ok((_, false)) => return Err(PlayError::UnavailableMove),
296 Err(TileOccupied) => return Err(PlayError::UnavailableMove),
297 }
298 }
299
300 if self.rules.needs_history() {
302 self.history.insert(prev_zobrist);
303 }
304
305 self.next_player = other;
307 self.state = State::Normal;
308 }
309 }
310
311 Ok(())
312 }
313
314 fn outcome(&self) -> Option<Outcome> {
315 match self.state {
316 State::Normal | State::Passed => None,
317 State::Done(outcome) => Some(outcome),
318 }
319 }
320
321 fn can_lose_after_move() -> bool {
322 true
323 }
324}
325
326impl<'a> BoardMoves<'a, GoBoard> for GoBoard {
327 type AllMovesIterator = AllMovesIterator<GoBoard>;
328 type AvailableMovesIterator = AvailableMovesIterator<'a, GoBoard>;
329
330 fn all_possible_moves() -> Self::AllMovesIterator {
331 AllMovesIterator::default()
332 }
333
334 fn available_moves(&'a self) -> Result<Self::AvailableMovesIterator, BoardDone> {
335 AvailableMovesIterator::new(self)
336 }
337}
338
339impl Komi {
340 pub fn zero() -> Self {
341 Komi { komi_2: 0 }
342 }
343
344 pub fn new(komi_2: i16) -> Self {
345 Komi { komi_2 }
346 }
347
348 pub fn as_int(self) -> i16 {
349 self.komi_2
350 }
351
352 pub fn as_float(self) -> f32 {
353 self.komi_2 as f32 / 2.0
354 }
355}
356
357impl std::ops::Neg for Komi {
358 type Output = Self;
359
360 fn neg(self) -> Self::Output {
361 Komi { komi_2: -self.komi_2 }
362 }
363}
364
365impl InternalIterator for AllMovesIterator<GoBoard> {
366 type Item = Move;
367
368 fn try_for_each<R, F>(self, mut f: F) -> ControlFlow<R>
369 where
370 F: FnMut(Self::Item) -> ControlFlow<R>,
371 {
372 f(Move::Pass)?;
373 for tile in Tile::all(GO_MAX_SIZE) {
374 f(Move::Place(tile))?;
375 }
376 ControlFlow::Continue(())
377 }
378}
379
380impl InternalIterator for AvailableMovesIterator<'_, GoBoard> {
381 type Item = Move;
382
383 fn try_for_each<R, F>(self, mut f: F) -> ControlFlow<R>
384 where
385 F: FnMut(Self::Item) -> ControlFlow<R>,
386 {
387 let board = self.board();
394
395 f(Move::Pass)?;
396 for tile in Tile::all(board.size()) {
397 if board.is_available_move(Move::Place(tile)).unwrap() {
398 f(Move::Place(tile))?;
399 }
400 }
401
402 ControlFlow::Continue(())
403 }
404
405 }
407
408impl BoardSymmetry<GoBoard> for GoBoard {
409 type Symmetry = D4Symmetry;
410 type CanonicalKey = Zobrist;
411
412 fn map(&self, sym: D4Symmetry) -> Self {
413 GoBoard {
414 rules: self.rules,
415 chains: self.chains.map_symmetry(sym),
416 next_player: self.next_player,
417 state: self.state,
418 history: self.history.clone(),
419 komi: self.komi,
420 }
421 }
422
423 fn map_move(&self, sym: D4Symmetry, mv: Move) -> Move {
424 match mv {
425 Move::Pass => Move::Pass,
426 Move::Place(tile) => Move::Place(tile.map_symmetry(sym, self.size())),
427 }
428 }
429
430 fn canonical_key(&self) -> Zobrist {
431 self.chains().zobrist()
433 }
434}
435
436impl Hash for GoBoard {
437 fn hash<H: Hasher>(&self, state: &mut H) {
439 self.zobrist().hash(state);
440 }
441}