Ejemplo n.º 1
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
Ejemplo n.º 2
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
Ejemplo n.º 3
0
    def cpu_play(self, characters, mode):
        # make sure characters is an uppercase string
        if isinstance(characters, list):
            characters = "".join(characters)
        characters = characters.upper()

        wlens = range(2, 8)

        if mode == CPU_MODE_MAX:
            wlens = reversed(wlens)

        selected = None
        for l in wlens:
            for word in itertools.permutations(characters, l):
                # convert tupple to str
                word = ''.join(word)

                # check if valid word
                if word in config.GREEK7_WORDS:
                    score = Bag.count_score(word)

                    if selected == None or selected[1] < score:
                        selected = (word, score, l)

                    if mode < CPU_MODE_SMART:
                        return selected

        return selected
Ejemplo n.º 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
Ejemplo n.º 5
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
Ejemplo n.º 6
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
Ejemplo n.º 7
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
Ejemplo n.º 8
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
Ejemplo n.º 9
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)
Ejemplo n.º 10
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
Ejemplo n.º 11
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'])])
Ejemplo n.º 12
0
def test_init():
    bag = Bag()
Ejemplo n.º 13
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
Ejemplo n.º 14
0
def test_create_rack():
    rack = Rack(Bag())
Ejemplo n.º 15
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
Ejemplo n.º 16
0
def test_create_gui():
    players = [None, None]
    bag = Bag()
    game = GameController(players, bag)
    gui = ConsoleGui(game)
Ejemplo n.º 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
Ejemplo n.º 18
0
def test_rack_size():
    rack = Rack(Bag())
    assert len(rack.rack_tiles) == RACK_SIZE
Ejemplo n.º 19
0
class NewGame(View):
    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)

    def on_clear_click(self, button, event):
        self.board.on_press_esc(event)

    def on_end_round_click(self, button, event):
        if not self.playing:
            self.play()

    def on_backspace_click(self, button, event):
        self.board.on_press_backspace(event)

    def get_player(self):
        return self.player_a if self.player_a.is_playing else self.player_b

    def set_deck(self):
        player = self.get_player()
        deck = []

        if self.bag.remaining_chars() < config.MAX_WORD_LEN:
            return None

        if len(player.unused_words) != config.MAX_WORD_LEN:
            for i in range(len(player.unused_words)):
                deck.append(player.unused_words[i])
                self.deck.append_character(player.unused_words[i])
        else:
            for i in range(len(player.unused_words)):
                self.bag.collection.append(player.unused_words[i])

        while not self.deck.get_free_tile() == -1:
            word = self.bag.get_char()
            deck.append(word)
            self.deck.append_character(word)

        return deck

    def flip_players(self):
        self.player_a.is_playing = not self.player_a.is_playing
        self.player_b.is_playing = not self.player_b.is_playing

    def play(self):

        self.playing = True
        self.turn += 1

        word, word_score = self.board.get_word()

        # check user word and update score
        if word in config.GREEK7_WORDS:
            self.player_a.add_score(word_score)
            self.player_a.append_word(word)

        # clear deck and board
        self.player_a.unused_words = self.deck.clear()
        self.board.clear()

        # computer turn
        self.flip_players()

        if self.bag.remaining_chars() < config.MAX_WORD_LEN:
            return self.end_game()

        deck_cpu = self.set_deck()
        # TODO add back to bag deck_cpu not used chars
        word_cpu = self.player_a.cpu_play([i[0] for i in deck_cpu],
                                          CPU_MODE_SMART)

        if word_cpu is None:
            return self.end_game()
        else:
            for i in range(len(word_cpu[0])):
                for j in range(len(deck_cpu)):
                    if deck_cpu[j][0] == word_cpu[0][i]:
                        deck_cpu.pop(j)
                        break

            self.player_b.unused_words = deck_cpu

            self.player_b.append_word(word_cpu[0])
            self.player_b.add_score(word_cpu[1])
            self.board.set_word(word_cpu[0])

        self.render_sleep = 1

    def end_game(self):
        data = [self.player_a.score, self.player_b.score, self.turn]
        utils.write_to_file(config.SCORES_PATH, data)
        self.game_finished = True

    def render(self):

        # draw background
        self.render_background()

        # draws the score board
        self.score_board.render()

        if self.game_finished:
            end_font = pygame.font.SysFont('sans-serif', 100)
            end_font.set_bold(True)
            winner = self.player_a.get_name(
            ) if self.player_a.score >= self.player_b.score else self.player_b.get_name(
            )
            rendered_message = end_font.render(winner + " WIN !", 1,
                                               config.WHITE)
            self.screen.blit(
                rendered_message,
                (config.SCREEN_W / 2 - rendered_message.get_rect().width / 2,
                 config.SCREEN_H / 2 - 20))
            pygame.display.flip()

            pygame.time.delay(3000)
            self.event_manager.post(MainMenuEvent())
        else:
            # draws the board
            self.board.render()

            # draws the options
            for option in self.options:
                option.render(self.screen)

            # draws the decks
            self.deck.render()

            # draws the number of remaining chars
            self.render_remaining_chars()

            # checks if CPU is playing
            self.check_sleep()

        # limit to 60 frames per second
        self.clock.tick(60)

        # go ahead and update the screen with what we've drawn
        pygame.display.flip()

    def on_destroy(self):
        print("destroy new_game view")

        # call nested views on_destroy
        self.score_board.on_destroy()
        self.board.on_destroy()
        self.deck.on_destroy()

        for option in self.options:
            self.event_manager.remove(ClickEvent, option)

    def check_sleep(self):
        if self.render_sleep > 0:
            self.render_sleep += 1

            if self.render_sleep > 3 and not self.game_finished:
                pygame.time.delay(1000)
                self.render_sleep = 0
                self.board.clear()
                self.deck.clear()
                self.flip_players()
                self.playing = False

                if self.set_deck() is None:
                    return self.end_game()

    def render_remaining_chars(self):
        rem_chars_render = self.std_font.render(
            "remaining: " + str(self.bag.remaining_chars()), 1,
            (255, 255, 255))
        self.screen.blit(rem_chars_render,
                         (config.SCREEN_W - 140, config.SCREEN_H - 100))

    def render_background(self):
        img_path = os.path.normpath(
            os.path.dirname(__file__) + "/../res/images/green_fabric.jpg")
        img = pygame.image.load(img_path)
        self.screen.fill(config.BLACK)
        for x in range(0, config.SCREEN_W, img.get_rect().width):
            for y in range(0, config.SCREEN_H, img.get_rect().height):
                self.screen.blit(img, (x, y))
Ejemplo n.º 20
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)