From 915ec4cb9d151173c9ea1fb3599e22dee982a604 Mon Sep 17 00:00:00 2001 From: Cameron Martin Date: Sun, 10 May 2020 21:55:16 +0100 Subject: [PATCH] Refactor terminal detection. Detection of terminal conditions is now done in it's own dedicated method, to prepare for using df-pn (#20). --- src/go.rs | 35 +++++++++++++++--------- src/puzzle.rs | 74 ++++++++++++++++++++++++--------------------------- 2 files changed, 58 insertions(+), 51 deletions(-) diff --git a/src/go.rs b/src/go.rs index 9ea9b24..0c14a3e 100644 --- a/src/go.rs +++ b/src/go.rs @@ -231,12 +231,19 @@ impl GoBoard { } } +#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)] +pub enum PassState { + NoPass, + PassedOnce, + PassedTwice, +} + #[derive(Debug, PartialEq, Clone, Copy)] pub struct GoGame { ko_violations: BitBoard, board: GoBoard, pub current_player: GoPlayer, - pub last_move_pass: bool, + pub pass_state: PassState, } #[derive(Debug, PartialEq)] @@ -254,7 +261,7 @@ impl GoGame { board: GoBoard::empty(), ko_violations: BitBoard::empty(), current_player: GoPlayer::Black, - last_move_pass: false, + pass_state: PassState::NoPass, } } @@ -263,7 +270,7 @@ impl GoGame { board, ko_violations: BitBoard::empty(), current_player, - last_move_pass: false, + pass_state: PassState::NoPass, } } @@ -343,7 +350,7 @@ impl GoGame { ko_violations, board: new_board, current_player: next_player, - last_move_pass: false, + pass_state: PassState::NoPass, }) } @@ -352,7 +359,11 @@ impl GoGame { board: self.board, ko_violations: BitBoard::empty(), current_player: self.current_player.flip(), - last_move_pass: true, + pass_state: match self.pass_state { + PassState::NoPass => PassState::PassedOnce, + PassState::PassedOnce => PassState::PassedTwice, + PassState::PassedTwice => panic!("Cannot pass when the game is finished"), + }, } } @@ -375,10 +386,10 @@ impl GoGame { games.push(( self.pass(), - if self.last_move_pass { - Move::PassTwice - } else { - Move::PassOnce + match self.pass_state { + PassState::NoPass => Move::PassOnce, + PassState::PassedOnce => Move::PassTwice, + PassState::PassedTwice => panic!("Cannot generate moves when the game is finished"), }, )); @@ -527,9 +538,9 @@ mod tests { #[test] fn pass_sets_last_move_pass() { let game = GoGame::from_sgf(include_str!("test_sgfs/ko_rule_simple.sgf")); - let new_game = game.pass(); + let game = game.pass(); - assert!(new_game.last_move_pass); + assert_eq!(game.pass_state, PassState::PassedOnce); } #[test] @@ -538,7 +549,7 @@ mod tests { let game = game.pass(); let game = game.play_placing_move(BoardPosition::new(13, 7)).unwrap(); - assert!(!game.last_move_pass); + assert_eq!(game.pass_state, PassState::NoPass); } #[test] diff --git a/src/puzzle.rs b/src/puzzle.rs index 5cf0c69..c8098ea 100644 --- a/src/puzzle.rs +++ b/src/puzzle.rs @@ -1,7 +1,7 @@ mod profiler; mod proof_number; -use crate::go::{GoBoard, GoGame, GoPlayer, Move}; +use crate::go::{GoBoard, GoGame, GoPlayer, Move, PassState}; use petgraph::stable_graph::NodeIndex; use petgraph::stable_graph::StableGraph; use petgraph::visit::EdgeRef; @@ -64,17 +64,17 @@ impl AndOrNode { } } - pub fn create_true_leaf() -> AndOrNode { - AndOrNode { - proof_number: ProofNumber::finite(0), - disproof_number: ProofNumber::infinite(), - } - } - - pub fn create_false_leaf() -> AndOrNode { - AndOrNode { - proof_number: ProofNumber::infinite(), - disproof_number: ProofNumber::finite(0), + pub fn create_terminal(value: bool) -> AndOrNode { + if value { + AndOrNode { + proof_number: ProofNumber::finite(0), + disproof_number: ProofNumber::infinite(), + } + } else { + AndOrNode { + proof_number: ProofNumber::infinite(), + disproof_number: ProofNumber::finite(0), + } } } @@ -157,33 +157,8 @@ impl Puzzle

{ self.profiler.add_nodes(moves.len() as u8); for (child, board_move) in moves { - let new_node = if board_move == Move::PassTwice { - // If both players pass sequentially, the game ends and - // the player to pass second loses. - if game.current_player == self.player { - AndOrNode::create_false_leaf() - } else { - AndOrNode::create_true_leaf() - } - // If the defender has unconditionally alive blocks, the defender wins - } else if !child - .get_board() - .unconditionally_alive_blocks_for_player(self.defender()) - .is_empty() - { - if self.defender() == self.player { - AndOrNode::create_true_leaf() - } else { - AndOrNode::create_false_leaf() - } - // If the defender doesn't have any space to create eyes, the attacker wins - } else if self.is_defender_dead(child.get_board()) { - if self.attacker == self.player { - AndOrNode::create_true_leaf() - } else { - AndOrNode::create_false_leaf() - } - // Otherwise, the result is a non-terminal node + let new_node = if let Some(game_theoretic_value) = self.is_terminal(child) { + AndOrNode::create_terminal(game_theoretic_value) } else { AndOrNode::create_non_terminal_leaf() }; @@ -195,6 +170,27 @@ impl Puzzle

{ } } + fn is_terminal(&self, game: GoGame) -> Option { + // If both players pass sequentially, the game ends and + // the player to pass second loses. + if game.pass_state == PassState::PassedTwice { + Some(game.current_player == self.player) + // If the defender has unconditionally alive blocks, the defender wins + } else if !game + .get_board() + .unconditionally_alive_blocks_for_player(self.defender()) + .is_empty() + { + Some(self.defender() == self.player) + // If the defender doesn't have any space to create eyes, the attacker wins + } else if self.is_defender_dead(game.get_board()) { + Some(self.attacker == self.player) + // Otherwise, the result is a non-terminal node + } else { + None + } + } + /// A conservative estimate on whether the group is dead. /// true means it's definitely dead, false otherwise fn is_defender_dead(&self, board: GoBoard) -> bool {