def __init__(self, tc, player_b=None, player_w=None, **kwargs): self.tc = tc kwargs.setdefault('board_size', 9) game_controller = gtp_controller.Game_controller('one', 'two') if player_b is None: player_b = gtp_engine_fixtures.Test_player() if player_w is None: player_w = gtp_engine_fixtures.Test_player() engine_b = gtp_engine_fixtures.make_player_engine(player_b) engine_w = gtp_engine_fixtures.make_player_engine(player_w) channel_b = gtp_controller_test_support.Testing_gtp_channel(engine_b) channel_w = gtp_controller_test_support.Testing_gtp_channel(engine_w) controller_b = gtp_controller.Gtp_controller(channel_b, 'player one') controller_w = gtp_controller.Gtp_controller(channel_w, 'player two') game_controller.set_player_controller('b', controller_b) game_controller.set_player_controller('w', controller_w) game = gtp_games.Gtp_game(game_controller, **kwargs) self.game_controller = game_controller self.game = game self.controller_b = controller_b self.controller_w = controller_w self.channel_b = channel_b self.channel_w = channel_w self.engine_b = channel_b.engine self.engine_w = channel_w.engine self.player_b = channel_b.engine.player self.player_w = channel_w.engine.player
def __init__(self, tc): self.tc = tc self.channel = gtp_engine_fixtures.get_test_channel() self.underlying_engine = self.channel.engine self.controller = gtp_controller.Gtp_controller( self.channel, 'testbackend') self.proxy = gtp_proxy.Gtp_proxy() self.proxy.set_back_end_controller(self.controller) self.engine = self.proxy.engine self.commands_handled = self.underlying_engine.commands_handled
def test_error_from_list_commands(tc): channel = gtp_engine_fixtures.get_test_channel() channel.engine.force_error("list_commands") controller = gtp_controller.Gtp_controller(channel, 'testbackend') proxy = gtp_proxy.Gtp_proxy() with tc.assertRaises(BackEndError) as ar: proxy.set_back_end_controller(controller) tc.assertEqual( str(ar.exception), "failure response from first command " "(list_commands) to testbackend:\n" "handler forced to fail") tc.assertIsInstance(ar.exception.cause, BadGtpResponse) proxy.close()
def main(): try: channel = gtp_controller.Subprocess_gtp_channel( ["gnugo", "--mode=gtp"]) controller = gtp_controller.Gtp_controller(channel, "gnugo") controller.do_command("boardsize", "19") controller.do_command("clear_board") controller.do_command("komi", "6") controller.do_command("play", "B", "D4") print controller.do_command("genmove", "W") print controller.do_command("showboard") controller.close() except (gtp_controller.GtpChannelError, gtp_controller.BadGtpResponse), e: sys.exit(str(e))
def check_player(player_check, discard_stderr=False): """Do a test run of a GTP engine. player_check -- Player_check object This starts an engine subprocess, sends it some GTP commands, and ends the process again. Raises CheckFailed if the player doesn't pass the checks. Returns a list of warning messages. Currently checks: - any explicitly specified cwd exists and is a directory - the engine subprocess starts, and replies to GTP commands - the engine reports protocol version 2 (if it supports protocol_version) - the engine accepts any startup_gtp_commands - the engine accepts the specified board size and komi - the engine accepts the 'clear_board' command - the engine accepts 'quit' and closes down cleanly """ player = player_check.player if player.cwd is not None and not os.path.isdir(player.cwd): raise CheckFailed("bad working directory: %s" % player.cwd) if discard_stderr: stderr = open(os.devnull, "w") else: stderr = None try: env = player.make_environ() env['GOMILL_GAME_ID'] = 'startup-check' try: channel = gtp_controller.Subprocess_gtp_channel(player.cmd_args, env=env, cwd=player.cwd, stderr=stderr) except GtpChannelError, e: raise GtpChannelError("error starting subprocess for %s:\n%s" % (player.code, e)) controller = gtp_controller.Gtp_controller(channel, player.code) controller.set_gtp_aliases(player.gtp_aliases) controller.check_protocol_version() for command, arguments in player.startup_gtp_commands: controller.do_command(command, *arguments) controller.do_command("boardsize", str(player_check.board_size)) controller.do_command("clear_board") controller.do_command("komi", str(player_check.komi)) controller.safe_close()
def test_nontgtp_backend(tc): channel = gtp_controller_test_support.Preprogrammed_gtp_channel( "Usage: randomprogram [options]\n\nOptions:\n" "--help show this help message and exit\n") controller = gtp_controller.Gtp_controller(channel, 'testbackend') proxy = gtp_proxy.Gtp_proxy() with tc.assertRaises(BackEndError) as ar: proxy.set_back_end_controller(controller) tc.assertEqual(str(ar.exception), "GTP protocol error reading response to first command " "(list_commands) from testbackend:\n" "engine isn't speaking GTP: first byte is 'U'") tc.assertIsInstance(ar.exception.cause, GtpProtocolError) proxy.close()
class Game(object): """A single game between two GTP engines. Instantiate with: board_size -- int komi -- float (default 0.0) move_limit -- int (default 1000) The 'commands' values are lists of strings, as for subprocess.Popen. Normal use: game = Game(...) game.set_player_code('b', ...) game.set_player_code('w', ...) game.use_internal_scorer() or game.allow_scorer(...) [optional] game.set_move_callback...() [optional] game.set_player_subprocess('b', ...) or set_player_controller('b', ...) game.set_player_subprocess('w', ...) or set_player_controller('w', ...) game.request_engine_descriptions() [optional] game.ready() game.set_handicap(...) [optional] game.run() game.close_players() game.make_sgf() or game.write_sgf(...) [optional] then retrieve the Game_result and moves. If neither use_internal_scorer() nor allow_scorer() is called, the game won't be scored. Public attributes for reading: players -- map colour -> player code game_id -- string or None result -- Game_result (None before the game is complete) moves -- list of tuples (colour, move, comment) move is a pair (row, col), or None for a pass player_scores -- map player code -> string or None engine_names -- map player code -> string engine_descriptions -- map player code -> string player_scores values are the response to the final_score GTP command (if the player was asked). Methods which communicate with engines may raise BadGtpResponse if the engine returns a failure response. Methods which communicate with engines will normally raise GtpChannelError if there is trouble communicating with the engine. But after the game result has been decided, they will set these errors aside; retrieve them with describe_late_errors(). This enforces a simple ko rule, but no superko rule. It accepts self-capture moves. """ def __init__(self, board_size, komi=0.0, move_limit=1000): self.players = {'b': 'b', 'w': 'w'} self.game_id = None self.controllers = {} self.claim_allowed = {'b': False, 'w': False} self.after_move_callback = None self.board_size = board_size self.komi = komi self.move_limit = move_limit self.allowed_scorers = [] self.internal_scorer = False self.handicap_compensation = "no" self.handicap = 0 self.first_player = "b" self.engine_names = {} self.engine_descriptions = {} self.moves = [] self.player_scores = {'b': None, 'w': None} self.additional_sgf_props = [] self.late_errors = [] self.handicap_stones = None self.result = None self.board = boards.Board(board_size) self.simple_ko_point = None ## Configuration methods (callable before set_player_...) def set_player_code(self, colour, player_code): """Specify a player code. player_code -- short ascii string The player codes are used to identify the players in game results, sgf files, and the error messages. Setting these is optional but strongly encouraged. If not explicitly set, they will just be 'b' and 'w'. Raises ValueError if both players are given the same code. """ s = str(player_code) if self.players[opponent_of(colour)] == s: raise ValueError("player codes must be distinct") self.players[colour] = s def set_game_id(self, game_id): """Specify a game id. game_id -- string The game id is reported in the game result, and used as a default game name in the SGF file. If you don't set it, it will have value None. """ self.game_id = str(game_id) def use_internal_scorer(self, handicap_compensation='no'): """Set the scoring method to internal. The internal scorer uses area score, assuming all stones alive. handicap_compensation -- 'no' (default), 'short', or 'full'. If handicap_compensation is 'full', one point is deducted from Black's score for each handicap stone; if handicap_compensation is 'short', one point is deducted from Black's score for each handicap stone except the first. (The number of handicap stones is taken from the parameter to set_handicap().) """ self.internal_scorer = True if handicap_compensation not in ('no', 'short', 'full'): raise ValueError("bad handicap_compensation value: %s" % handicap_compensation) self.handicap_compensation = handicap_compensation def allow_scorer(self, colour): """Allow the specified player to score the game. If this is called for both colours, both are asked to score. """ self.allowed_scorers.append(colour) def set_claim_allowed(self, colour, b=True): """Allow the specified player to claim a win. This will have no effect if the engine doesn't implement gomill-genmove_ex. """ self.claim_allowed[colour] = bool(b) def set_move_callback(self, fn): """Specify a callback function to be called after every move. This function is called after each move is played, including passes but not resignations, and not moves which triggered a forfeit. It is passed three parameters: colour, move, board move is a pair (row, col), or None for a pass Treat the board parameter as read-only. Exceptions raised from the callback will be propagated unchanged out of run(). """ self.after_move_callback = fn ## Channel methods def set_player_controller(self, colour, controller, check_protocol_version=True): """Specify a player using a Gtp_controller. controller -- Gtp_controller check_protocol_version -- bool (default True) By convention, the channel name should be 'player <player code>'. If check_protocol_version is true, rejects an engine that declares a GTP protocol version <> 2. Propagates GtpChannelError if there's an error checking the protocol version. """ self.controllers[colour] = controller if check_protocol_version: controller.check_protocol_version() def set_player_subprocess(self, colour, command, check_protocol_version=True, **kwargs): """Specify the a player as a subprocess. command -- list of strings (as for subprocess.Popen) check_protocol_version -- bool (default True) Additional keyword arguments are passed to the Subprocess_gtp_channel constructor. If check_protocol_version is true, rejects an engine that declares a GTP protocol version <> 2. Propagates GtpChannelError if there's an error creating the subprocess or checking the protocol version. """ try: channel = gtp_controller.Subprocess_gtp_channel(command, **kwargs) except GtpChannelError, e: raise GtpChannelError( "error starting subprocess for player %s:\n%s" % (self.players[colour], e)) controller = gtp_controller.Gtp_controller( channel, "player %s" % self.players[colour]) self.set_player_controller(colour, controller, check_protocol_version)
def main(): ## usage = "%prog [options] --black='<command>' --white='<command>'" ## parser = OptionParser(usage=usage) ## parser.add_option("--black", help=SUPPRESS_HELP) ## parser.add_option("--white", help=SUPPRESS_HELP) ## parser.add_option("--komi", type="float", default=7.5) ## parser.add_option("--size", type="int", default=19) ## parser.add_option("--games", type="int", default=1, metavar="COUNT") ## parser.add_option("--verbose", type="choice", choices=('0','1','2'), ## default=0, metavar="0|1|2") ## parser.add_option("--sgfbase", type="string", metavar="FILENAME-PREFIX") ## ## (options, args) = parser.parse_args() ## if args: ## parser.error("too many arguments") ## if not options.black or not options.white: ## parser.error("players not specified") ## ## black_command = shlex.split(options.black) ## white_command = shlex.split(options.white) ## b_code = black_command[0] ## w_code = white_command[0] ## if b_code == w_code: ## b_code += '-b' ## w_code += '-w' #these used to be in an options object verbose = '2' size = 13 komi = 5.5 b_code = 'b' w_code = 'w' white_command = 'gnugo\gnugo --mode gtp --silent --color white' #video_source = 'tests/go game.mp4' video_source = 0 game_controller = gtp_controller.Game_controller(b_code, w_code) try: player = video_player.VideoPlayer("camera_params.npz", video_source, size, "Gowatcher", rotate_pic=True, debug=True) engine = video_player.make_engine(player) channel = gtp_controller.Internal_gtp_channel(engine) black_controller = gtp_controller.Gtp_controller(channel, 'b') game_controller.set_player_controller('b', black_controller) game_controller.set_player_subprocess('w', white_command) except GtpChannelError as e: game_controller.close_players() sys.exit("error creating players:\n%s\n" % e) eds = game_controller.engine_descriptions if verbose == '1': print('Black: %s' % eds['b'].get_short_description()) print('White: %s' % eds['w'].get_short_description()) elif verbose == '2': print('Black: %s\n' % eds['b'].get_long_description()) print('White: %s\n' % eds['w'].get_long_description()) game = gtp_games.Gtp_game(game_controller, board_size=size, komi=komi, move_limit=1000) if verbose == '1': game.set_move_callback(print_move) elif verbose == '2': game.set_move_callback(print_board) game.allow_scorer('b') game.allow_scorer('w') try: game.prepare() game.run() except (GtpChannelError, BadGtpResponse) as e: game_controller.close_players() sys.exit("aborting run due to error:\n%s\n" % e) print(game.result.describe()) ## if options.sgfbase is not None: ## sgf_game = game.make_sgf() ## sgf_game.get_root().set("AP", ("Gomill twogtp", __version__)) ## try: ## write_sgf(sgf_game, game_number, options.sgfbase) ## except EnvironmentError, e: ## sys.exit("error writing SGF file: %s" % e) game_controller.close_players() late_error_messages = game_controller.describe_late_errors() if late_error_messages: sys.exit(late_error_messages)
command -- list of strings (as for subprocess.Popen) Additional keyword arguments are passed to the Subprocess_gtp_channel constructor. Raises BackEndError if it can't communicate with the back end. """ try: channel = gtp_controller.Subprocess_gtp_channel(command, **kwargs) except GtpChannelError, e: # Probably means exec failure raise BackEndError("can't launch back end command\n%s" % e, cause=e) controller = gtp_controller.Gtp_controller(channel, "back end") self.set_back_end_controller(controller) def close(self): """Close the channel to the back end. It's safe to call this at any time after set_back_end_... (including after receiving a BackEndError). It's not strictly necessary to call this if you're going to exit from the parent process anyway, as that will naturally close the command channel. But some engines don't behave well if you don't send 'quit', so it's safest to close the proxy explicitly. This will send 'quit' if low-level errors have not previously been seen on the channel, unless expect_back_end_exit() has been called.