Ejemplo n.º 1
0
    def get_move(self, board: Board, player_id: int, rules: Rules) -> int:
        # find out which moves are valid
        possible_moves = []
        for x in range(board.width):
            if rules.check_move(board.get_board(), board.height, x):
                possible_moves += [x]

        # this simple bot checks if it can win by playing a certain column first
        for x in possible_moves:
            board.do_move(x, player_id)
            if rules.check_win(board.get_board(), board.width, board.height,
                               board.get_last_move(), player_id):
                # even though a winning move has been detected, undo it - "game" will handle further process
                board.undo_move()
                return x
            # if bot cannot win with this move, undo it
            board.undo_move()

        # if bot cannot win in this round, it checks if opponent can win and blocks his move
        opponent_player_id = 1 if player_id == 2 else 2
        for x in possible_moves:
            board.do_move(x, opponent_player_id)
            if rules.check_win(board.get_board(), board.width, board.height,
                               board.get_last_move(), opponent_player_id):
                # make sure to undo the latest move - "game" will handle further process
                board.undo_move()
                return x
            # if bot cannot prevent a win by the opponent with this move, undo it
            board.undo_move()
        # if no victory or defeat can be detected with this very limited search depth, play randomly
        return choice(possible_moves)
Ejemplo n.º 2
0
class Gomoku:
    def __init__(self, size, connection_to_win, players):
        self.size = size
        self.win = connection_to_win
        self.clear(players)
        self.rules = Rules(size, self.win)

    def clear(self, players):
        self.fst_board = np.zeros((self.size, self.size), dtype=np.bool_)
        self.snd_board = np.zeros((self.size, self.size), dtype=np.bool_)
        self.fst_str, self.snd_str = players
        self.moves = 0

    def occupied(self, position):
        x, y = position
        return self.fst_board[x, y] or self.snd_board[x, y]

    def move(self, position):
        x, y = position
        assert not self.fst_board[x, y] and not self.snd_board[x, y]
        self.moves += 1
        self.fst_board[x, y] = True
        win, connections = self.rules.check_win(position, self.fst_board)
        if win:
                return Result.WIN, connections
        if self.moves == self.size ** 2:
            return Result.TIE, []
        self.fst_board, self.snd_board = self.snd_board, self.fst_board
        self.fst_str, self.snd_str = self.snd_str, self.fst_str

        return Result.CONTINUE, None

    def print(self):
        print("  ", end="")
        for x in range(1, self.size + 1):
            print("{:2}".format(chr(ord('A') + x - 1)), end="")
        print()
        for x in range(1, self.size + 1):
            print("{:2}".format(chr(ord('a') + x - 1)), end="")
            for y in range(1, self.size + 1):
                if self.fst_board[x - 1, y - 1]:
                    c = self.fst_str
                elif self.snd_board[x - 1, y - 1]:
                    c = self.snd_str
                else:
                    c = ' '
                print("{:2}".format(c), end='')
            print()
        print()
