| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
|
|---|
| 10 |
|
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 |
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 |
|
|---|
| 25 |
|
|---|
| 26 |
from copy import deepcopy |
|---|
| 27 |
|
|---|
| 28 |
class abstractBoard: |
|---|
| 29 |
""" This class administrates a go board. |
|---|
| 30 |
It keeps track of the stones currently on the board in the dictionary self.status, |
|---|
| 31 |
and of the moves played so far in self.undostack |
|---|
| 32 |
|
|---|
| 33 |
The "coordinate system" of the board is as follows: |
|---|
| 34 |
|
|---|
| 35 |
(0,0) ------- (self.boardsize-1,0) |
|---|
| 36 |
| | |
|---|
| 37 |
| | |
|---|
| 38 |
(0,bsize-1) --- (bsize-1, bsize-1) |
|---|
| 39 |
|
|---|
| 40 |
(this is analogous to the SGF coordinate system). |
|---|
| 41 |
|
|---|
| 42 |
It has methods to clear the board, play a stone, undo a move. """ |
|---|
| 43 |
|
|---|
| 44 |
def __init__(self, boardsize = 19): |
|---|
| 45 |
self.status = {} |
|---|
| 46 |
self.undostack = [] |
|---|
| 47 |
self.boardsize = boardsize |
|---|
| 48 |
|
|---|
| 49 |
def getStatus(self, x, y): |
|---|
| 50 |
if self.status.has_key((x, y)): return self.status[(x, y)] |
|---|
| 51 |
else: return ' ' |
|---|
| 52 |
|
|---|
| 53 |
def setStatus(self, x, y, value): |
|---|
| 54 |
self.status[(x, y)] = value |
|---|
| 55 |
|
|---|
| 56 |
|
|---|
| 57 |
def copy(self): |
|---|
| 58 |
ab = abstractBoard(self.boardsize) |
|---|
| 59 |
ab.status = deepcopy(self.status) |
|---|
| 60 |
ab.undostack = deepcopy(self.undostack) |
|---|
| 61 |
return ab |
|---|
| 62 |
|
|---|
| 63 |
def restore(self, ab): |
|---|
| 64 |
self.boardsize = ab.boardsize |
|---|
| 65 |
self.status = ab.status |
|---|
| 66 |
self.undostack = ab.undostack |
|---|
| 67 |
|
|---|
| 68 |
def len_cap_last(self): |
|---|
| 69 |
""" Return number of captured stones in last move. """ |
|---|
| 70 |
|
|---|
| 71 |
return len(self.undostack[-1][2]) |
|---|
| 72 |
|
|---|
| 73 |
def get_cap_last(self): |
|---|
| 74 |
return self.undostack[-1][2] |
|---|
| 75 |
|
|---|
| 76 |
def undostack_pop(self): |
|---|
| 77 |
return self.undostack.pop() |
|---|
| 78 |
|
|---|
| 79 |
def undostack_append_pass(self): |
|---|
| 80 |
self.undostack.append(((20,20), '' ,[])) |
|---|
| 81 |
|
|---|
| 82 |
def len_undostack(self): |
|---|
| 83 |
return len(self.undostack) |
|---|
| 84 |
|
|---|
| 85 |
def neighbors(self,x): |
|---|
| 86 |
""" Returns the coordinates of the 4 (resp. 3 resp. 2 at the side / in the corner) intersections |
|---|
| 87 |
adjacent to the given one. """ |
|---|
| 88 |
if x[0]== 0 : l0 = [1] |
|---|
| 89 |
elif x[0]== self.boardsize-1 : l0 = [self.boardsize-2] |
|---|
| 90 |
else: l0 = [x[0]-1, x[0]+1] |
|---|
| 91 |
|
|---|
| 92 |
if x[1]== 0 : l1 = [1] |
|---|
| 93 |
elif x[1]== self.boardsize-1 : l1 = [self.boardsize-2] |
|---|
| 94 |
else: l1 = [x[1]-1, x[1]+1] |
|---|
| 95 |
|
|---|
| 96 |
l = [] |
|---|
| 97 |
for i in l0: l.append((i,x[1])) |
|---|
| 98 |
for j in l1: l.append((x[0],j)) |
|---|
| 99 |
|
|---|
| 100 |
return l |
|---|
| 101 |
|
|---|
| 102 |
|
|---|
| 103 |
def clear(self): |
|---|
| 104 |
""" Clear the board """ |
|---|
| 105 |
self.status = {} |
|---|
| 106 |
self.undostack=[] |
|---|
| 107 |
|
|---|
| 108 |
|
|---|
| 109 |
|
|---|
| 110 |
|
|---|
| 111 |
def play(self,pos,color): |
|---|
| 112 |
""" This plays a color=black/white stone at pos, if that is a legal move |
|---|
| 113 |
(disregarding ko), and deletes stones captured by that move. |
|---|
| 114 |
It returns 1 if the move has been played, 0 if not. """ |
|---|
| 115 |
|
|---|
| 116 |
if color is None: color = self.currentColor |
|---|
| 117 |
elif color in ['black', 'b']: color = 'B' |
|---|
| 118 |
elif color in ['white', 'w']: color = 'W' |
|---|
| 119 |
|
|---|
| 120 |
if self.getStatus(pos[0], pos[1]) != ' ': |
|---|
| 121 |
return 0 |
|---|
| 122 |
|
|---|
| 123 |
l = self.legal(pos,color) |
|---|
| 124 |
if l: |
|---|
| 125 |
captures = l[1] |
|---|
| 126 |
for x in captures: del self.status[x] |
|---|
| 127 |
self.undostack.append((pos,color,captures)) |
|---|
| 128 |
return 1 |
|---|
| 129 |
else: return 0 |
|---|
| 130 |
|
|---|
| 131 |
|
|---|
| 132 |
def legal(self, pos, color): |
|---|
| 133 |
""" Check if a play by color at pos would be a legal move. """ |
|---|
| 134 |
c = [] |
|---|
| 135 |
for x in self.neighbors(pos): |
|---|
| 136 |
if self.getStatus(x[0],x[1])==self.invert(color): |
|---|
| 137 |
c = c + self.hasNoLibExcP(x, pos) |
|---|
| 138 |
|
|---|
| 139 |
self.status[pos]=color |
|---|
| 140 |
|
|---|
| 141 |
if c: |
|---|
| 142 |
captures = [] |
|---|
| 143 |
for x in c: |
|---|
| 144 |
if not x in captures: captures.append(x) |
|---|
| 145 |
return (1, captures) |
|---|
| 146 |
|
|---|
| 147 |
if self.hasNoLibExcP(pos): |
|---|
| 148 |
del self.status[pos] |
|---|
| 149 |
return () |
|---|
| 150 |
else: return (1, []) |
|---|
| 151 |
|
|---|
| 152 |
|
|---|
| 153 |
def hasNoLibExcP(self, pos, exc = None): |
|---|
| 154 |
""" This function checks if the string (=solidly connected) of stones containing |
|---|
| 155 |
the stone at pos has a liberty (resp. has a liberty besides that at exc). |
|---|
| 156 |
If no liberties are found, a list of all stones in the string is returned. |
|---|
| 157 |
|
|---|
| 158 |
The algorithm is a non-recursive implementation of a simple flood-filling: |
|---|
| 159 |
starting from the stone at pos, the main while-loop looks at the intersections |
|---|
| 160 |
directly adjacent to the stones found so far, for liberties or other stones that belong |
|---|
| 161 |
to the string. Then it looks at the neighbors of those newly found stones, and so |
|---|
| 162 |
on, until it finds a liberty, or until it doesn't find any new stones belonging |
|---|
| 163 |
to the string, which means that there are no liberties. |
|---|
| 164 |
Once a liberty is found, the function returns immediately. """ |
|---|
| 165 |
|
|---|
| 166 |
st = [] |
|---|
| 167 |
|
|---|
| 168 |
newlyFound = [pos] |
|---|
| 169 |
foundNew = 1 |
|---|
| 170 |
|
|---|
| 171 |
while foundNew: |
|---|
| 172 |
foundNew = 0 |
|---|
| 173 |
n = [] |
|---|
| 174 |
for x in newlyFound: |
|---|
| 175 |
for y in self.neighbors(x): |
|---|
| 176 |
if self.getStatus(y[0], y[1]) == ' ' and y != exc: |
|---|
| 177 |
return [] |
|---|
| 178 |
elif self.getStatus(y[0],y[1])==self.getStatus(x[0],x[1]) \ |
|---|
| 179 |
and not y in st and not y in newlyFound: |
|---|
| 180 |
n.append(y) |
|---|
| 181 |
foundNew = 1 |
|---|
| 182 |
|
|---|
| 183 |
st[:0] = newlyFound |
|---|
| 184 |
newlyFound = n |
|---|
| 185 |
|
|---|
| 186 |
return st |
|---|
| 187 |
|
|---|
| 188 |
|
|---|
| 189 |
def undo(self, no=1): |
|---|
| 190 |
""" Undo the last no moves. """ |
|---|
| 191 |
for i in range(no): |
|---|
| 192 |
if self.undostack: |
|---|
| 193 |
pos, color, captures = self.undostack.pop() |
|---|
| 194 |
del self.status[pos] |
|---|
| 195 |
for p in captures: self.status[p] = self.invert(color) |
|---|
| 196 |
|
|---|
| 197 |
|
|---|
| 198 |
def remove(self, pos, removeFromUndostack=0): |
|---|
| 199 |
"""Remove the stone at pos, if not removeFromUndostack, append this as capture to undostack. |
|---|
| 200 |
Otherwise, find the placement of this stone in undostack, and remove it from there.""" |
|---|
| 201 |
|
|---|
| 202 |
if removeFromUndostack: |
|---|
| 203 |
for i in range(len(self.undostack), 0, -1): |
|---|
| 204 |
if pos == self.undostack[i-1][0]: |
|---|
| 205 |
self.undostack[i-1:i] = [] |
|---|
| 206 |
else: |
|---|
| 207 |
self.undostack.append(((-1,-1), self.invert(self.status[pos]), [pos])) |
|---|
| 208 |
del self.status[pos] |
|---|
| 209 |
|
|---|
| 210 |
|
|---|
| 211 |
def invert(self,color): |
|---|
| 212 |
if color == 'B': return 'W' |
|---|
| 213 |
else: return 'B' |
|---|