Ejemplo n.º 1
0
 def handle_undo(self, args):
     if not self.move_history:
         raise GtpError("cannot undo")
     try:
         self.reset_to_moves(self.move_history[:-1])
     except ValueError:
         raise GtpError("corrupt history")
Ejemplo n.º 2
0
 def handle_place_free_handicap(self, args):
     try:
         number_of_stones = gtp_engine.interpret_int(args[0])
     except IndexError:
         gtp_engine.report_bad_arguments()
     max_points = handicap_layout.max_free_handicap_for_board_size(
         self.board_size)
     if not 2 <= number_of_stones <= max_points:
         raise GtpError("invalid number of stones")
     if not self.board.is_empty():
         raise GtpError("board not empty")
     if number_of_stones == max_points:
         number_of_stones = max_points - 1
     moves = self._choose_free_handicap_moves(number_of_stones)
     try:
         try:
             if len(moves) > number_of_stones:
                 raise ValueError
             for row, col in moves:
                 self.board.play(row, col, 'b')
         except (ValueError, TypeError):
             raise GtpError("invalid result from move generator: %s"
                            % format_vertex_list(moves))
     except Exception:
         self.reset()
         raise
     self.simple_ko_point = None
     self.handicap = number_of_stones
     self.set_history_base(self.board.copy())
     return " ".join(format_vertex((row, col))
                     for (row, col) in moves)
Ejemplo n.º 3
0
    def handle_savesgf(self, args):
        try:
            pathname = args[0]
        except IndexError:
            gtp_engine.report_bad_arguments()

        sgf_game = sgf.Sgf_game(self.board_size)
        root = sgf_game.get_root()
        root.set('KM', self.komi)
        root.set('AP', ("gomill", __version__))
        sgf_game.set_date()
        if self.handicap is not None:
            root.set('HA', self.handicap)
        for arg in args[1:]:
            try:
                identifier, value = arg.split("=", 1)
                if not identifier.isalpha():
                    raise ValueError
                identifier = identifier.upper()
                value = value.replace("\\_", " ").replace("\\\\", "\\")
            except Exception:
                gtp_engine.report_bad_arguments()
            root.set_raw(identifier, sgf_grammar.escape_text(value))
        sgf_moves.set_initial_position(sgf_game, self.history_base)
        for history_move in self.move_history:
            node = sgf_game.extend_main_sequence()
            node.set_move(history_move.colour, history_move.move)
            if history_move.comments is not None:
                node.set("C", history_move.comments)
        sgf_moves.indicate_first_player(sgf_game)
        try:
            self._save_file(pathname, sgf_game.serialise())
        except EnvironmentError, e:
            raise GtpError("error writing file: %s" % e)
Ejemplo n.º 4
0
 def handle_play(self, args):
     self.seen_played.append(args[1].upper())
     if self.reject is None:
         return
     vertex, msg = self.reject
     if args[1].lower() == vertex.lower():
         raise GtpError(msg)
Ejemplo n.º 5
0
 def handle_boardsize(self, args):
     try:
         size = gtp_engine.interpret_int(args[0])
     except IndexError:
         gtp_engine.report_bad_arguments()
     if size not in self.acceptable_sizes:
         raise GtpError("unacceptable size")
     self.board_size = size
     self.reset()
Ejemplo n.º 6
0
 def handle_fixed_handicap(self, args):
     try:
         number_of_stones = gtp_engine.interpret_int(args[0])
     except IndexError:
         gtp_engine.report_bad_arguments()
     if not self.board.is_empty():
         raise GtpError("board not empty")
     try:
         points = handicap_layout.handicap_points(number_of_stones,
                                                  self.board_size)
     except ValueError:
         raise GtpError("invalid number of stones")
     for row, col in points:
         self.board.play(row, col, 'b')
     self.simple_ko_point = None
     self.handicap = number_of_stones
     self.set_history_base(self.board.copy())
     return " ".join(format_vertex((row, col)) for (row, col) in points)
Ejemplo n.º 7
0
 def handle_genmove(self, args):
     colour = gtp_engine.interpret_colour(args[0])
     for move_colour, vertex in self.iter:
         if move_colour == colour:
             if callable(vertex):
                 vertex = vertex()
             if vertex == 'fail':
                 raise GtpError("forced to fail")
             if vertex.endswith('&exit'):
                 raise GtpQuit(vertex[:-5])
             return vertex
     return "pass"
Ejemplo n.º 8
0
 def handle_set_free_handicap(self, args):
     max_points = handicap_layout.max_free_handicap_for_board_size(
         self.board_size)
     if not 2 <= len(args) <= max_points:
         raise GtpError("invalid number of stones")
     if not self.board.is_empty():
         raise GtpError("board not empty")
     try:
         for vertex_s in args:
             move = gtp_engine.interpret_vertex(vertex_s, self.board_size)
             if move is None:
                 raise GtpError("'pass' not permitted")
             row, col = move
             try:
                 self.board.play(row, col, 'b')
             except ValueError:
                 raise GtpError("engine error: %s is occupied" % vertex_s)
     except Exception:
         self.reset()
         raise
     self.set_history_base(self.board.copy())
     self.handicap = len(args)
     self.simple_ko_point = None
Ejemplo n.º 9
0
 def handle_loadsgf(self, args):
     try:
         pathname = args[0]
     except IndexError:
         gtp_engine.report_bad_arguments()
     if len(args) > 1:
         move_number = gtp_engine.interpret_int(args[1])
     else:
         move_number = None
     # The GTP spec mandates the "cannot load file" error message, so we
     # can't be more helpful.
     try:
         s = self._load_file(pathname)
     except EnvironmentError:
         raise GtpError("cannot load file")
     try:
         sgf_game = sgf.Sgf_game.from_string(s)
     except ValueError:
         raise GtpError("cannot load file")
     new_size = sgf_game.get_size()
     if new_size not in self.acceptable_sizes:
         raise GtpError("unacceptable size")
     self.board_size = new_size
     try:
         komi = sgf_game.get_komi()
     except ValueError:
         raise GtpError("bad komi")
     try:
         handicap = sgf_game.get_handicap()
     except ValueError:
         # Handicap isn't important, so soldier on
         handicap = None
     try:
         sgf_board, plays = sgf_moves.get_setup_and_moves(sgf_game)
     except ValueError, e:
         raise GtpError(str(e))
Ejemplo n.º 10
0
    def handle_command(self, command, args):
        """Run a command on the back end, from inside a GTP handler.

        This is a variant of pass_command, intended to be used directly in a
        command handler.

        Failure responses from the back end are reported by raising GtpError.

        Low-level (ie, transport or protocol) errors are reported by raising
        GtpFatalError.

        """
        try:
            return self.pass_command(command, args)
        except BadGtpResponse, e:
            raise GtpError(e.gtp_error_message)
Ejemplo n.º 11
0
 def handle_play(self, args):
     try:
         colour_s, vertex_s = args[:2]
     except ValueError:
         gtp_engine.report_bad_arguments()
     colour = gtp_engine.interpret_colour(colour_s)
     move = gtp_engine.interpret_vertex(vertex_s, self.board_size)
     if move is None:
         self.simple_ko_point = None
         self.move_history.append(History_move(colour, None))
         return
     row, col = move
     try:
         self.simple_ko_point = self.board.play(row, col, colour)
         self.simple_ko_player = opponent_of(colour)
     except ValueError:
         raise GtpError("illegal move")
     self.move_history.append(History_move(colour, move))