Ejemplo n.º 3
0
class Game:
    def __init__(self) -> None:
        # create instances of needed classes
        self.__board = Board()
        self.__gui = GUI()
        self.__rules = Rules()
        # player objects will be set in "initialize_match"
        self.__player_1 = None
        self.__player_2 = None
        # players will have these well distinguishable symbols by default
        self.__player_1_default_symbol = 'x'
        self.__player_2_default_symbol = 'o'
        # "id_to_symbol" translates field states from database value to output symbol; player symbols will be set later
        self.__id_to_symbol = {0: ' '}
        # start game session
        self.__play()

    def __welcome(self) -> None:
        self.__gui.text_blue("Ready to play CONNECT FOUR?\nHere we go!")
        # wait for user to press any key
        input()
        self.__gui.clear_display()
        input_change_board = input(
            "Press 'B' to change default board dimensions or hit ENTER to play."
        ).lower()
        self.__gui.clear_display()
        if input_change_board == 'b':
            self.__change_board_dimensions()

    def __human_player_wanted(self, player_number: int) -> bool:
        # inform user about options
        self.__gui.text("Player {} shall be:".format(player_number))
        self.__gui.text("-> human (press 'H')")
        self.__gui.text("-> a bot (press 'B')")
        # ask for input until valid decision has been made
        while True:
            # let user choose type of player (human or bot) by pressing the corresponding key
            player_type = input().lower()
            self.__gui.clear_display()
            if player_type == 'h':
                return True
            elif player_type == 'b':
                return False
            self.__gui.text_red("ERROR: Press 'H' or 'B'.")

    def __initialize_session(self) -> None:
        # create either objects of "Player" or "Bot" for both players - this is decided in "human_player_wanted"
        # initialization of player names and symbols for representation on board is done in "Player" or "Bot"
        if self.__human_player_wanted(1):
            self.__player_1 = Player(1, self.__player_1_default_symbol,
                                     self.__gui)
        else:
            self.__player_1 = Bot(1, self.__player_1_default_symbol)

        if self.__human_player_wanted(2):
            self.__player_2 = Player(2, self.__player_2_default_symbol,
                                     self.__gui)
        else:
            self.__player_2 = Bot(2, self.__player_2_default_symbol)

        # ensure that players have unique names and symbols
        if self.__player_1.name == self.__player_2.name or self.__player_1.symbol == self.__player_2.symbol:
            if type(self.__player_1) == Player and type(
                    self.__player_2) == Player:
                # if both players are human, restart initialization
                self.__gui.text_red(
                    "ERROR: Both players have the same name or symbol! Try again."
                )
                # wait for user to press any key
                input()
                self.__gui.clear_display()
                self.__initialize_session()
                return  # abort current call of "initialize_match" since new one has been created
            else:
                # if at least one player is a bot, resolve ambiguity automatically
                # by creating a new Bot instance, a new random name is given
                while self.__player_1.name == self.__player_2.name:
                    if type(self.__player_1) == Bot:
                        self.__player_1 = Bot(1,
                                              self.__player_1_default_symbol)
                    else:
                        self.__player_2 = Bot(2,
                                              self.__player_2_default_symbol)
                # if both players have the same symbol, the human player must have chosen the default symbol that was
                # intended for the other player - to resolve this, the bot simply picks 'o' instead of 'x' or vice versa
                if type(self.__player_1) == Bot:
                    self.__player_1.symbol = self.__player_2_default_symbol
                else:
                    self.__player_2.symbol = self.__player_1_default_symbol

        # map ids to symbols
        self.__id_to_symbol[self.__player_1.id] = self.__player_1.symbol
        self.__id_to_symbol[self.__player_2.id] = self.__player_2.symbol

        # summarize player information before exiting initialization
        self.__gui.text_blue("Great. Let's start the game!")
        self.__gui.text("Player 1 ({}): {} is '{}'.".format(
            "HUMAN" if type(self.__player_1) == Player else "BOT",
            self.__player_1.name, self.__player_1.symbol))
        self.__gui.text("Player 2 ({}): {} is '{}'.".format(
            "HUMAN" if type(self.__player_2) == Player else "BOT",
            self.__player_2.name, self.__player_2.symbol))
        # wait for user to press any key
        input()
        self.__gui.clear_display()

    # with "typing.Union", the function annotation can be expanded so that player can be of different Classes
    def __move(self, player: Union[Player, Bot]) -> None:
        # a move of a bot will always be valid - thus, only for a human player further checking is needed
        if type(player) == Bot:
            self.__gui.text("{} 'thinks' about the next move.".format(
                player.name))
            self.__board.do_move(
                player.get_move(self.__board, player.id, self.__rules),
                player.id)
        else:
            while True:
                # "player.get_move" asks user for valid input (when width is 7: integer between 0 and 6)
                desired_move = player.get_move(self.__board,
                                               self.__id_to_symbol, self.__gui)
                # "rules.is_move_possible" checks if desired move is not against the rules
                if self.__rules.check_move(self.__board.get_board(),
                                           self.__board.height, desired_move):
                    break
                else:
                    self.__gui.clear_display()
                    print(self.__board.width, self.__board.height)
                    self.__gui.show_board(self.__board.get_board(),
                                          self.__board.width,
                                          self.__board.height,
                                          self.__id_to_symbol)
                    # let user know the move is not possible
                    # "desired_move" is zero-based and needs to be increased by one for display
                    self.__gui.text_red(
                        "ERROR: Column {} is already full! Try again.".format(
                            desired_move + 1))
            # when a legal move is given, "board.do_move" organizes actually playing it
            self.__board.do_move(desired_move, player.id)

    def __play_match(self) -> None:
        # "match_round" keeps track of number of played moves so far
        match_round = 0
        while True:
            match_round += 1
            # before asking a player what to do, show the board
            self.__gui.show_board(self.__board.get_board(), self.__board.width,
                                  self.__board.height, self.__id_to_symbol)
            # depending on who's turn it is, let player do a move
            if match_round % 2 == 1:
                # prompt player to do a move with method "move"
                self.__move(self.__player_1)
                # check if player 1 has won with his latest move
                winning_line = self.__rules.check_win(
                    self.__board.get_board(),
                    self.__board.width, self.__board.height,
                    self.__board.get_last_move(), self.__player_1.id)
                # "winning_line" is either None or a list of tuples that store positions of tokens which led to win
                if winning_line is not None:
                    self.__gui.clear_display()
                    # player 1 has won; show board - emphasizing the winning line - one last time before exiting method
                    self.__gui.show_board(self.__board.get_board(),
                                          self.__board.width,
                                          self.__board.height,
                                          self.__id_to_symbol, winning_line)
                    self.__gui.text_blue("{} has won. Good game!".format(
                        self.__player_1.name))
                    # increase the score of player 1 by one before exiting method
                    self.__player_1.score += 1
                    # wait for user to press any key
                    input()
                    self.__gui.clear_display()
                    return
            else:
                # prompt player to do a move with method "move"
                self.__move(self.__player_2)
                # check if player 2 has won with his latest move
                winning_line = self.__rules.check_win(
                    self.__board.get_board(),
                    self.__board.width, self.__board.height,
                    self.__board.get_last_move(), self.__player_2.id)
                # "winning_line" is either None or a list of tuples that store positions of tokens which led to win
                if winning_line is not None:
                    self.__gui.clear_display()
                    # player 2 has won; show board - emphasizing the winning line - one last time before exiting method
                    self.__gui.show_board(self.__board.get_board(),
                                          self.__board.width,
                                          self.__board.height,
                                          self.__id_to_symbol, winning_line)
                    self.__gui.text_blue("{} has won. Good game!".format(
                        self.__player_2.name))
                    # increase the score of player 2 by one before exiting method
                    self.__player_2.score += 1
                    # wait for user to press any key
                    input()
                    self.__gui.clear_display()
                    return

            self.__gui.clear_display()
            # if board is full, show it one last time and let players know that match is a draw before exiting method
            if self.__rules.check_game_over(self.__board.get_board(),
                                            self.__board.width,
                                            self.__board.height):
                self.__gui.show_board(self.__board.get_board(),
                                      self.__board.width, self.__board.height,
                                      self.__id_to_symbol)
                self.__gui.text_blue("It's a draw!")
                # wait for user to press any key
                input()
                self.__gui.clear_display()
                return

    def __replay_match(self):
        self.__gui.text(
            "You are about to watch the latest match again. Press Enter to see the next move."
        )
        # get game history from board as a list
        history = self.__board.get_history()
        # in some situations (like changing the board dimensions and then wanting a replay), history might be corrupted
        if len(history) == 0:
            self.__gui.text_red(
                "ERROR: Game history is no longer available after setting changes."
            )
            input()
            self.__gui.clear_display()
            return

        # reset board and history before "playing it again"
        self.__board.clear_board()
        self.__board.clear_history()

        player_id = 1
        # show all moves except the last one highlighting the current move as "winning_line"
        for move in history[:-1]:
            # only x value is needed to play the move
            self.__board.do_move(move[0], player_id)
            # display current board
            self.__gui.show_board(self.__board.get_board(), self.__board.width,
                                  self.__board.height, self.__id_to_symbol,
                                  [move])
            player_id = 1 if player_id == 2 else 2
            input()
            self.__gui.clear_display()
        # play the final move
        self.__board.do_move(history[-1][0], player_id)
        # emphasize the winning line (if it exists)
        winning_line = self.__rules.check_win(self.__board.get_board(),
                                              self.__board.width,
                                              self.__board.height, history[-1],
                                              player_id)
        self.__gui.show_board(self.__board.get_board(), self.__board.width,
                              self.__board.height, self.__id_to_symbol,
                              winning_line)

    def __change_board_dimensions(self):
        while True:
            self.__gui.clear_display()
            input_width = input("What width should the board have?")
            self.__gui.clear_display()
            input_height = input("What height should the board have?")
            self.__gui.clear_display()
            try:
                input_width_int = int(input_width)
                input_height_int = int(input_height)
                if 0 < input_width_int < 10 and 0 < input_height_int <= 20:
                    self.__board = Board(input_width_int, input_height_int)
                    self.__board.clear_history()
                    self.__gui.text_blue("Alright, changes have been saved.")
                    return
                self.__gui.text_red(
                    "ERROR: Width cannot exceed 9 and height cannot exceed 20! Try again."
                )
                # wait for user to press any key
                input()
            except ValueError:
                self.__gui.text_red(
                    "ERROR: Only integers are permissible input! Try again.")
                # wait for user to press any key
                input()

    def __settings(self) -> None:
        while True:
            self.__gui.text("Welcome to the settings. Here's what you can do")
            self.__gui.text("-> change board dimensions (D)")
            self.__gui.text("-> initialize a new session and reset scores (R)")
            input_change_player_settings = input().lower()
            if input_change_player_settings == 'd':
                self.__change_board_dimensions()
                return
            elif input_change_player_settings == 'r':
                self.__initialize_session()
                return
            else:
                self.__gui.text_red("ERROR: Your input is not an option.")

    def __goodbye(self) -> None:
        self.__gui.clear_display()
        self.__gui.text_blue("Thank you for playing. Bye for now!")

    def __keep_playing(self) -> bool:
        # show users current score and display options to continue
        self.__gui.text("What a match!")
        self.__gui.text("Your score now is: {} ({}) - {} ({})".format(
            self.__player_1.score, self.__player_1.name, self.__player_2.score,
            self.__player_2.name))
        # stay in this "menu" until user decides to exit it
        while True:
            self.__gui.text("What would you like to do next?")
            self.__gui.text("-> watch a replay of the match (R)")
            self.__gui.text("-> change game settings (S)")
            self.__gui.text("-> start a new match (M)")
            self.__gui.text("-> quit game (Q)")
            input_decision = input().lower()
            self.__gui.clear_display()

            if input_decision == 'r':
                self.__replay_match()
            elif input_decision == 's':
                self.__settings()
                # clear history to prevent player from replaying a previous game after fiddling around in settingsd
                self.__board.clear_board()
                self.__board.clear_history()
            elif input_decision == 'm':
                self.__gui.text_blue("Cool, next match starts now!")
                # wait for user to press any key
                input()
                self.__gui.clear_display()
                self.__board.clear_board()
                self.__board.clear_history()
                return True
            elif input_decision == 'q':
                self.__goodbye()
                return False
            else:
                self.__gui.text_red("ERROR: Your input is not an option.")
                input()
                self.__gui.clear_display()

    def __play(self) -> None:
        self.__gui.clear_display()
        # welcome players once in the beginning
        self.__welcome()
        # before playing, players need to be set etc.; this is done in "initialize_match"
        self.__initialize_session()
        # until breaking out of an infinite loop, matches are played
        while True:
            # "play_match" will carry out an entire match until a player wins or there are no moves left
            self.__play_match()
            # find out if another match is wanted and react accordingly with "keep_playing"
            if self.__keep_playing():
                self.__board.clear_board()
            else:
                break
