BinMatrix
I (Kjeld Petersen) have been working on a library implementation of a 19x19 binary matrix, to implement the game rules by simple matrix calculations. You are free to use the method, but please give credits if used. BinMatrix is implemented with 19 integers (32 bits). At the end of this page is a list of operations that can be performed on BinMatrix'es.
NB: There could be some benefit in implementing a lowestRowWithBitSet and highestRowWithBitSet, that should keep track of how many of the 19 integeres actually should be compared in all methods. I need to perform some speed testing, to see if this would benefit the overall algorithm. But in general only few lines are used when doing this algorithm. And it is not that often that a capture is spread across the hole board.
Implementing the game rules can be done with:
BinMatrix B,W,oldB,oldW,P,S,N int bScore,wScore Player turn = BLACK
In the move method I do the below checking. Particular line 4 and 5 are important. 4) Checks if there are any surrounding opponent groups. 5) Expand the surrounding group and find the groups with no liberties, and count the possible captured stones. Line 6+7 checks, if none is captured, that it is not suicide. If only 1 stone is captured it may be a KO, so line 9 checks for that. The rest is just copying the new positions, and removing the captured stones by a simple XOR.
1: if( B.is(pos) || W.is(pos) ) return OCCUPIED 2: if( turn == BLACK ) 3: P.copy(B).set(pos) 4: capture = ( S.none().set(pos).neighbors4().and(W).equals(false) ) 5: ? 0 : N.copy( S.expandTo(W) ).neighbors4().minus(P).neighbors4().and(S).expandTo(S).xor(S).count() 6: if( capture == 0 ) 7: if( S.none().set(pos).expandTo(P).neighbors4().minus(W).equals(false) ) return SUICIDE 8: else 9: if( capture == 1 && P.equals(oldB) ) return KO 10: bScore += capture 11: oldW.copy(W) 12: W.xor(N) 13: oldB.copy(B) 14: B.set(pos) 15: else 16: P.copy(W).set(pos) 17: capture = ( S.none().set(pos).neighbors4().and(B).equals(false) ) 18: ? 0 : N.copy( S.expandTo(B) ).neighbors4().minus(P).neighbors4().and(S).expandTo(S).xor(S).count() 19: if( capture == 0 ) 20: if( S.none().set(pos).expandTo(P).neighbors4().minus(B).equals(false) ) return SUICIDE 21: else 22: if( capture == 1 && P.equals(oldW) ) return KO 23: wScore += capture 24: oldB.copy(B) 25: B.xor(N) 26: oldW.copy(W) 27: W.set(pos) 28: turn = opponent(turn) 29: return APPROVED
Here is how it works:
Mask the neighbors with the white stones. The test S.equals(false) will now give false, so capture should be calculated
Expand S so it covers the connected stone, with a series of grow() + and(W) until no there are no more changes. We now have a BinMatrix S with the surrounding white stones to the new stone. The BinMatrix N is simply a copy of S
Remove all the markings where there are black stones. So that must be the liberties of the surrounding white stones. Here I could test if N.equals(false) to see if all surrounding stones should be captured, and calculate capture=N.copy(S).count(). But I need to perform some speed test to see if this would benefit the whole algorithm. It depends on what is more common. If we capture, then it is a full capture, where the played stone has no opponent stones as neighbor after the capture. Or it is more common it does have some neighbor stones after any capture. N.neighbors4().and(S).expandTo(S).xor(S) does have some costly operations. Particular expandTo(S).
Mask this with the surrounding white stones again. So this "group" should NOT be captured, since it have some liberties.
We can now switch over from the stones that should NOT be captured, to the stones that SHOULD be captured with a simple xor. Now calculate capture = N.count()
My BinMatrix class have the following function:
Constructors
Manipulators
- set(position) // a[pos/19] |= 1<<(pos%19)
- clear(position) // a[pos/19] &= ~(1<<(pos%19))
- toogle(position) // a[pos/19] ^= 1<<(pos%19)
- all() // set all bits to 1
- none() // clear all bits (0's)
- copy() // deep copy
- clone() // creates a clone to work on
Binary operators
- and(other) // A&B
- or(other) // A|B
- xor(other) // A^B
- minus(other) // A&~B
- expandTo(other) // a while loop that repeats grow4() + and(other) until there are no changes
Unary operators
- inverse() // ~A
- increment() // A++, regards the matrix as an 361 bits int. Increment the first (a[0]) integer, if it turns into 19 zero's (7FFFF+1 = 80000), then clear the integer (a[0]=0), and repeat for the next integer, otherwise break the loop.
- decrement() // A--, regards the matrix as an 361 bits int
- twoComplement() //
-A = (~A)++
- first() // this and twoComplement(this),
A&-A
- firstChain() // this.clone().first().expandTo(this)
- withoutFirst()
- randomFree() // while loop trying random position until a free is found.
- down() // shift rows down, filling the top with 0's
- up() // shift row up, filling the bottom with 0,s
- left() // shift columns left, filling the right with 0's
- right() // shift columns right, filling the left with 0īs
- explode4() // up() or down() or left() or right()
- explode8() // First up() or down(), then right() or left(), then or with explode4() for single stones
- grow4() // explode4() or this
- grow8() // explode8() or this
- neighbors4() // explode4() and not(this)
- neighbors8() // explode8() and not(this)
Testing
- is(position)
- equals(other) // compare 2 BinMatrix
- equals(boolean) // tests if all are true or false
- count()
If you have any comments, to the algorithm, I would be pleased to read them: Please make comments here below: