# A Python interface to libboard from gnugo # Also has functions for printing the board via pygame, # and for controlling that printing to some degree. # # I'm only implementing what I need for my own purposes. # Further API is welcome! from ctypes import * try: import pygame have_pygame = True except ImportError: print 'Warning: pygame is not installed. Goban.draw_board() and Goban.draw_info() will fail.' have_pygame = False BOARDSIZE=361 MAX_MOVE_HISTORY=361 class board_state(Structure): _fields_ = [ ('board_size', c_int), ('board', c_char * BOARDSIZE), ('board_ko_pos', c_int), ('black_captured', c_int), ('white_captured', c_int), ('initial_board', c_char * BOARDSIZE), ('initial_board_ko_pos', c_int), ('initial_white_captured', c_int), ('initial_black_captured', c_int), ('move_history_color', c_int * MAX_MOVE_HISTORY), ('move_history_pos', c_int * MAX_MOVE_HISTORY), ('move_history_pointer', c_int), ('komi', c_float), ('move_number', c_int) ] class SGFTree(Structure): _fields_ = [ ('root', c_void_p), # SGFNode *root; ('lastnode', c_void_p) # SGFNode *lastnode; ] # class Gameinfo(Structure): # _fields_ = [ # ('handicap', c_int), # ('to_move', c_int), # ('game_record', SGFTree), # ('computer_player', c_int), # ('outfilename', c_char * 128), # ('outfile', c_void_p) # ] class Goban: # This is our dynamic interface to the gnugo libraries libboard = None BLACK = 2 WHITE = 1 EMPTY = 0 def __init__(self): if not Goban.libboard: Goban.libboard = CDLL('lib/libboard.so') Goban.libboard.clear_board() self.to_move = Goban.BLACK self.hover = None def play_move(self, pos, color=None): """Make a move.""" realpos = _real_pos(pos) if color is None: color = self.to_move if Goban.libboard.is_legal(realpos, color): Goban.libboard.play_move(realpos, color) self.to_move = self.OTHER_COLOR(self.to_move) return True else: return False def undo_move(self, n): """Undo n moves. Return True on success, False on failure. On failure, no moves are removed.""" return Goban.libboard.undo_move(n) def set_hover(self, pos): realpos = _real_pos(pos) if self.hover == realpos: return if Goban.libboard.is_legal(realpos): self.hover = realpos else: self.clear_hover() def clear_hover(self): self.hover = None def OTHER_COLOR(self, color): if color == Goban.WHITE: return Goban.BLACK elif color == Goban.BLACK: return Goban.WHITE else: return Goban.EMPTY def draw_board(self, size, img_res): """ Return a pygame.Surface() with an image of the board. If pygame isn't available, this function prints an error and returns harmlessly.""" if not have_pygame: return ret = pygame.Surface((size,size)) inc = size / 19 p = c_int * 421 board = p.in_dll(Goban.libboard, 'board') for pos in range(421): if _i(pos) < 0 or _j(pos) < 0: continue code = self._get_draw_code(pos, board[pos]) if code == 'e': continue s = img_res[code] s = pygame.transform.scale(s, (inc, inc)) ret.blit(s, (_i(pos)*inc, _j(pos)*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, (_i(pos)*inc, _j(pos)*inc)) return ret.convert_alpha() def draw_info(self): """ Return a pygame.Surface() with an image of text describing the game state. If pygame isn't available, this function prints an error and returns harmlessly.""" if not have_pygame: return textbox = pygame.Surface((150, 300)) textbox = textbox.convert() textbox.fill((250, 250, 250)) font = pygame.font.Font(None, 24) # heading = font.render('Captures', 1, (10, 10, 10)) # black_cap = font.render('Black: {}'.format(self.captures[0]), 1, (10, 10, 10)) # white_cap = font.render('White: {}'.format(self.captures[1]), 1, (10, 10, 10)) # turn = font.render('Turn: {}'.format(['Black', 'White'][self.turn]), 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)) return textbox def _get_draw_code(self, pos, board_value): if board_value == Goban.BLACK: return 'b' if board_value == Goban.WHITE: return 'w' if _i(pos) == 0 and _j(pos) == 0: return 'ul' if _i(pos) == 18 and _j(pos) == 0: return 'ur' if _i(pos) == 0 and _j(pos) == 18: return 'dl' if _i(pos) == 18 and _j(pos) == 18: return 'dr' if (_i(pos), _j(pos)) in [(3,3), (3,9), (3,15), (9,3), (9,9), (9,15), (15,3), (15,9), (15,15)]: return 'h' if _i(pos) == 0: return 'l' if _i(pos) == 18: return 'r' if _j(pos) == 0: return 'u' if _j(pos) == 18: return 'd' else: return 'm' # This is equivalent to gnugo's POS macro def _real_pos(pos): x,y = pos return 21 + x * 20 + y def _i(pos): return (pos / 20) - 1 def _j(pos): return (pos % 20) - 1