Ejemplo n.º 4
0
class MonteCarloExploer:
    state_init = 0
    state_select = 1
    state_simulation = 2
    state_backpropagation = 3
    state_waitfor_move = 4

    def __init__(self, size, board, tree, network):
        self.network = network
        self.size = size
        self.tree = tree
        self.orig = board
        self.rules = Rules(size, FLAGS.connections)
        self.nr_moves = 0
        self.reinitialize()

    def reinitialize(self):
        self.tree.total += 1.0
        self.nr_moves = 0
        a, b = self.orig
        self.board = a.copy(), b.copy(), a + b
        self.state = MonteCarloExploer.state_select
        self.trace = [self.tree]
        self.termination = False
        self.reward = 0.0
        self.pending_state = None
        self.move = None
        self.node = self.tree

    def step(self):
        return_value = 0
        if self.state == MonteCarloExploer.state_select:
            fst, snd, m = self.board
            if self.node.prior is None:
                state = np.stack(self.board, axis=-1)
                moves, Q = DqnAgent.select(np.expand_dims(state, axis=0),
                                           self.network)
                Q = Q.reshape(-1)
                Q = Q - Q.min()
                Q /= np.max(np.abs(Q))
                self.node.prior = Q
            mask = m.ravel()
            move = self.node.select(mask)
            x, y = move // self.size, move % self.size
            assert (fst[(x, y)] == False and m[(x, y)] == False)
            fst[x, y] = True
            m[x, y] = True
            self.nr_moves += 1
            win_condition, _ = self.rules.check_win((x, y), fst)
            if win_condition:
                self.reward = 1.0
                self.termination = True
            if np.all(m):
                self.reward = 0.5
                self.termination = True
            expand = False
            if self.node.children[move] == None:
                # expand
                self.node.children[move] = MonteCarloTree(self.size)
                expand = True
            self.trace.append(self.node.children[move])
            self.node = self.node.children[move]
            self.node.total += 1.0
            if self.termination == True:
                self.state = MonteCarloExploer.state_backpropagation
            elif expand:
                self.state = MonteCarloExploer.state_simulation
            else:
                self.state = MonteCarloExploer.state_select
            self.board = snd, fst, m
        elif self.state == MonteCarloExploer.state_backpropagation:
            if self.nr_moves % 2 == 1:
                self.reward = 1.0 - self.reward
            for i in self.trace:
                i.reward += self.reward
                self.reward = 1.0 - self.reward
            self.state = MonteCarloExploer.state_select
            self.reinitialize()
        elif self.state == MonteCarloExploer.state_simulation:
            self.pending_state = np.stack(self.board, axis=-1)
            self.move = None
            self.state = MonteCarloExploer.state_waitfor_move
            return_value = 1
        elif self.state == MonteCarloExploer.state_waitfor_move:
            if self.move != None:
                fst, snd, m = self.board
                if random.random() < FLAGS.mcts_epsilon:
                    while True:
                        m = self.board[0] + self.board[1]
                        move = random.randint(0, self.size**2 - 1)
                        move = move // self.size, move % self.size
                        if m[move] == False:
                            break
                else:
                    move = self.move // self.size, self.move % self.size
                self.move = None
                fst[move] = True
                m[move] = True
                self.nr_moves += 1
                termination = False
                win_condition, _ = self.rules.check_win(move, fst)
                if win_condition:
                    self.reward = 1.0
                    termination = True
                if np.all(m):
                    self.reward = 0.5
                    termination = True
                if termination:
                    self.state = MonteCarloExploer.state_backpropagation
                else:
                    self.state = MonteCarloExploer.state_simulation
                self.board = snd, fst, m
        return return_value
