pygo/lib/goban.py

239 lines
6.2 KiB
Python
Raw Normal View History

# 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
2012-04-12 20:43:57 +00:00
class SGFTree(Structure):
_fields_ = [
('root', c_void_p), # SGFNode *root;
('lastnode', c_void_p) # SGFNode *lastnode;
]
2012-04-13 19:27:07 +00:00
# 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 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
2012-04-13 19:27:07 +00:00
lib = None
EMPTY = 0
2012-04-13 19:27:07 +00:00
WHITE = 1
BLACK = 2
def __init__(self):
2012-04-13 19:27:07 +00:00
if not Goban.lib:
Goban.lib = CDLL('lib/libboard.so')
2012-04-13 19:27:07 +00:00
# self.ginfo = Gameinfo()
Goban.lib.clear_board()
self.to_move = Goban.BLACK
self.hover = None
def play_move(self, pos, color=None):
"""Make a move."""
2012-04-13 19:27:07 +00:00
# i,j = pos
# if color is None:
# color = self.ginfo.to_move
# return Goban.lib.gameinfo_play_move(self.ginfo, i, j, color)
realpos = _real_pos(pos)
if color is None:
color = self.to_move
2012-04-13 19:27:07 +00:00
if Goban.lib.is_legal(realpos, color):
Goban.lib.play_move(realpos, color)
2012-04-13 05:41:33 +00:00
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."""
2012-04-13 19:27:07 +00:00
return Goban.lib.undo_move(n)
def set_hover(self, pos):
realpos = _real_pos(pos)
2012-04-13 05:48:04 +00:00
if _i(realpos) < 0 or _i(realpos) > 18 or _j(realpos) < 0 or _j(realpos) > 18:
return
if self.hover == realpos:
return
2012-04-13 19:27:07 +00:00
if Goban.lib.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
2012-04-13 19:27:07 +00:00
board = p.in_dll(Goban.lib, 'board')
2012-04-13 19:27:07 +00:00
for i in range(19):
for j in range(19):
pos = _real_pos((i,j))
2012-04-13 19:27:07 +00:00
code = self._get_draw_code(pos, board[pos])
if code == 'e':
continue
2012-04-13 19:27:07 +00:00
s = img_res[code]
s = pygame.transform.scale(s, (inc, inc))
ret.blit(s, (_i(pos)*inc, _j(pos)*inc))
2012-04-13 19:27:07 +00:00
if self.hover == pos:
c = img_res['bH']
# if self.ginfo.to_move == Goban.WHITE:
if self.to_move == Goban.WHITE:
c = img_res['wH']
2012-04-13 19:27:07 +00:00
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):
2012-04-13 19:27:07 +00:00
i,j = pos
return 21 + i * 20 + j
def _i(pos):
return (pos / 20) - 1
def _j(pos):
return (pos % 20) - 1