Beispiel #1
0
def test_add_tiles():
    bag = Bag()
    starting_num_of_tiles = bag.remaining_tiles()
    tiles = bag.get_tiles(10)
    assert bag.remaining_tiles() == starting_num_of_tiles - 10
    bag.add_tiles(tiles)
    assert bag.remaining_tiles() == starting_num_of_tiles
Beispiel #2
0
def test_get_tiles():
    bag = Bag()
    starting_num_of_tiles = len(bag.bag_tiles)
    assert starting_num_of_tiles == sum(LETTER_DISTRIBUTIONS)
    tiles = bag.get_tiles(10)
    assert len(tiles) == 10
    assert len(bag.bag_tiles) == starting_num_of_tiles - 10
Beispiel #3
0
def test_contents():
    rack = Rack(Bag())
    rack.rack_tiles = ['A', 'A', 'B', 'B', 'C', 'D', 'E']
    assert 'ABC' in rack
    assert 'AAB' in rack
    assert 'AAABBB' not in rack
    assert ['D', 'A', 'B'] in rack
    assert ['D', 'A', 'B', 'D'] not in rack
Beispiel #4
0
def test_get_a_tile_in_rack():
    rack = Rack(Bag())
    letter = rack.rack_tiles[0]
    assert len(rack.rack_tiles) == RACK_SIZE
    tile = rack.get_tile(letter)
    assert len(rack.rack_tiles) == (RACK_SIZE - 1)
    assert tile is not None
    assert tile == letter
Beispiel #5
0
def test_init():
    players = [None, None]
    bag = Bag()
    game = GameController(players, bag)
    gui = ConsoleGui(game)
    player1 = HumanPlayer(bag, gui, "Player 1")
    player2 = HumanPlayer(bag, gui, "Player 2")
    game.players = [player1, player2]
    assert game.game_state == GameState.PENDING
Beispiel #6
0
def test_replenish():
    rack = Rack(Bag())
    for i in range(13):
        rack.get_tiles(str(rack))
        assert len(rack.rack_tiles) == 0
        rack.replenish_tiles()
        assert len(rack.rack_tiles) == RACK_SIZE
    rack.get_tiles(str(rack))
    assert len(rack.rack_tiles) == 0
    rack.replenish_tiles()
    assert len(rack.rack_tiles) < RACK_SIZE
Beispiel #7
0
def test_change_active_player():
    players = [None, None]
    bag = Bag()
    game = GameController(players, bag)
    gui = ConsoleGui(game)
    player1 = HumanPlayer(bag, gui, "Player 1")
    player2 = HumanPlayer(bag, gui, "Player 2")
    game.players = [player1, player2]
    game.active_player = player1
    assert game.active_player == player1
    game.change_active_player()
    assert game.active_player == player2
Beispiel #8
0
def test_fill():
    bag = Bag()
    bag.bag_tiles.clear()
    assert bag.bag_tiles == []
    bag.fill()
    assert bag.remaining_tiles() == sum(LETTER_DISTRIBUTIONS)

    temp_distributions = copy.deepcopy(LETTER_DISTRIBUTIONS)
    for i in range(bag.remaining_tiles()):
        tile = bag.get_tiles(1)[0]
        temp_distributions[ord(tile) -
                           64] -= 1  # check off each letter as it is drawn

    assert temp_distributions.count(0) == len(
        temp_distributions)  # all elements should now be zero
Beispiel #9
0
def test_parse_move_string():
    bag = Bag()
    game = GameController([None, None], bag)
    gui = ConsoleGui(game)
    player1 = HumanPlayer(bag, gui, "Player 1")
    player2 = HumanPlayer(bag, gui, "Player 2")
    game.players = [player1, player2]
    game.active_player = player1
    player1.rack.rack_tiles[0] = 'A'
    player1.rack.rack_tiles[1] = 'C'
    player1.rack.rack_tiles[2] = 'T'
    move = gui.parse_move_string('F8HCAT')
    assert move.row.direction == Direction.HORIZONTAL
    assert move.row.rank == 8
    assert len(move.played_squares) == 3
    assert all([a == b for a, b in zip(move.played_squares, [6, 7, 8])])
    assert len(move.tiles) == 3
    assert all([a == b for a, b in zip(move.tiles, ['C', 'A', 'T'])])