Ejemplo n.º 12
0
 def _handle_genmove(self, args, for_regression=False, allow_claim=False):
     """Common implementation for genmove commands."""
     try:
         colour = gtp_engine.interpret_colour(args[0])
     except IndexError:
         gtp_engine.report_bad_arguments()
     game_state = Game_state()
     game_state.size = self.board_size
     game_state.board = self.board
     game_state.history_base = self.history_base
     game_state.move_history = self.move_history
     game_state.komi = self.komi
     game_state.for_regression = for_regression
     if self.simple_ko_point is not None and self.simple_ko_player == colour:
         game_state.ko_point = self.simple_ko_point
     else:
         game_state.ko_point = None
     game_state.handicap = self.handicap
     game_state.time_settings = self.time_settings
     game_state.time_remaining, game_state.canadian_stones_remaining = \
         self.time_status[colour]
     generated = self.move_generator(game_state, colour)
     if allow_claim and generated.claim:
         return 'claim'
     if generated.resign:
         return 'resign'
     if generated.pass_move:
         if not for_regression:
             self.move_history.append(
                 History_move(colour, None, generated.comments,
                              generated.cookie))
         return 'pass'
     row, col = generated.move
     vertex = format_vertex((row, col))
     if not for_regression:
         try:
             self.simple_ko_point = self.board.play(row, col, colour)
             self.simple_ko_player = opponent_of(colour)
         except ValueError:
             raise GtpError("engine error: tried to play %s" % vertex)
         self.move_history.append(
             History_move(colour, generated.move, generated.comments,
                          generated.cookie))
     return vertex