Ejemplo n.º 5
0
class DqnAgent(Agent):
    state_self = 0
    state_opponent = 1
    state_mask = 2

    def __init__(self, size, session, scope, threads):
        super().__init__(size, session, scope, threads)
        self.autoresolve = False
        self.rules = Rules(size, FLAGS.connections)
        self.test_mode = False
        self.epsilon = 0.0
        self.board = None

    def clear(self):
        super().clear()
        self.buffered_move = None
        self.board = np.zeros((self.size, self.size), dtype = np.int32), \
                     np.zeros((self.size, self.size), dtype = np.int32), \
                     np.zeros((self.size, self.size), dtype = np.int32)
        self.fst_move = self.snd_move = None

    def update_state_(self, position, mover):
        mask = self.board[2]
        mover_board = self.board[mover]
        assert (mask[position] == 0.0 and mover_board[position] == 0.0)
        mask[position] = 1.0
        mover_board[position] = 1.0

    @staticmethod
    def select(input, network):
        stacked = np.stack(input, axis=0)
        pred, out = network.session.run(
            [network.predictions, network.legal_moves],
            feed_dict={network.input: stacked})
        m = np.argmax(out, axis=1)
        return m, out

    def self_move(self, _):
        fst, snd, mask = self.board
        move = None
        if self.autoresolve and self.fst_move and self.snd_move:
            if move == None:
                win_condition, set = self.rules.check_win(
                    self.fst_move, fst, mask)
                if win_condition:
                    for s in set:
                        x, y = s // self.size, s % self.size
                        if fst[(x, y)] == 0.0:
                            move = x, y
                            break
            if move == None:
                win_condition, set = self.rules.check_win(
                    self.snd_move, snd, mask)
                if win_condition:
                    for s in set:
                        x, y = s // self.size, s % self.size
                        move = x, y
                        if snd[(x, y)] == 0.0:
                            move = x, y
                            break
        m, q = self.buffered_move
        if move == None:
            if random.uniform(0, 1) < self.epsilon:
                x, y = move = super().random_policy(mask)
            else:
                assert (self.buffered_move != None)
                x, y = move = m // self.size, m % self.size
        self.buffered_move = None
        self.update_state_(move, DqnAgent.state_self)
        self.fst_move = move
        return move, q

    def opponent_move(self, position, _):
        self.update_state_(position, DqnAgent.state_opponent)
        self.snd_move = position