class Goban: """Represents the go board. Handles stone placement, captures, etc""" # enum values for the board array EMPTY=0 WHITE=1 BLACK=2 # GRAY_WHITE=3 # GRAY_BLACK=4 def __init__(self, board_size=19): # Build the board intersections self.board_size = board_size num_points = board_size * board_size self.board = [Goban.EMPTY] * num_points self.def_draw_codes = self._make_default_draw_codes() self.to_move = Goban.BLACK self.black_captures = 0 self.white_captures = 0 self.last_move = None self.passed_last = False self.ko = None self.hover = None self.elapsed_time = 0 self.winner = Goban.EMPTY def set_hover(self, pos): rpos = self._real_pos(pos) if rpos == self.hover: return if not self._valid_move(rpos) or self.to_move == Goban.EMPTY: self.clear_hover() return self.hover = rpos def reset(self): """Reset the board to a pre-game state""" # Clear the board by setting it to the same size it currently is at self.set_board_size(self.board_size) self.to_move = Goban.BLACK self.black_captures = 0 self.white_captures = 0 self.last_move = None self.passed_last = False self.ko = None self.hover = None self.elapsed_time = 0 self.winner = Goban.EMPTY def set_board_size(self, new_size): """Set the board to a new size. This will also reset the board to a blank state, but will *not* reset captures, etc (call reset() first if you want that)""" self.board_size = new_size num_points = board_size * board_size self.board = [Goban.EMPTY] * num_points def clear_hover(self): self.hover = None def play_move(self, pos, color=None): if color is None: color = self.to_move if self.to_move == Goban.EMPTY: return rpos = self._real_pos(pos) if not self._valid_move(rpos, color): return self.board[rpos] = color self._capture(rpos) self.last_move = rpos self.passed_last = False self.to_move = self._other_color(color) self.clear_hover() # fixme: need to handle post-game stuff here... scoring code def pass_move(self, color=None): if color is None: color = self.to_move if self.passed_last: self.to_move = Goban.EMPTY else: self.to_move = self._other_color(color) self.passed_last = True self.last_move = None self.ko = None def resign(self, color=None): if color is None: color = self.to_move self.passed_last = False self.last_move = None self.ko = None self.winner = self._other_color(color) self.to_move = Goban.EMPTY def _capture(self, pos, color=None): """Look for stones captured on the 4 sides of pos, remove them and increment capture counter. This pos must be a *real* position value, not an x,y tuple.""" if color is None: color = self.to_move # If we get here, we've definitely played a move, # clearing any existing ko point self.ko = None # Who are we capturing who = self._other_color(color) captures = 0 for p in self._neighbors(pos): if not self._on_board(p): continue if not self._num_liberties(p, who): captures += self._delete_group(p) if color == Goban.BLACK: self.black_captures += captures elif color == Goban.WHITE: self.white_captures += captures # Check for ko if captures == 1 and self._num_liberties(pos, color): # find the empty point for p in self._neighbors(pos): if self.board[p] == Goban.EMPTY: self.ko = p break def _valid_move(self, pos, color=None): if not self._on_board(pos): return False if color is None: color = self.to_move # Can't play atop another stone or on the ko point if self.board[pos] != Goban.EMPTY or pos == self.ko: return False # Temporarily place the stone self.board[pos] = color liberties = self._num_liberties(pos, color) opponent = self._other_color(color) kills_group = False for d in self._neighbors(pos): if not self._on_board(d): continue if self._num_liberties(d, opponent) == 0: kills_group = True break # Remove temporary stone self.board[pos] = Goban.EMPTY return liberties > 0 or kills_group # Recursively find whether there are liberties for the group # at pos. def _num_liberties(self, pos, who): if not self._on_board(pos) or self.board[pos] != who: return -1 bs = self.board_size * self.board_size checked = [False] * bs return self._num_liberties_r(pos, who, checked) def _num_liberties_r(self, pos, who, checked=None): if checked[pos]: return 0 else: checked[pos] = True square = self.board[pos] if square == Goban.EMPTY: return 1 elif square != who: return 0 else: liberties = 0 for d in self._neighbors(pos): liberties += self._num_liberties_r(d, who, checked) return liberties # We don't need to worry about crossing ourselves with the # recursion here, because we've already deleted the stone. def _delete_group(self, pos): if not self._on_board(pos): return who = self.board[pos] if who == Goban.EMPTY: return 0 return self._delete_group_r(pos, who) if who == Goban.EMPTY: return 0 self.board[x][y].state = Goban.EMPTY def _delete_group_r(self, pos, who): if not self._on_board(pos): return if self.board[pos] != who: return 0 self.board[pos] = Goban.EMPTY num_deleted = 1 num_deleted += self._delete_group_r(pos + 1, who) num_deleted += self._delete_group_r(pos - 1, who) num_deleted += self._delete_group_r(pos + self.board_size, who) num_deleted += self._delete_group_r(pos - self.board_size, who) return num_deleted # def draw_board(self, size, img_res): # ret = pygame.Surface((size,size)) # inc = size / self.board_size; # for pos in range(len(self.board)): # point = self.board[pos] # if point == Goban.EMPTY: # code = self.def_draw_codes[pos] # elif point == Goban.BLACK: # code = 'b' # elif point == Goban.WHITE: # code = 'w' # if pos == self.last_move: # code = code + 'T' # if pos == self.ko: # code = code + 'C' # s = img_res[code] # s = pygame.transform.scale(s, (inc, inc)) # ret.blit(s, ((pos % self.board_size) *inc, (pos / self.board_size) *inc)) # if self.hover == pos: # c = img_res['bH'] # if self.to_move == Goban.WHITE: # c = img_res['wH'] # c = pygame.transform.scale(c, (inc, inc)) # ret.blit(c, ((pos % self.board_size) *inc, (pos / self.board_size) *inc)) # return ret.convert_alpha() # def draw_info(self): # textbox = pygame.Surface((150, 300)) # textbox = textbox.convert() # textbox.fill((250, 250, 250)) # font = pygame.font.Font(None, 24) # # time = font.render('Time: {:02d}:{:02d}'.format(self.elapsed_time / 60, self.elapsed_time % 60), 1, (10, 10, 10)) # heading = font.render('Captures', 1, (10, 10, 10)) # black_cap = font.render('Black: {}'.format(self.black_captures), 1, (10, 10, 10)) # white_cap = font.render('White: {}'.format(self.white_captures), 1, (10, 10, 10)) # if self.to_move == Goban.BLACK: # turn = font.render('To move: Black', 1, (10, 10, 10)) # elif self.to_move == Goban.WHITE: # turn = font.render('To move: White', 1, (10, 10, 10)) # else: # if self.winner == Goban.WHITE: # turn = font.render('Winner: White', 1, (10, 10, 10)) # elif self.winner == Goban.BLACK: # turn = font.render('Winner: Black', 1, (10, 10, 10)) # else: # turn = font.render('Scoring', 1, (10, 10, 10)) # textbox.blit(heading, (0, 0)) # textbox.blit(black_cap, (0, 28)) # textbox.blit(white_cap, (0, 56)) # textbox.blit(turn, (0, 100)) # # textbox.blit(time, (0, 150)) # return textbox def _make_default_draw_codes(self): ret = [] for pos in range(len(self.board)): if pos == 0: ret.append('ul') elif pos == self.board_size - 1: ret.append('ur') elif pos == self.board_size * self.board_size - self.board_size: ret.append('dl') elif pos == self.board_size * self.board_size - 1: ret.append('dr') elif pos in [60, 66, 72, 174, 180, 186, 288, 294, 300]: ret.append('h') elif pos < self.board_size - 1: ret.append('u') elif pos % self.board_size == 0: ret.append('l') elif pos > (self.board_size * self.board_size - self.board_size - 1): ret.append('d') elif pos % self.board_size == 18: ret.append('r') else: ret.append('m') return ret def _real_pos(self, pos): x,y = pos return x * self.board_size + y def _on_board(self, pos): return pos >= 0 or pos < self.board_size * self.board_size def _other_color(self, color): if color == Goban.BLACK: return Goban.WHITE elif color == Goban.WHITE: return Goban.BLACK def _neighbors(self, pos): neighbors = [] if pos >= self.board_size: neighbors.append(pos - self.board_size) if pos <= self.board_size * self.board_size - self.board_size - 1: neighbors.append(pos + self.board_size) if pos % self.board_size != 0: neighbors.append(pos - 1) if (pos + 1) % self.board_size != 0: neighbors.append(pos + 1) return neighbors