Ejemplo n.º 13
0
class Gtp_state(object):
    """Manage the stateful part of the GTP engine protocol.

    This supports implementing a GTP engine using a stateless move generator.

    Sample use:
      gtp_state = Gtp_state(...)
      engine = Gtp_engine_protocol()
      engine.add_commands(gtp_state.get_handlers())

    A Gtp_state maintains the following state:
      board configuration
      move history
      komi
      simple ko ban


    Instantiate with a _move generator function_ and a list of acceptable board
    sizes (default 19 only).

    The move generator function is called to handle genmove. It is passed
    arguments (game_state, colour to play). It should return a
    Move_generator_result. It must not modify data passed in the game_state.

    If the move generator returns an occupied point, Gtp_state will report a GTP
    error. Gtp_state does not enforce any ko rule. It permits self-captures.

    """

    def __init__(self, move_generator, acceptable_sizes=None):
        self.komi = 0.0
        self.time_settings = None
        self.time_status = {
            'b' : (None, None),
            'w' : (None, None),
            }
        self.move_generator = move_generator
        if acceptable_sizes is None:
            self.acceptable_sizes = set((19,))
            self.board_size = 19
        else:
            self.acceptable_sizes = set(acceptable_sizes)
            self.board_size = min(self.acceptable_sizes)
        self.reset()

    def reset(self):
        self.board = boards.Board(self.board_size)
        # None, or a small integer
        self.handicap = None
        self.simple_ko_point = None
        # Player that any simple_ko_point is banned for
        self.simple_ko_player = None
        self.history_base = boards.Board(self.board_size)
        # list of History_move objects
        self.move_history = []

    def set_history_base(self, board):
        """Change the history base to a new position.

        Takes ownership of 'board'.

        Clears the move history.

        """
        self.history_base = board
        self.move_history = []

    def reset_to_moves(self, history_moves):
        """Reset to history base and play the specified moves.

        history_moves -- list of History_move objects.

        'history_moves' becomes the new move history. Takes ownership of
        'history_moves'.

        Raises ValueError if there is an invalid move in the list.

        """
        self.board = self.history_base.copy()
        simple_ko_point = None
        simple_ko_player = None
        for history_move in history_moves:
            if history_move.is_pass():
                self.simple_ko_point = None
                continue
            row, col = history_move.move
            # Propagates ValueError if the move is bad
            simple_ko_point = self.board.play(row, col, history_move.colour)
            simple_ko_player = opponent_of(history_move.colour)
        self.simple_ko_point = simple_ko_point
        self.simple_ko_player = simple_ko_player
        self.move_history = history_moves

    def set_komi(self, f):
        max_komi = 625.0
        if f < -max_komi:
            f = -max_komi
        elif f > max_komi:
            f = max_komi
        self.komi = f

    def handle_boardsize(self, args):
        try:
            size = gtp_engine.interpret_int(args[0])
        except IndexError:
            gtp_engine.report_bad_arguments()
        if size not in self.acceptable_sizes:
            raise GtpError("unacceptable size")
        self.board_size = size
        self.reset()

    def handle_clear_board(self, args):
        self.reset()

    def handle_komi(self, args):
        try:
            f = gtp_engine.interpret_float(args[0])
        except IndexError:
            gtp_engine.report_bad_arguments()
        self.set_komi(f)

    def handle_fixed_handicap(self, args):
        try:
            number_of_stones = gtp_engine.interpret_int(args[0])
        except IndexError:
            gtp_engine.report_bad_arguments()
        if not self.board.is_empty():
            raise GtpError("board not empty")
        try:
            points = handicap_layout.handicap_points(
                number_of_stones, self.board_size)
        except ValueError:
            raise GtpError("invalid number of stones")
        for row, col in points:
            self.board.play(row, col, 'b')
        self.simple_ko_point = None
        self.handicap = number_of_stones
        self.set_history_base(self.board.copy())
        return " ".join(format_vertex((row, col))
                        for (row, col) in points)

    def handle_set_free_handicap(self, args):
        max_points = handicap_layout.max_free_handicap_for_board_size(
            self.board_size)
        if not 2 <= len(args) <= max_points:
            raise GtpError("invalid number of stones")
        if not self.board.is_empty():
            raise GtpError("board not empty")
        try:
            for vertex_s in args:
                move = gtp_engine.interpret_vertex(vertex_s, self.board_size)
                if move is None:
                    raise GtpError("'pass' not permitted")
                row, col = move
                try:
                    self.board.play(row, col, 'b')
                except ValueError:
                    raise GtpError("engine error: %s is occupied" % vertex_s)
        except Exception:
            self.reset()
            raise
        self.set_history_base(self.board.copy())
        self.handicap = len(args)
        self.simple_ko_point = None

    def _choose_free_handicap_moves(self, number_of_stones):
        i = min(number_of_stones,
                handicap_layout.max_fixed_handicap_for_board_size(
                    self.board_size))
        return handicap_layout.handicap_points(i, self.board_size)

    def handle_place_free_handicap(self, args):
        try:
            number_of_stones = gtp_engine.interpret_int(args[0])
        except IndexError:
            gtp_engine.report_bad_arguments()
        max_points = handicap_layout.max_free_handicap_for_board_size(
            self.board_size)
        if not 2 <= number_of_stones <= max_points:
            raise GtpError("invalid number of stones")
        if not self.board.is_empty():
            raise GtpError("board not empty")
        if number_of_stones == max_points:
            number_of_stones = max_points - 1
        moves = self._choose_free_handicap_moves(number_of_stones)
        try:
            try:
                if len(moves) > number_of_stones:
                    raise ValueError
                for row, col in moves:
                    self.board.play(row, col, 'b')
            except (ValueError, TypeError):
                raise GtpError("invalid result from move generator: %s"
                               % format_vertex_list(moves))
        except Exception:
            self.reset()
            raise
        self.simple_ko_point = None
        self.handicap = number_of_stones
        self.set_history_base(self.board.copy())
        return " ".join(format_vertex((row, col))
                        for (row, col) in moves)

    def handle_play(self, args):
        try:
            colour_s, vertex_s = args[:2]
        except ValueError:
            gtp_engine.report_bad_arguments()
        colour = gtp_engine.interpret_colour(colour_s)
        move = gtp_engine.interpret_vertex(vertex_s, self.board_size)
        if move is None:
            self.simple_ko_point = None
            self.move_history.append(History_move(colour, None))
            return
        row, col = move
        try:
            self.simple_ko_point = self.board.play(row, col, colour)
            self.simple_ko_player = opponent_of(colour)
        except ValueError:
            raise GtpError("illegal move")
        self.move_history.append(History_move(colour, move))

    def handle_showboard(self, args):
        return "\n%s\n" % ascii_boards.render_board(self.board)

    def _handle_genmove(self, args, for_regression=False, allow_claim=False):
        """Common implementation for genmove commands."""
        try:
            colour = gtp_engine.interpret_colour(args[0])
        except IndexError:
            gtp_engine.report_bad_arguments()
        game_state = Game_state()
        game_state.size = self.board_size
        game_state.board = self.board
        game_state.history_base = self.history_base
        game_state.move_history = self.move_history
        game_state.komi = self.komi
        game_state.for_regression = for_regression
        if self.simple_ko_point is not None and self.simple_ko_player == colour:
            game_state.ko_point = self.simple_ko_point
        else:
            game_state.ko_point = None
        game_state.handicap = self.handicap
        game_state.time_settings = self.time_settings
        game_state.time_remaining, game_state.canadian_stones_remaining = \
            self.time_status[colour]
        generated = self.move_generator(game_state, colour)
        if allow_claim and generated.claim:
            return 'claim'
        if generated.resign:
            return 'resign'
        if generated.pass_move:
            if not for_regression:
                self.move_history.append(History_move(
                    colour, None, generated.comments, generated.cookie))
            return 'pass'
        row, col = generated.move
        vertex = format_vertex((row, col))
        if not for_regression:
            try:
                self.simple_ko_point = self.board.play(row, col, colour)
                self.simple_ko_player = opponent_of(colour)
            except ValueError:
                raise GtpError("engine error: tried to play %s" % vertex)
            self.move_history.append(
                History_move(colour, generated.move,
                             generated.comments, generated.cookie))
        return vertex

    def handle_genmove(self, args):
        return self._handle_genmove(args)

    def handle_genmove_ex(self, args):
        if not args:
            return "claim"
        allow_claim = False
        for arg in args[1:]:
            if arg == 'claim':
                allow_claim = True
        return self._handle_genmove(args[:1], allow_claim=allow_claim)

    def handle_reg_genmove(self, args):
        return self._handle_genmove(args, for_regression=True)

    def handle_undo(self, args):
        if not self.move_history:
            raise GtpError("cannot undo")
        try:
            self.reset_to_moves(self.move_history[:-1])
        except ValueError:
            raise GtpError("corrupt history")

    def _load_file(self, pathname):
        """Read the specified file and return its contents as a string.

        Subclasses can override this to change how loadsgf interprets filenames.

        May raise EnvironmentError.

        """
        with open(pathname) as f:
            return f.read()

    def handle_loadsgf(self, args):
        try:
            pathname = args[0]
        except IndexError:
            gtp_engine.report_bad_arguments()
        if len(args) > 1:
            move_number = gtp_engine.interpret_int(args[1])
        else:
            move_number = None
        # The GTP spec mandates the "cannot load file" error message, so we
        # can't be more helpful.
        try:
            s = self._load_file(pathname)
        except EnvironmentError:
            raise GtpError("cannot load file")
        try:
            sgf_game = sgf.Sgf_game.from_string(s)
        except ValueError:
            raise GtpError("cannot load file")
        new_size = sgf_game.get_size()
        if new_size not in self.acceptable_sizes:
            raise GtpError("unacceptable size")
        self.board_size = new_size
        try:
            komi = sgf_game.get_komi()
        except ValueError:
            raise GtpError("bad komi")
        try:
            handicap = sgf_game.get_handicap()
        except ValueError:
            # Handicap isn't important, so soldier on
            handicap = None
        try:
            sgf_board, plays = sgf_moves.get_setup_and_moves(sgf_game)
        except ValueError, e:
            raise GtpError(str(e))
        history_moves = [History_move(colour, move)
                         for (colour, move) in plays]
        if move_number is None:
            new_move_history = history_moves
        else:
            # gtp spec says we want the "position before move_number"
            move_number = max(0, move_number-1)
            new_move_history = history_moves[:move_number]
        old_history_base = self.history_base
        old_move_history = self.move_history
        try:
            self.set_history_base(sgf_board)
            self.reset_to_moves(new_move_history)
        except ValueError:
            try:
                self.set_history_base(old_history_base)
                self.reset_to_moves(old_move_history)
            except ValueError:
                raise GtpError("bad move in file and corrupt history")
            raise GtpError("bad move in file")
        self.set_komi(komi)
        self.handicap = handicap
Ejemplo n.º 14
0
 def handle_error(args):
     raise GtpError("normal error")
Ejemplo n.º 15
0
 def _forced_error(self, args):
     raise GtpError("handler forced to fail")
Ejemplo n.º 16
0
 def handle_fail(self, args):
     raise GtpError("test player forced to fail")