Skip to content

Commit

Permalink
Add support for positional super-ko
Browse files Browse the repository at this point in the history
Add a small set with the eight most recent zobrist hashes for detecting
ko with the is_valid method. This is used to detect positional super-ko
during play.
  • Loading branch information
kblomdahl committed Nov 29, 2017
1 parent 8381ca7 commit 42ec765
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 5 deletions.
94 changes: 89 additions & 5 deletions src/go/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
// limitations under the License.

mod circular_buf;
mod small_set;
pub mod symmetry;
mod zobrist;

use self::circular_buf::CircularBuf;
use self::small_set::SmallSet;
use std::fmt;

#[repr(u8)]
Expand Down Expand Up @@ -113,7 +115,10 @@ pub struct Board {
count: u16,

/// The zobrist hash of the current board state.
zobrist_hash: u64
zobrist_hash: u64,

/// The zobrist hash of the most recent board positions.
zobrist_history: SmallSet
}

impl Clone for Board {
Expand All @@ -123,7 +128,8 @@ impl Clone for Board {
next_vertex: self.next_vertex,
history: self.history.clone(),
count: self.count,
zobrist_hash: self.zobrist_hash
zobrist_hash: self.zobrist_hash,
zobrist_history: self.zobrist_history.clone()
}
}
}
Expand All @@ -136,7 +142,8 @@ impl Board {
next_vertex: [0; 361],
history: CircularBuf::new(),
count: 0,
zobrist_hash: 0
zobrist_hash: 0,
zobrist_history: SmallSet::new()
};

for i in 361..368 {
Expand Down Expand Up @@ -281,6 +288,30 @@ impl Board {
}
}

/// Returns the zobrist hash adjustment that would need to be done if the
/// group at the given index was capture and was of the given color.
///
/// # Arguments
///
/// * `color` - the color of the group to capture
/// * `index` - the index of a stone in the group
///
fn capture_if(&self, color: usize, index: usize) -> u64 {
let mut adjustment = 0;
let mut current = index;

loop {
adjustment ^= zobrist::TABLE[color][current];

current = self.next_vertex[current] as usize;
if current == index {
break
}
}

adjustment
}

/// Connects the chains of the two vertices into one chain. This method
/// should not be called with the same group twice as that will result
/// in a corrupted chain.
Expand Down Expand Up @@ -360,6 +391,36 @@ impl Board {
}
}

/// Returns whether playing the given rule violates the super-ko
/// rule. This functions assumes the given move is not suicide and
/// does not play on top of another stone, these pre-conditions can
/// be checked with the `_is_valid` function.
///
/// # Arguments
///
/// * `color` - the color of the move
/// * `index` - the HW index of the move
///
pub fn _is_ko(&self, color: Color, index: usize) -> bool {
let mut zobrist_pretend = self.zobrist_hash ^ zobrist::TABLE[color as usize][index];
let opponent = color.opposite() as u8;

if N!(self.vertices, index) == opponent && !self.has_two_liberties(index + 19) {
zobrist_pretend ^= self.capture_if(opponent as usize, index + 19);
}
if E!(self.vertices, index) == opponent && !self.has_two_liberties(index + 1) {
zobrist_pretend ^= self.capture_if(opponent as usize, index + 1);
}
if S!(self.vertices, index) == opponent && !self.has_two_liberties(index - 19) {
zobrist_pretend ^= self.capture_if(opponent as usize, index - 19);
}
if W!(self.vertices, index) == opponent && !self.has_two_liberties(index - 1) {
zobrist_pretend ^= self.capture_if(opponent as usize, index - 1);
}

self.zobrist_history.contains(zobrist_pretend)
}

/// Returns whether the given move is valid according to the
/// Tromp-Taylor rules.
///
Expand All @@ -370,7 +431,9 @@ impl Board {
/// * `y` - the row of the move
///
pub fn is_valid(&self, color: Color, x: usize, y: usize) -> bool {
self._is_valid(color, 19 * y + x)
let index = 19 * y + x;

self._is_valid(color, index) && !self._is_ko(color, index)
}

/// Place the given stone on the board without checking if it is legal, the
Expand Down Expand Up @@ -416,6 +479,7 @@ impl Board {
// 2. the circular stack starts with all buffers as zero, so there is no need to
// keep track of the initial board state.
self.history.push(&self.vertices);
self.zobrist_history.push(self.zobrist_hash);
}

/// Fills the given array with all liberties of in the provided array of vertices
Expand Down Expand Up @@ -545,7 +609,12 @@ impl Board {
}
}

// x
// compute the distance to all neighbours using a dynamic programming
// approach where we at each iteration try to update the neighbours of
// each updated vertex, and if the distance we tried to set was smaller
// than the current distance we try to update that vertex neighbours.
//
// This is equivalent to a Bellman–Ford algorithm for the shortest path.
while probes.len() > 0 {
let index = probes.pop().unwrap();
let t = territory[index] + 1;
Expand Down Expand Up @@ -811,4 +880,19 @@ mod tests {

assert_eq!(board.get_num_liberties_if(Color::Black, 1), 5);
}

/// Test that we can accurately detect ko using the simplest possible
/// corner ko.
#[test]
fn ko() {
let mut board = Board::new();

board.place(Color::Black, 0, 0);
board.place(Color::Black, 0, 2);
board.place(Color::Black, 1, 1);
board.place(Color::White, 1, 0);
board.place(Color::White, 0, 1);

assert!(!board.is_valid(Color::Black, 0, 0));
}
}
71 changes: 71 additions & 0 deletions src/go/small_set.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2017 Karl Sundequist Blomdahl <karl.sundequist.blomdahl@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// A LRA set that only keeps the eight most recently added values.
#[derive(Clone)]
pub struct SmallSet {
buf: [u64; 8],
count: usize
}

impl SmallSet {
/// Returns an empty set.
pub fn new() -> SmallSet {
SmallSet { buf: [0; 8], count: 0 }
}

/// Adds the given value to this set, removing the oldest value if the set overflows.
///
/// # Arguments
///
/// * `value` - the value to add to the set
///
pub fn push(&mut self, value: u64) {
self.buf[self.count] = value;
self.count += 1;

if self.count == 8 {
self.count = 0;
}
}

/// Returns true if this set contains the given value.
///
/// # Arguments
///
/// * `other` - the value to look for
///
pub fn contains(&self, other: u64) -> bool {
(0..8).any(|x| self.buf[x] == other)
}
}

#[cfg(test)]
mod tests {
use go::small_set::*;

#[test]
fn check() {
let mut s = SmallSet::new();

s.push(1);
s.push(2);
s.push(3);

assert!(s.contains(1));
assert!(s.contains(2));
assert!(s.contains(3));
assert!(!s.contains(4));
}
}

0 comments on commit 42ec765

Please sign in to comment.