Beispiel #10
0
    def __init__(self, event_manager):
        super().__init__(event_manager)

        self.game_finished = False
        self.playing = False
        self.turn = 0

        self.clock = pygame.time.Clock()
        self.std_font = pygame.font.SysFont("sans-serif", 22)
        self.button_font = pygame.font.SysFont("monospace", 24)
        self.button_font.set_bold(True)
        self.board = Board(self.event_manager, 50, 100, 1, 7)

        self.options = [
            Button(self.button_font, config.END_ROUND, (235, 200),
                   self.on_end_round_click),
            Button(self.button_font, config.CLEAR, (150, 250),
                   self.on_clear_click),
            Button(self.button_font, config.BACKSPACE, (350, 250),
                   self.on_backspace_click)
        ]

        # init players
        self.player_a = Player('Player 1')
        self.player_b = Player('PC')
        self.render_sleep = 0

        # todo remove
        self.player_a.is_playing = True

        self.score_board = ScoreBoard(self.player_a, self.player_b, 20, 20)
        self.deck = Deck(self.event_manager, 50, 350)
        self.bag = Bag()
        self.set_deck()

        for option in self.options:
            self.event_manager.register(ClickEvent, option)
Beispiel #11
0
def test_overflow_rack():
    rack = Rack(Bag())
    with pytest.raises(ValueError) as e_info:
        rack.add_tiles(['A', 'B'])
    assert len(rack.rack_tiles) == RACK_SIZE
Beispiel #12
0
def test_init():
    bag = Bag()
Beispiel #13
0
def process_game(gcg_lines, game_number):
    players = [None, None]
    game = GameController(players, Bag())
    gui = ConsoleGui(game)
    movelist = [line.split() for line in gcg_lines]
    player1 = AiPlayer(game, gui, movelist.pop(0)[1])
    player2 = AiPlayer(game, gui, movelist.pop(0)[1])
    game.players = [player1, player2]
    game.active_player = player1 if player1.name == movelist[0][0][1:-1] else player2

    current_move = 0  # current move
    while movelist:
        current_move += 1
        current_move_info = movelist.pop(0)

        # if there are things in brackets, they are score adjustments so we've
        # finished all the moves:
        if '(' in current_move_info[1]:
            return

        # we use '@' for blank for pragmatic reasons (it's the ASCII character before 'A')
        # GCG uses '?', so let's fix that:
        game.active_player.rack.rack_tiles = list(current_move_info[1].replace('?', '@'))

        # Now we have the correct tiles in the rack and the board is advanced to the correct position,
        # let's get a list of all the moves ALexIS can come up with:
        alexis_moves = game.active_player.generate_all_moves()
        alexis_moves.sort(key=lambda x: x.score, reverse=True)  # Sort if with highest scores first

        # put any blanks back to having letter un-assigned, ready for parsing Quackle move
        game.active_player.rack.reset_blanks()

        # Now let's move onto getting our 'good' move from the GCG file:

        # GCG has digit(s) followed by letter for horizontal move, or vice versa for vertical,
        # wheras we are expecting digit, then letter, then 'H' or 'V', so let's fix that:

        start_square = current_move_info[2]

        if start_square.startswith('-'):
            tiles = ''
            if len(start_square)>1:
                tiles = start_square[1]
            start_square = ''
        else:
            if start_square[0].isdigit():
                start_square = start_square[-1] + start_square[:-1] + 'H'
            else:
                start_square += 'V'
            # lowercase letters in the GCG represent blanks. We're expecting a question mark for a blank,
            # followed by the desired letter, so replace 'a' with '?A', etc:
            tiles = ''.join(['?' + letter.upper() if letter.islower() else letter for letter in current_move_info[3]])

        # GCG gives the start square as the first letter in the word,
        # and uses '.' as a placeholder for any tile already on the board,
        # whereas we list the first square we're actually playing on, and
        # just the tiles actually played, so let's strip out '.' and adjust the
        # starting square if necessary:

        if '.' in tiles:
            start_square = list(start_square)  # treat string as char list
            x = 0
            while tiles[x] == '.':  # whilst there's a dot at the start
                if start_square[-1] == 'V':
                    start_square[-2] = str(int(start_square[-2]) + 1)  # increase the row if playing vertical
                else:
                    start_square[0] = chr(ord(start_square[0]) + 1)  # or the column if horizontal
                x += 1
            start_square = ''.join(start_square)  # make a string again
            tiles = tiles.replace('.', '')  # strip out any dot in the middle of the word

        # save the rack for later:
        cached_rack = copy.deepcopy(game.active_player.rack)

        # This will get a move and remove tiles from rack:
        quackle_move = gui.parse_move_string(start_square + tiles)

        if quackle_move.direction is not Direction.NOT_APPLICABLE:
            # validator will place tiles onto row in course of validation,
            # so first we'll copy the row so as not to mess up the board:
            quackle_move.row = copy.deepcopy(quackle_move.row)
            game.validator.is_valid(quackle_move)

            # this will calculate score (but only if tiles are already played on row):
            quackle_move.calculate_score()

        # now choose some moves. If we do data augmentation by transposing the 'correct' Quackle-derived moves,
        # we'll have 2 correct moves, so picking six of these would give us multiples of 8 moves.
        # Picking the top couple and randomly picking the rest would analyse a couple of 'good' moves
        # but still allow a little exploration (c.f. Q learning)

        if quackle_move in alexis_moves:
            alexis_moves.remove(quackle_move)  # don't process the quackle move as one of the wrong ones

        # just in case it's the end game:
        while len(alexis_moves) < 6:
            # add a pass:
            alexis_moves.append(Move(None, None, None))

        wrong_moves = []
        wrong_moves.append(alexis_moves.pop(0))
        wrong_moves.append(alexis_moves.pop(0))

        # add 4 randomly chosen moves:
        wrong_moves.extend(choices(alexis_moves, k=4))

        for option in range(len(wrong_moves)):
            base_layer = copy.deepcopy(game.board.existing_letters[:-1, :-1])  # slice off last sentinel
            # set first sentinel squares to zero instead of sentinel value:
            base_layer[0, :] = 0
            base_layer[:, 0] = 0

            move_layer = np.zeros([16, 16], dtype='int')
            # put rack tiles we're playing from in first row:
            move_layer[0][0:len(cached_rack)] = [ord(t) - 64 for t in cached_rack.rack_tiles]

            # set first sentinel squares to zero instead of sentinel value:
            word_mult = np.where(game.board.word_multipliers > 1, game.board.word_multipliers * 8, 0)
            letter_mult = np.where(game.board.letter_multipliers > 1, game.board.letter_multipliers * 2, 0)
            score_layer = np.where(game.board.existing_letter_scores > 0, game.board.existing_letter_scores * 2,
                                   word_mult + letter_mult)
            score_layer = score_layer[:-1, :-1]  # slice off last sentinel
            # set first sentinel squares to zero instead of sentinel value:
            score_layer[0, :] = 0
            score_layer[:, 0] = 0

            move = wrong_moves[option]
            move_tiles = move.tiles if move.tiles else []

            if move.direction == Direction.NOT_APPLICABLE:  # pass or exchange
                # put rack tiles we're exchanging in first row:
                if move.tiles: # unless it's a pass with no tiles
                    move_layer[0][0:len(move_tiles)] = [ord(t) - 64 for t in move.tiles]

            else:  # regular move
                row = move_layer[move.row.rank, :] if move.direction == Direction.HORIZONTAL else move_layer[:,
                                                                                                  move.row.rank]
                # put rack tiles we're playing on board:
                row[move.played_squares] = [ord(t) - 64 for t in move.tiles]
                # change score of square containing blank to zero:
                score_layer = np.where(move_layer <= 26, score_layer, 0)
                # change blanks to normal letter ordinal (by subtracting 32)
                move_layer = np.where(move_layer <= 26, move_layer, move_layer - 32)

            # flatten arrays and convert int8 to int so values aren't clipped at 128:
            rgb = zip((base_layer.astype(int)).flatten() * 9, (score_layer.astype(int)).flatten() * 9,
                      (move_layer.astype(int)).flatten() * 9)
            # put in a list:
            rgb = [pixel for pixel in rgb]

            # convert to an image, and resize so things like
            # max pooling layers won't lose all the information in the image:
            img = Image.new('RGB', (16, 16))
            img.putdata(rgb)
            img = img.resize((256, 256), Image.NEAREST)

            # save the image
            img.save(img_path + 'a_g' + str(game_number).zfill(4)
                     + '_m' + str(current_move).zfill(2)
                     + '_option' + str(option + 1) + '.png')

            # add a little feedback to the console:
            print(":" + str((game_number, current_move, option)))

        # now process the Quackle move:
        base_layer = copy.deepcopy(game.board.existing_letters[:-1, :-1])  # slice off last sentinel
        # set first sentinel squares to zero instead of sentinel value:
        base_layer[0, :] = 0
        base_layer[:, 0] = 0

        move_layer = np.zeros([16, 16], dtype='int')
        # put rack tiles we're playing from in first row:
        move_layer[0][0:len(cached_rack)] = [ord(t) - 64 for t in cached_rack.rack_tiles]

        # set first sentinel squares to zero instead of sentinel value:
        word_mult = np.where(game.board.word_multipliers > 1, game.board.word_multipliers * 8, 0)
        letter_mult = np.where(game.board.letter_multipliers > 1, game.board.letter_multipliers * 2, 0)
        score_layer = np.where(game.board.existing_letter_scores > 0, game.board.existing_letter_scores * 2,
                               word_mult + letter_mult)
        score_layer = score_layer[:-1, :-1]  # slice off last sentinel
        # set first sentinel squares to zero instead of sentinel value:
        score_layer[0, :] = 0
        score_layer[:, 0] = 0

        move = quackle_move
        move_tiles = move.tiles if move.tiles else []

        if move.direction == Direction.NOT_APPLICABLE:  # pass or exchange
            # put rack tiles we're exchanging in first row:
            if move_tiles:
                move_layer[0][0:len(move_tiles)] = [ord(t) - 64 for t in move.tiles]

        else:  # regular move
            row = move_layer[move.row.rank, :] if move.direction == Direction.HORIZONTAL else move_layer[:,
                                                                                              move.row.rank]
            # put rack tiles we're playing on board:
            row[move.played_squares] = [ord(t) - 64 for t in move.tiles]
            # change score of square containing blank to zero:
            score_layer = np.where(move_layer <= 26, score_layer, 0)
            # change blanks to normal letter ordinal (by subtracting 32)
            move_layer = np.where(move_layer <= 26, move_layer, move_layer - 32)

        # flatten arrays and convert int8 to int so values aren't clipped at 128:
        rgb = zip((base_layer.astype(int)).flatten() * 9, (score_layer.astype(int)).flatten() * 9,
                  (move_layer.astype(int)).flatten() * 9)
        # put in a list:
        rgb = [pixel for pixel in rgb]

        # convert to an image, and resize so things like
        # max pooling layers won't lose all the information in the image:
        img = Image.new('RGB', (16, 16))
        img.putdata(rgb)
        img = img.resize((256, 256), Image.NEAREST)

        # save the image
        img.save(img_path + 'q_g' + str(game_number).zfill(4)
                 + '_m' + str(current_move).zfill(2)
                 + '_option1.png')

        # now do data augmentation by transposing the board:
        base_layer[1:16, 1:16] = base_layer[1:16, 1:16].T
        move_layer[1:16, 1:16] = move_layer[1:16, 1:16].T
        score_layer[1:16, 1:16] = score_layer[1:16, 1:16].T

        # save the transposed version:
        rgb = zip((base_layer.astype(int)).flatten() * 9, (score_layer.astype(int)).flatten() * 9,
                  (move_layer.astype(int)).flatten() * 9)
        rgb = [pixel for pixel in rgb]
        img = Image.new('RGB', (16, 16))
        img.putdata(rgb)
        # img = img.resize((256, 256), Image.NEAREST)
        img.save(img_path + 'q_q' + str(game_number).zfill(4)
                 + '_m' + str(current_move).zfill(2)
                 + '_option2.png')

        # now actually execute the move to prepare the board for the next move:

        # we've probably fake-played all the tiles so put them back in the rack:
        game.active_player.rack = cached_rack

        # only bother playing the move if it actually changes the board,
        # since we're not tracking what's in the bag, or what the current scores are:
        if quackle_move.direction is not Direction.NOT_APPLICABLE:
            # ensure the row we're using is a slice of the board, not a copy:
            if quackle_move.row:
                quackle_move.row = game.board.get_row(quackle_move.row.rank, quackle_move.row.direction)

            # clear move validation and re-validate the move, in doing so play it onto the correct row:
            quackle_move.is_valid = None
            game.validator.is_valid(quackle_move)

            game.execute_move(quackle_move)
Beispiel #14
0
def test_remaining_tiles():
    bag = Bag()
    starting_num_of_tiles = sum(LETTER_DISTRIBUTIONS)
    assert bag.remaining_tiles() == starting_num_of_tiles
    bag.get_tiles(10)
    assert bag.remaining_tiles() == starting_num_of_tiles - 10
Beispiel #15
0
def test_create_rack():
    rack = Rack(Bag())
Beispiel #16
0
def test_rack_size():
    rack = Rack(Bag())
    assert len(rack.rack_tiles) == RACK_SIZE
Beispiel #17
0
def test_add_too_many_tiles():
    rack = Rack(Bag())
    with pytest.raises(IndexError) as e_info:
        rack.add_tile('A')  # adds tile to a new (hence full) rack
Beispiel #18
0
def test_create_gui():
    players = [None, None]
    bag = Bag()
    game = GameController(players, bag)
    gui = ConsoleGui(game)