コード例 #1
0
    def setup():
        test_board = FishBoard(4, 3)
        player_1 = Player("red", 0, 0, [])
        player_2 = Player("white", 0, 0, [])
        test_players = [player_1, player_2]

        state = FishGameState(board=test_board,
                              num_players=len(test_players),
                              players=test_players,
                              current_player=player_1,
                              phase=GameStatePhase.INITIAL)

        return state
コード例 #2
0
def main():
    player1 = Player('first', 'Z')
    player2 = Player('second', 'O')
    game = Game(player1=player1, player2=player2)
    game.check(row=1, column=2)
    game.check(row=2, column=2)
    game.check(row=1, column=1)
    game.check(row=2, column=1)
    game.check(row=1, column=3)
    game.check(row=2, column=3)
    print(game)
    # print(game.current_player)
    # game.check()
    print(game.is_end())
コード例 #3
0
def Demo2(n_fish_per_tile=1) -> FishGameController:
    """
    Randomly Generated Holes with 1 Fish on all tiles...
    :return: A controller to be execute in main.
    """

    hexgrid = HexGridView()
    board = FishBoard(num_rows=HEXES_ROWS, num_cols=HEXES_COLUMNS)

    for row in range(board.num_rows):
        for col in range(board.num_cols):
            if board.retrieve_num_fish(
                    row=row, col=col) == 0 and board.is_tile(row=row, col=col):
                for i in range(n_fish_per_tile):
                    board = board.add_fish(row=row, col=col)
    player_1 = Player("red", 0, 0, [])
    state = FishGameState(board=board,
                          num_players=1,
                          players=[player_1],
                          current_player=player_1)
    behavior = FishGameBehavior(state=state, view=hexgrid)

    controller = FishGameController(behavior=behavior,
                                    view=hexgrid,
                                    state=state)

    return controller
コード例 #4
0
 def test_create_game_state(self):
     board = FishBoard(4, 3)
     game_state = FishGameState(board,
                                3,
                                TestGameState.player_list,
                                current_player=TestGameState.player_list[0],
                                phase=GameStatePhase.INITIAL)
     expected_players = {
         "red": Player(color="red", age=0, score=0, penguins=[]),
         "white": Player(color="white", age=0, score=0, penguins=[]),
         "brown": Player(color="brown", age=0, score=0, penguins=[])
     }
     self.assertEqual(game_state.players, expected_players)
     self.assertEqual(game_state.board, board)
     self.assertEqual(game_state.num_players, 3)
     self.assertEqual(game_state.num_penguins, 3)
コード例 #5
0
    def add_penguin(self, row, col, color):
        """
        Add a penguin for the given player id at the given row and column
        :param row: the row of the tile to add penguin on
        :param col: the col of the tile to add penguin on
        :param color: the color who will be adding the penguin
        :return: Updated GameState with added penguin
        """

        is_valid, err = self.can_add_penguin(row=row, col=col, color=color)
        if not is_valid:
            raise ValueError(err)

        players = []
        for player in self.players.values():
            if player.color == color:
                players.append(
                    Player.from_dict(
                        {
                            **player._asdict(),
                            **{
                                "penguins":
                                player.penguins + [(Coordinate(row, col))]
                            }
                        }))
            else:
                players.append(player)

        return FishGameState(board=self.board,
                             num_players=self.num_players,
                             players=players,
                             current_player=self.next_player(),
                             phase=self.phase)
コード例 #6
0
def Demo3(holes: List[Coordinate]) -> FishGameController:
    """
    Given the list of holes, render a board with holes at those
    cooridates and 1 fish at every non-hole tile.
    :param holes: List of hole Coordinates
    :return: A controller to be execute in main.
    """

    hexgrid = HexGridView()
    board = FishBoard(num_rows=HEXES_ROWS, num_cols=HEXES_COLUMNS)

    for hole in holes:
        board = board.create_hole(row=hole.row, col=hole.col)

    for row in range(board.num_rows):
        for col in range(board.num_cols):
            if board.retrieve_num_fish(
                    row=row, col=col) == 0 and board.is_tile(row=row, col=col):
                board = board.add_fish(row=row, col=col)

    player_1 = Player("red", 0, 0, [])
    state = FishGameState(board=board,
                          num_players=1,
                          players=[player_1],
                          current_player=player_1)
    behavior = FishGameBehavior(state=state, view=hexgrid)

    controller = FishGameController(behavior=behavior,
                                    view=hexgrid,
                                    state=state)

    return controller
コード例 #7
0
def Demo1() -> FishGameController:
    """
    "Randomly Generate number of holes, hole locations, number of fish, and
    fish location.

    :return: A controller to be execute in main.
    """

    hexgrid = HexGridView()
    board = FishBoard(num_rows=HEXES_ROWS, num_cols=HEXES_COLUMNS)

    board = RandomlyGenerateHole(board)
    board = RandomlyGenerateFish(board)

    player_1 = Player("red", 0, 0, [])
    state = FishGameState(board=board,
                          num_players=1,
                          players=[player_1],
                          current_player=player_1)
    behavior = FishGameBehavior(state=state, view=hexgrid)

    controller = FishGameController(behavior=behavior,
                                    view=hexgrid,
                                    state=state)

    return controller
コード例 #8
0
    def setup():
        test_board = FishBoard(4, 3)
        player_1 = Player("red", 0, 0, [])
        player_2 = Player("white", 0, 0, [])
        test_players = [player_1, player_2]
        state = FishGameState(board=test_board,
                              num_players=len(test_players),
                              players=test_players,
                              current_player=player_1,
                              phase=GameStatePhase.INITIAL)
        state = state.add_penguin(0, 0, "red") \
            .add_penguin(0, 2, "white") \
            .add_penguin(0, 1, "red") \
            .add_penguin(1, 2, "white") \
            .add_penguin(2, 0, "red") \
            .add_penguin(2, 2, "white") \
            .add_penguin(3, 0, "red") \
            .add_penguin(3, 2, "white")

        return state.finalize(), state.board, state.players
コード例 #9
0
def Demo1() -> FishGameController:
    """
    Players in but no penguins has been selected. No Penguins Rendered.

    :return: A controller to be execute in main.
    """

    board, hexgrid = setup()
    player_1 = Player("red", 0, 0, [])
    player_2 = Player("white", 0, 0, [])
    player_3 = Player("brown", 0, 0, [])
    factory = FishGameStateFactory(board=board,
                                   players=[player_1, player_2, player_3])
    state = factory.build()
    behavior = FishGameBehavior(state=state, view=hexgrid)

    controller = FishGameController(behavior=behavior,
                                    view=hexgrid,
                                    state=state)

    return controller
コード例 #10
0
def Demo2() -> FishGameController:
    """
    Non-Hole board where players have each selected 1 penguin.
    :return: A controller to be execute in main.
    """

    board, hexgrid = setup(no_holes=True)

    player_1 = Player("red", 0, 0, [])
    player_2 = Player("white", 0, 0, [])
    factory = FishGameStateFactory(board=board, players=[player_1, player_2])
    factory = factory.add_penguin(row=0, col=0, color="red")
    factory = factory.add_penguin(row=1, col=1, color="white")
    state = factory.build()

    behavior = FishGameBehavior(state=state, view=hexgrid)

    controller = FishGameController(behavior=behavior,
                                    view=hexgrid,
                                    state=state)

    return controller
コード例 #11
0
 def new_game(self):
     inventory = Inventory(9, 3, self.crafts, self)
     try:  # стартовый набор
         inventory.add_item(self.objects["torch"].generate_item(20))
         inventory.add_item(self.objects["dirt"].generate_item(120))
     except Exception:
         pass
     self.gravity_force = 0
     self.chunk_controller = ChunkController(
         self.block_width + self.additional,
         self.block_height + self.additional, random.randint(0, 100000))
     self.light_controller = LightController(
         self.block_width + self.additional,
         self.block_height + self.additional)
     self.sun = GlobalRotatingObject(self.sun_tile)
     self.moon = GlobalRotatingObject(self.moon_tile, coff=1)
     val = self.chunk_controller.get_player_start_pos(
         100000
     )  # на 0 координате "стык" - мир на отрицательных координатах является отражением мира на положительных
     self.player = Player(100000, val - 2, inventory, self)
     self.player.make_model()
     self.pos_center = (int(self.player.x), int(self.player.y))
     self.started_time = time.time()
コード例 #12
0
 def gen_load(self, data):
     self.chunk_controller = ChunkController(
         self.block_width + self.additional,
         self.block_height + self.additional,
         data['chunk_controller']['seed'])
     self.chunk_controller.load(data['chunk_controller'], self.objects)
     self.started_time = time.time() - data['started_time']
     if not self.is_server:
         inv = data['player']['inventory']
         inventory = Inventory(inv['x_size'], inv['y_size'], self.crafts,
                               self)
         inventory.get_sync(inv, self.objects)
         inventory.craft_model.craft_make_handle(None, None, None, None)
         self.player = Player(data['player']['x'], data['player']['y'],
                              inventory, self)
         self.player.make_model()
         self.status = IN_GAME
         self.light_controller = LightController(
             self.block_width + self.additional,
             self.block_height + self.additional)
         self.sun = GlobalRotatingObject(self.sun_tile)
         self.moon = GlobalRotatingObject(self.moon_tile, coff=1)
         self.pos_center = (int(self.player.x), int(self.player.y))
         self.gravity_force = 0
コード例 #13
0
 def kick_player(self, player_to_kick: Player):
     updated_players = [
         Player.from_dict({
             **player._asdict(),
             **{
                 "penguins": []
             }
         }) if player.color == player_to_kick.color else player
         for player in self.players.values()
     ]
     return FishGameState(board=self.board,
                          num_players=self.num_players,
                          players=updated_players,
                          current_player=self.next_player(),
                          phase=self.phase)
コード例 #14
0
 def connect(self, ip, port, name):
     try:
         self.new_game()
         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self.socket.connect((ip, int(port)))
         self.connected = True
         self.send({'request': JOIN_REQUEST, 'data': {'name': name}})
         response = self.receive()
         if not response['success']:  # запрос наверное может быть отклонен
             self.server_leave()
             self.interface.server_message.generate_text(
                 response['reason'], self.text_font, self.width // 20)
             self.interface.status = CONNECT_ERROR
             return
         self.chunk_controller = ChunkController(
             self.block_width + self.additional,
             self.block_height + self.additional,
             response['level']['chunk_controller']['seed'])
         self.chunk_controller.load(
             response['level']['chunk_controller'],
             self.objects)  # загрузка соответствующего мира
         self.player.x = response['player_data']['x']
         self.player.y = response['player_data']['y']
         self.player.inventory.get_sync(
             response['player_data']['inventory'], self.objects)
         self.started_time = time.time() - response['started_time']
         for i in response['players']:
             self.other_players[i['id']] = {
                 'name':
                 i['name'],
                 'model':
                 Player(i['x'], i['y'], None, self),
                 'selected':
                 self.objects[i['selected']].generate_item(1)
                 if i['selected'] != 'None' else None
             }
             self.other_players[i['id']]['model'].make_model()
             self.other_players[i['id']]['model'].update_model(
                 self.player.x, self.player.y)
         self.data_handler = threading.Thread(target=self.data_receive)
         self.data_handler.start()
         self.start()
     except socket.error as e:
         self.server_leave()
         self.interface.server_message.generate_text(
             str(e), self.text_font, self.width // 20)
         self.interface.status = CONNECT_ERROR
コード例 #15
0
    def test_create_choose_action_base_case_no_children_for_non_maximizing_player(
            self):
        """
        If the player has turns > 0 but has no children - end-game - and this is not
        the maximizing player, then we return None.
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        production_tree.get_children = MagicMock(return_value=[])
        production_tree.previous_action = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        expected = None
        _, actual = GenericStrategyComponent.minimax(tree=production_tree,
                                                     num_rounds=2,
                                                     maximizing_player=Player(
                                                         "white", 0, 0, []))
        self.assertEqual(expected, actual)
コード例 #16
0
    def test_create_choose_action_recursive_case_is_maximizing_twice(self):
        """
        In a recursive case where 1 round is played and the
        all leave nodes are the maximizing players, the minimum of
        these leave nodes is return.
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        expected = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        children = [child for child in production_tree.get_children()]
        for idx, child in enumerate(children):
            second_players_children = [
                second_players_child
                for second_players_child in child.get_children()
            ]
            for jdx, second_players_child in enumerate(
                    second_players_children):
                if jdx == 1 and idx == 1:
                    score = 0
                    child.previous_action = expected
                else:
                    score = 10
                modified_player = Player.from_dict({
                    **second_players_child.state.current_player._asdict(),
                    **{
                        'score': score
                    }
                })
                second_players_child.state.current_player = modified_player
                second_players_child.get_children = MagicMock(return_value=[])
                second_players_child.state.check_any_player_can_move = MagicMock(
                    return_value=False)

        production_tree.get_children = MagicMock(return_value=children)
        actual = GenericStrategyComponent.choose_action(tree=production_tree,
                                                        num_turns=2)
        self.assertEqual(expected, actual)
コード例 #17
0
    def data_handle(self, data):  # обрабатывает данные с сервера
        body = data['data']
        if data['request'] == PING:
            model = self.other_players[body['id']]['model']
            self.lerp_query[body['id']] = (model.x, model.y, body['x'],
                                           body['y'], time.time())
            model.x = body['x']
            model.y = body['y']
            model.update_model(self.player.x, self.player.y)
            model.model.direction = body['dir']
            model.moving = body['moving']
            self.other_players[body['id']]['selected'] = self.objects[
                body['selected']].generate_item(
                    1) if body['selected'] != 'None' else None

        elif data['request'] == JOIN:
            self.other_players[body['id']] = {
                'name':
                body['name'],
                'model':
                Player(body['x'], body['y'], None, self),
                'selected': (self.objects[body['selected']].generate_item(1)
                             if body['selected'] != 'None' else None)
            }

            self.other_players[body['id']]['model'].make_model()
            self.other_players[body['id']]['model'].update_model(
                self.player.x, self.player.y)
        elif data['request'] == BLOCK_UPDATE:
            new = self.chunk_controller.load_block(body['block'], self.objects)
            self.chunk_controller.place_block(body['x'], body['y'], new, self)
        elif data['request'] == LEAVE:
            self.other_players.pop(body['id'])
            self.lerp_query.pop(body['id'])
        elif data['request'] == SYNC_BLOCK:
            self.chunk_controller.get(body['x'], body['y'], self.objects,
                                      self).get_sync(body['block'])
コード例 #18
0
    def move_penguin(self, color, start_row, start_col, end_row, end_col):
        """
        Move penguin for player with color from start position to end position if possible.
        :param color: the color of the player who wants to move the penguin
        :param start_row: the current row of the penguin to be moved
        :param start_col: the current col of the penguin to be moved
        :param end_row: the desired row to which the penguin will be moved
        :param end_col: the desired col to which the penguin will be moved
        :return: a new FishGameState with the action taken.
        """

        is_valid, err = self.can_move_penguin(color, start_row, start_col,
                                              end_row, end_col)
        if not is_valid:
            raise ValueError(err)

        players = [
            Player.from_dict({
                **self.players[color]._asdict(),
                **{
                    "score": (self.players[color].score + self.board.retrieve_num_fish(
                                  start_row, start_col))
                },
                **{
                    "penguins": [
                        Coordinate(end_row, end_col) if penguin.row == start_row and penguin.col == start_col else penguin for penguin in self.players[color].penguins
                    ]
                }
            }) if player.color == color else player
            for player in self.players.values()
        ]
        board = self.board.create_hole(start_row, start_col)
        return FishGameState(board=board,
                             num_players=self.num_players,
                             players=players,
                             current_player=self.next_player(),
                             phase=self.phase)
コード例 #19
0
    def __init__(self, players: List[PlayerComponent], num_rows: int,
                 num_cols: int):
        """
        A Referee handles the gameplay of a Fish game. A gameplay consists of
        requesting players for penguin placements, the players' movements, kicking players
        for invalid moves, and notify players of notable events - kicking, endgame, and winners.
        A Referee is provided a list of players and the board dimensions from the tournament manager.
        """
        board = FishBoard(num_rows=num_rows, num_cols=num_cols)
        state_players = [
            Player(color=color, age=0, score=0, penguins=[])
            for color in PENGUIN_COLORS[:len(players)]
        ]

        self.players = {
            player.color: component
            for component, player in zip(players, state_players)
        }

        self.state = FishGameState(board=board,
                                   num_players=len(players),
                                   players=state_players,
                                   current_player=state_players[0],
                                   phase=GameStatePhase.INITIAL)
コード例 #20
0
class Game:
    def __init__(self):
        self.light_coff = 1

        self.status = IN_GAME

        self.objects = {}
        self.crafts = []

        self.phys_blocks_group = pygame.sprite.Group()
        self.all_blocks = pygame.sprite.Group()

        self.import_objects()

        self.player = None
        self.other_players = {}
        self.lerp_query = {}
        self.data_stack = deque()
        self.stack_lock = threading.Lock()
        self.sun = None
        self.moon = None
        self.pos_center = (0, 0)
        self.gravity_force = 0
        self.chunk_controller = None
        self.light_controller = None
        self.last_target = None
        self.interface = None
        self.running = False
        self.socket = None
        self.is_server = False
        self.connected = False
        self.data_handler = None

        self.block_size = 0
        self.block_height = 0
        self.block_width = 0
        self.additional = 0
        self.started_time = time.time()

    def import_objects(
            self):  # сериализация всех json обьектов и загрузка py классов
        for i in os.listdir(os.path.join(application_path, 'objects')):
            if os.path.splitext(i)[1] != '.json':
                continue
            st = open(os.path.join(application_path, 'objects', i), 'r')
            object = json.load(st)
            self.objects[object['name']] = ObjectData(
                type=object['name'],
                game=self,
                path=object['path'],
                tile_image=TileImage(None, None, None),
                block_class_path=object['class'],
                item_class_path=object['items_class'],
                block_class=custom_import(object['class'] + '.Object'),
                item_class=custom_import(object['items_class'] + '.Object'),
                is_phys=object['is_phys'],
                is_item=object['is_item'],
                transparent=object['transparent'],
                hp=object['hp'],
                lighting=object['lighting'],
                drop=BlockDrop(
                    *object['drop']) if len(object['drop']) else None)
        for i in os.listdir(application_path + '/' + 'recipes'):
            if os.path.splitext(i)[1] != '.json':
                continue
            st = open(os.path.join(application_path, 'recipes/', i), 'r')
            recipe = json.load(st)
            self.crafts.append((recipe['recipe'], recipe['result']))

    def initialise(self,
                   width=BASE_BLOCK_WIDTH,
                   height=BASE_BLOCK_HEIGHT,
                   block_size=BASE_BLOCK_SIZE):  # загрузка всего и всякого
        self.screen = pygame.display.set_mode(
            (width * block_size, height * block_size),
            pygame.OPENGL | pygame.DOUBLEBUF)
        glLoadIdentity()
        glMatrixMode(GL_PROJECTION)
        gluOrtho2D(0, width * block_size, height * block_size, 0)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glEnable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        self.block_size = block_size
        self.block_width = width
        self.block_height = height

        self.width = self.block_size * self.block_width
        self.height = self.block_size * self.block_height

        self.x_center = self.width // 2
        self.y_center = self.height // 2

        self.block_x_center = self.width // self.block_size
        self.block_y_center = self.height // self.block_size

        self.additional = 20  # дополнительное ко-во блоков для счета физики и освещения
        # считается, что этого достаточно чтобы не делать слишком частую подгрузку и при этом подгрузка не была тяжелой

        for i in self.objects.values():
            if i.tile_image is not None and i.tile_image.gl_image is not None:
                glDeleteTextures(1, i.tile_image.gl_image)
            i.tile_image = TileImage(load_image(i.path), self.block_size,
                                     self.block_size)
        self.text_font = pygame.font.Font(
            os.path.join(application_path, 'lobster.ttf'),
            min(self.block_width * self.block_size // 25,
                self.block_height * self.block_size // 25))
        self.destroy_images = [
            createTexDL(
                make_gl_image(
                    pygame.transform.scale(
                        load_image("destroy_stage_" + str(i) + ".png"),
                        (self.block_size, self.block_size)))[0],
                self.block_size, self.block_size) for i in range(10)
        ]
        self.sun_tile = TileImage(load_image('sun.png'), self.block_size,
                                  self.block_size)
        self.moon_tile = TileImage(load_image('moon.png'), self.block_size,
                                   self.block_size)
        self.interface = GameInterface(self)

    def get_view_start(self):  # начальные координаты обзора игрока
        y, x = int(-(self.pos_center[1] - self.player.y) *
                   self.block_size), int(
                       -(self.pos_center[0] - self.player.x) * self.block_size)
        startx, starty = x // self.block_size + self.additional // 2, y // self.block_size + self.additional // 2
        return startx, starty

    def gen_load(self, data):
        self.chunk_controller = ChunkController(
            self.block_width + self.additional,
            self.block_height + self.additional,
            data['chunk_controller']['seed'])
        self.chunk_controller.load(data['chunk_controller'], self.objects)
        self.started_time = time.time() - data['started_time']
        if not self.is_server:
            inv = data['player']['inventory']
            inventory = Inventory(inv['x_size'], inv['y_size'], self.crafts,
                                  self)
            inventory.get_sync(inv, self.objects)
            inventory.craft_model.craft_make_handle(None, None, None, None)
            self.player = Player(data['player']['x'], data['player']['y'],
                                 inventory, self)
            self.player.make_model()
            self.status = IN_GAME
            self.light_controller = LightController(
                self.block_width + self.additional,
                self.block_height + self.additional)
            self.sun = GlobalRotatingObject(self.sun_tile)
            self.moon = GlobalRotatingObject(self.moon_tile, coff=1)
            self.pos_center = (int(self.player.x), int(self.player.y))
            self.gravity_force = 0

    def load(
        self,
        path='save.txt'
    ):  # загрузка всех тех jsonов от всех всех объектов - в будующем это будет сделано попроще...
        data = json.loads(
            open(os.path.join(application_path, path), 'r').read())
        self.gen_load(data)

    def server_leave(self):
        if not self.connected:
            return
        self.connected = False
        self.data_handler = None
        try:
            self.send({'request': LEAVE_REQUEST, 'data': {}})
            self.socket.close()
        except Exception:
            pass
        finally:
            self.socket = None
        self.other_players = {}
        self.data_stack = deque()
        self.lerp_query = {}
        self.status = MENU

    def send(self, data):
        prep = bytes(json.dumps(data), encoding='ascii')
        self.socket.send(len(prep).to_bytes(
            16, 'big'))  # сначала отправляется длина пакета
        self.socket.send(prep)  # сам пакет

    def receive(self):
        ln = int.from_bytes(self.socket.recv(16), 'big')
        data = json.loads(self.socket.recv(ln))
        return data

    def connect(self, ip, port, name):
        try:
            self.new_game()
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket.connect((ip, int(port)))
            self.connected = True
            self.send({'request': JOIN_REQUEST, 'data': {'name': name}})
            response = self.receive()
            if not response['success']:  # запрос наверное может быть отклонен
                self.server_leave()
                self.interface.server_message.generate_text(
                    response['reason'], self.text_font, self.width // 20)
                self.interface.status = CONNECT_ERROR
                return
            self.chunk_controller = ChunkController(
                self.block_width + self.additional,
                self.block_height + self.additional,
                response['level']['chunk_controller']['seed'])
            self.chunk_controller.load(
                response['level']['chunk_controller'],
                self.objects)  # загрузка соответствующего мира
            self.player.x = response['player_data']['x']
            self.player.y = response['player_data']['y']
            self.player.inventory.get_sync(
                response['player_data']['inventory'], self.objects)
            self.started_time = time.time() - response['started_time']
            for i in response['players']:
                self.other_players[i['id']] = {
                    'name':
                    i['name'],
                    'model':
                    Player(i['x'], i['y'], None, self),
                    'selected':
                    self.objects[i['selected']].generate_item(1)
                    if i['selected'] != 'None' else None
                }
                self.other_players[i['id']]['model'].make_model()
                self.other_players[i['id']]['model'].update_model(
                    self.player.x, self.player.y)
            self.data_handler = threading.Thread(target=self.data_receive)
            self.data_handler.start()
            self.start()
        except socket.error as e:
            self.server_leave()
            self.interface.server_message.generate_text(
                str(e), self.text_font, self.width // 20)
            self.interface.status = CONNECT_ERROR

    def new_game(self):
        inventory = Inventory(9, 3, self.crafts, self)
        try:  # стартовый набор
            inventory.add_item(self.objects["torch"].generate_item(20))
            inventory.add_item(self.objects["dirt"].generate_item(120))
        except Exception:
            pass
        self.gravity_force = 0
        self.chunk_controller = ChunkController(
            self.block_width + self.additional,
            self.block_height + self.additional, random.randint(0, 100000))
        self.light_controller = LightController(
            self.block_width + self.additional,
            self.block_height + self.additional)
        self.sun = GlobalRotatingObject(self.sun_tile)
        self.moon = GlobalRotatingObject(self.moon_tile, coff=1)
        val = self.chunk_controller.get_player_start_pos(
            100000
        )  # на 0 координате "стык" - мир на отрицательных координатах является отражением мира на положительных
        self.player = Player(100000, val - 2, inventory, self)
        self.player.make_model()
        self.pos_center = (int(self.player.x), int(self.player.y))
        self.started_time = time.time()

    def start(self):
        self.status = IN_GAME
        self.redraw_all()

    def gen_save(
        self
    ):  # там много вызовов save у разных обьектов и все это в один json схлопывается
        data = {}
        data['chunk_controller'] = self.chunk_controller.save()
        if self.player is not None:
            data['player'] = self.player.save()
        data['started_time'] = time.time() - self.started_time
        return data

    def save(self, path='save.txt'):
        out = open(os.path.join(application_path, path), 'w')
        out.write(json.dumps(self.gen_save()))
        out.close()

    def update_changes(self, was, block):  # обновляет группы
        if was.is_phys:
            self.phys_blocks_group.remove(was)
        if block.is_phys:
            self.phys_blocks_group.add(block)
        self.all_blocks.remove(was)
        self.all_blocks.add(block)
        if block.worldpos[0] not in self.chunk_controller.changes:
            self.chunk_controller.changes[block.worldpos[0]] = {}
        self.chunk_controller.changes[block.worldpos[0]][
            block.worldpos[1]] = block

    def place_block(self, x, y, player):
        block = self.chunk_controller.chunk[y][x]
        if block.type != 'empty' or player.inventory.get_selected(
        ) is None:  # если уже занято
            return
        if player.inventory.get_selected().is_item:  # если не блок
            return
        if pygame.sprite.collide_rect(
                block, player.model.get_current_tile()):  # если в персонаже
            return
        new = self.objects[
            player.inventory.get_selected().type].generate_block(
                x, y, block.worldpos)
        self.update_changes(block, new)
        self.chunk_controller.chunk[y][x] = new
        self.chunk_controller.chunk[y][x].on_place(self)
        player.inventory.place_selected()
        if not new.transparent:
            self.light_controller.max_y[x] = min(
                self.light_controller.max_y[x], new.worldpos[1])
        elif not block.transparent:
            self.light_controller.calc_max_y_for_x(new.x, self.pos_center,
                                                   self.chunk_controller,
                                                   self.objects)
        self.light_controller.calculate_light(
            self.chunk_controller.chunk, self.chunk_controller.ground_height)
        if self.connected:
            self.send({
                'request': PLACE_BLOCK,
                'data': {
                    'x': new.worldpos[0],
                    'y': new.worldpos[1]
                }
            })

    def draw_single_light(self, block):  # рисует свет на блоке
        if not block.transparent:
            color = (0, 0, 0,
                     1 - self.light_controller.lighting[block.y][block.x] *
                     self.light_coff)
        else:  # фон остается немного видимым (упрощает навигацию в шахте)
            color = (0, 0, 0,
                     min(
                         0.95,
                         1 - self.light_controller.lighting[block.y][block.x] *
                         self.light_coff))
        glColor4f(*color)
        glRectf(0, 0, block.rect.width, block.rect.height)
        glColor4f(1, 1, 1, 1)

    def draw_all_light(self):
        glBindTexture(GL_TEXTURE_2D, 0)
        startx, starty = GAME.get_view_start(
        )  # определяет область от общей области, которая видна игроку
        for g in range(startx - 1, startx + self.block_width + 1):
            for k in range(starty - 1, starty + self.block_height + 1):
                i = GAME.chunk_controller.chunk[k][g]
                glLoadIdentity()
                glTranslate(i.rect.x, i.rect.y, 0)
                self.draw_single_light(i)

    def destroy_block(self, x, y, player):
        block = self.chunk_controller.chunk[y][x]
        block.on_destroy(self)
        new = self.objects['empty'].generate_block(x, y, block.worldpos)
        self.update_changes(block, new)
        self.last_target = None
        if block.drop is not None:
            drop = block.drop.get_drop()
            player.inventory.add_item(self.objects[drop[0]].generate_item(
                drop[1]))
        self.chunk_controller.chunk[y][x] = new
        self.light_controller.calc_max_y_for_x(new.x, self.pos_center,
                                               self.chunk_controller,
                                               self.objects)
        self.light_controller.calculate_light(
            self.chunk_controller.chunk, self.chunk_controller.ground_height)
        if self.connected:
            self.send({
                'request': DESTROY_BLOCK,
                'data': {
                    'x': new.worldpos[0],
                    'y': new.worldpos[1]
                }
            })

    def damage_block(self, x, y, dmg, player):
        block = self.chunk_controller.chunk[y][x]
        if block.type == 'empty':
            return
        if self.last_target is not None and self.last_target[
                0] is not None and self.last_target[1] != block.worldpos:
            self.last_target[0].hp = self.objects[self.last_target[0].type].hp
        if block.damage(dmg):  # если сломал
            self.destroy_block(x, y, player)
        else:
            self.last_target = (block, block.worldpos, block.hp)

    def exit(self):
        self.server_leave()
        self.running = False

    def redraw_all(self):  # обновляет ВСЕ
        self.all_blocks.remove(self.all_blocks)
        self.phys_blocks_group.remove(self.phys_blocks_group)
        self.chunk_controller.update_chunk(self.pos_center[0],
                                           self.pos_center[1], self.objects)
        for i in self.chunk_controller.chunk:
            for g in i:
                self.all_blocks.add(g)
                if not g.is_phys:
                    continue
                self.phys_blocks_group.add(g)
        self.all_blocks.update(self.block_width, GAME.block_height,
                               GAME.pos_center, GAME.block_size, GAME.player.x,
                               self.player.y)
        self.light_controller.calc_max_y(self.pos_center,
                                         self.chunk_controller, self.objects)
        self.light_controller.calculate_light(
            self.chunk_controller.chunk, self.chunk_controller.ground_height)

    def draw_blocks_in_screen(self, draw_order):
        startx, starty = GAME.get_view_start(
        )  # определяет область от общей области, которая видна игроку
        for g in range(startx - 1, startx + self.block_width + 1):
            for k in range(starty - 1, starty + self.block_height + 1):
                i = GAME.chunk_controller.chunk[k][g]
                if draw_order == 1:
                    if not (i.type == 'empty' or i.transparent):
                        continue
                elif draw_order == 2:
                    if i.type == 'empty':
                        continue
                i.update(self.block_width, self.block_height, self.pos_center,
                         self.block_size, self.player.x, self.player.y)
                glLoadIdentity()
                glTranslate(i.rect.x, i.rect.y, 0)
                if i.transparent and draw_order != 2:
                    glCallList(self.objects['empty'].tile_image.gl_image)
                if not (i.transparent and draw_order == 1):
                    i.draw()
                #self.draw_single_light(i)
                if i.type != 'empty' and i.hp != self.objects[i.type].hp:
                    glCallList(self.destroy_images[int(
                        min(9,
                            max(0,
                                (1 - i.hp / self.objects[i.type].hp)) / 0.1))])

    def update_mouse(self, pos, events, fps):
        y, x = int(pos[1] - (self.pos_center[1] - self.player.y) *
                   self.block_size), int(pos[0] -
                                         (self.pos_center[0] - self.player.x) *
                                         self.block_size)
        x, y = x // self.block_size + self.additional // 2, y // self.block_size + self.additional // 2
        if int(
                math.sqrt(  # проверка расстояние от персонажа
                    abs(x - (self.block_width + self.additional) // 2 +
                        int(self.pos_center[0] - self.player.x))**2 +
                    abs(y - (self.block_height + self.additional) // 2 +
                        int(self.pos_center[1] - self.player.y) - 1)**2)) > 4:
            return
        block = GAME.chunk_controller.chunk[y][x]
        glBindTexture(GL_TEXTURE_2D, 0)
        glLoadIdentity()
        glTranslate(block.rect.x, block.rect.y, 0)
        glBegin(GL_LINE_LOOP)
        glVertex2f(0, 0)
        glVertex2f(block.rect.width, 0)
        glVertex2f(block.rect.width, block.rect.height)
        glVertex2f(0, block.rect.height)
        glEnd()
        if events[0]:
            self.damage_block(x, y, self.player.mine_speed / fps, self.player)
        elif events[2]:
            block.on_use(self)
            if self.player.inventory.get_selected() is not None:
                self.place_block(x, y, self.player)

    def update_inputs(self, fps):
        all_keys = pygame.key.get_pressed()  # проверка ввода
        key_inp = all_keys[273:277]  # стрелки
        alt_key_inp = [
            all_keys[119], all_keys[115], all_keys[100], all_keys[97]
        ]  # wasd
        move_vec = (0, 0)
        if self.status == IN_GAME:
            for i in range(4):
                move_vec = (move_vec[0] + dirs[i][0] * self.player.speed *
                            max(alt_key_inp[i], key_inp[i]), move_vec[1] +
                            dirs[i][1] * max(alt_key_inp[i], key_inp[i]))
        bf = self.player.model.get_current_tile()
        if check_square_collision(
                bf.rect.x + 3, bf.rect.y + bf.rect.height,
                bf.rect.x + bf.rect.width - 3, bf.rect.y + bf.rect.height + 1,
                self.phys_blocks_group):  # прыжки если персонаж стоит
            if move_vec[1] < 0 <= self.gravity_force:
                self.gravity_force = -(GRAV_CONST * 12.5)
            else:
                self.gravity_force = 0
        else:
            self.gravity_force += GRAV_CONST  # ускорение
        if move_vec[0] == 0 and move_vec[1] > 0:
            self.player.model.direction = 0
        self.player.move(move_vec[0] / fps, self.gravity_force, 300 / fps)
        if move_vec[0] != 0:
            self.player.moving = True
        else:
            self.player.moving = False
        self.light_coff = self.sun.update_pos(self.started_time, self.x_center,
                                              self.y_center, self.width,
                                              self.height)
        self.moon.update_pos(self.started_time, self.x_center, self.y_center,
                             self.width, self.height)

    def redraw(self):
        xoffset, yoffset = self.player.x - self.pos_center[
            0], self.player.y - self.pos_center[1]
        if abs(xoffset) >= self.additional // 2 or abs(
                yoffset) >= self.additional // 2:  # подгрузка
            self.pos_center = (int(self.player.x), int(self.player.y))
            self.redraw_all()
        self.draw_blocks_in_screen(1)  # чтобы солнце под землей не рисовалось
        self.sun.draw()
        self.moon.draw()
        self.draw_blocks_in_screen(2)

        for i in self.other_players.values():
            if i['model'].moving:
                i['model'].simulate_move(i['model'].model.direction, 300 / fps)
            else:
                i['model'].simulate_move(0, 300 / fps)
            i['model'].update_model(self.player.x, self.player.y)
            i['model'].model.draw(i['selected'], self.block_size)
            glLoadIdentity()
            bf = i['model'].model.get_current_tile().rect
            glTranslate(bf.x + bf.width // 2, bf.y - self.block_size // 2, 0)
            draw_text(i['name'], self.text_font, color=(225, 225, 225, 125))
        self.player.draw()
        self.draw_all_light()

    def data_receive(
        self
    ):  # получает данные с сервера в отдельном потоке (чтобы основной не ложить)
        try:
            while True:
                data = self.receive()
                if not data or not self.connected:
                    return
                self.stack_lock.acquire()
                try:
                    self.data_stack.append(data)
                finally:
                    self.stack_lock.release()
        except socket.error as e:
            self.interface.server_message.generate_text(
                str(e), self.text_font, self.width // 20)
            self.server_leave()
            self.interface.status = CONNECT_ERROR
            return
        except Exception:
            pass  # жсоны выбивает если сокет умирает

    def data_handle(self, data):  # обрабатывает данные с сервера
        body = data['data']
        if data['request'] == PING:
            model = self.other_players[body['id']]['model']
            self.lerp_query[body['id']] = (model.x, model.y, body['x'],
                                           body['y'], time.time())
            model.x = body['x']
            model.y = body['y']
            model.update_model(self.player.x, self.player.y)
            model.model.direction = body['dir']
            model.moving = body['moving']
            self.other_players[body['id']]['selected'] = self.objects[
                body['selected']].generate_item(
                    1) if body['selected'] != 'None' else None

        elif data['request'] == JOIN:
            self.other_players[body['id']] = {
                'name':
                body['name'],
                'model':
                Player(body['x'], body['y'], None, self),
                'selected': (self.objects[body['selected']].generate_item(1)
                             if body['selected'] != 'None' else None)
            }

            self.other_players[body['id']]['model'].make_model()
            self.other_players[body['id']]['model'].update_model(
                self.player.x, self.player.y)
        elif data['request'] == BLOCK_UPDATE:
            new = self.chunk_controller.load_block(body['block'], self.objects)
            self.chunk_controller.place_block(body['x'], body['y'], new, self)
        elif data['request'] == LEAVE:
            self.other_players.pop(body['id'])
            self.lerp_query.pop(body['id'])
        elif data['request'] == SYNC_BLOCK:
            self.chunk_controller.get(body['x'], body['y'], self.objects,
                                      self).get_sync(body['block'])

    def run(self):
        pygame.init()
        pygame.font.init()
        clock = pygame.time.Clock()

        self.running = True
        self.status = MENU
        self.initialise()
        self.started_time = time.time()
        last_send = time.time()
        while self.running:
            try:
                if self.connected:
                    if time.time() - last_send > PING_TIME:  # пинг на сервер
                        self.send({
                            'request': PING,
                            'data': {
                                'x': self.player.x,
                                'y': self.player.y,
                                'dir': self.player.model.direction,
                                'moving': self.player.moving,
                                'selected': self.player.inventory.selected[0]
                            }
                        })
                        last_send = time.time()
                self.stack_lock.acquire()
                try:  # обрабатываю полученные данные с сервера
                    while len(self.data_stack):
                        data = self.data_stack.popleft()
                        self.data_handle(data)
                finally:
                    self.stack_lock.release()
                for i in self.lerp_query.items(
                ):  # интерполирую координаты других игроков
                    delta = time.time() - i[1][-1]
                    x1, y1 = i[1][0], i[1][1]
                    x2, y2 = i[1][2], i[1][3]
                    #self.other_players[i[0]]['model'].x = x1 + (1 - math.cos(4 * delta * math.pi)) * (x2 - x1)
                    #self.other_players[i[0]]['model'].y = y1 + (1 - math.cos(4 * delta * math.pi)) * (y2 - y1)
                    self.other_players[
                        i[0]]['model'].x = x1 + 10 * delta * (x2 - x1)
                    self.other_players[
                        i[0]]['model'].y = y1 + 10 * delta * (y2 - y1)
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        self.exit()
                    if event.type == pygame.KEYDOWN:
                        if event.key == pygame.K_ESCAPE and self.status in [
                                IN_GAME, PAUSE
                        ]:
                            self.status = PAUSE if self.status == IN_GAME else IN_GAME
                        elif event.key == pygame.K_e and self.status not in [
                                MENU, PAUSE, SETTINGS, CONNECT
                        ]:
                            self.status = INVENTORY if self.status == IN_GAME else IN_GAME
                    if event.type == pygame.MOUSEBUTTONDOWN and GAME.status == IN_GAME:
                        self.player.inventory.wheel_event(
                            -1 if event.button == 4 else (
                                1 if event.button == 5 else 0))
                    self.interface.handle_event(event)
                if self.status not in [MENU, SETTINGS, CONNECT]:
                    self.update_inputs(fps)
                if self.status != MENU:
                    self.redraw()
                    self.player.inventory.draw_bar_only()
                if self.status == IN_GAME:
                    self.update_mouse(pygame.mouse.get_pos(),
                                      pygame.mouse.get_pressed(), fps)
                if self.status != IN_GAME:
                    glLoadIdentity()
                    glBindTexture(GL_TEXTURE_2D, 0)
                    glColor4f(0.75, 0.75, 0.75, 0.75)
                    glRectf(0, 0, self.width, self.height)
                    glColor4f(1, 1, 1, 1)
                if self.status == INVENTORY:
                    self.player.inventory.draw()
                elif self.status == PAUSE:
                    self.interface.draw()
                elif self.status == MENU:
                    self.interface.draw()
                for i in self.all_blocks:
                    i.on_tick(self)
                pygame.display.flip()
                clock.tick(fps)
            except socket.error as e:
                self.interface.server_message.generate_text(
                    str(e), self.text_font, self.width // 20)
                self.server_leave()
                self.interface.status = CONNECT_ERROR
            except Exception as e:
                print(e)
                self.server_leave()
                self.running = False
        pygame.quit()
コード例 #21
0
class TestGameState(unittest.TestCase):
    currentResult = None  # holds last result object passed to run method
    player_1 = Player("red", 0, 0, [])
    player_2 = Player("white", 0, 0, [])
    player_3 = Player("brown", 0, 0, [])
    player_list = [player_1, player_2, player_3]

    @classmethod
    def setResult(cls, amount, errors, failures, skipped):
        cls.amount, cls.errors, cls.failures, cls.skipped = \
            amount, errors, failures, skipped

    def tearDown(self):
        amount = self.currentResult.testsRun
        errors = self.currentResult.errors
        failures = self.currentResult.failures
        skipped = self.currentResult.skipped
        self.setResult(amount, errors, failures, skipped)

    @classmethod
    def tearDownClass(cls):
        print("\ntests run: " + str(cls.amount))
        print("errors: " + str(len(cls.errors)))
        print("failures: " + str(len(cls.failures)))
        print("success: " +
              str(cls.amount - len(cls.errors) - len(cls.failures)))
        print("skipped: " + str(len(cls.skipped)))

    def run(self, result=None):
        self.currentResult = result  # remember result for use in tearDown
        unittest.TestCase.run(self, result)  # call superclass run method

    def test_create_game_state(self):
        board = FishBoard(4, 3)
        game_state = FishGameState(board,
                                   3,
                                   TestGameState.player_list,
                                   current_player=TestGameState.player_list[0],
                                   phase=GameStatePhase.INITIAL)
        expected_players = {
            "red": Player(color="red", age=0, score=0, penguins=[]),
            "white": Player(color="white", age=0, score=0, penguins=[]),
            "brown": Player(color="brown", age=0, score=0, penguins=[])
        }
        self.assertEqual(game_state.players, expected_players)
        self.assertEqual(game_state.board, board)
        self.assertEqual(game_state.num_players, 3)
        self.assertEqual(game_state.num_penguins, 3)

    def test_validate_input_true(self):
        board = FishBoard(4, 3)
        game_state = FishGameState(board,
                                   3,
                                   TestGameState.player_list,
                                   current_player=TestGameState.player_list[0],
                                   phase=GameStatePhase.FINAL)
        is_valid, error_msg = game_state.validate_input("red", 0, 0)
        self.assertTrue(is_valid)
        self.assertEqual(error_msg, "")

    def test_validate_input_invalid_player_id(self):
        board = FishBoard(4, 3)
        game_state = FishGameState(board,
                                   3,
                                   TestGameState.player_list,
                                   current_player=TestGameState.player_list[0],
                                   phase=GameStatePhase.FINAL)
        is_valid, error_msg = game_state.validate_input(-1, 0, 0)
        self.assertFalse(is_valid)
        self.assertEqual(error_msg, "Player color does not exist")

    def test_validate_input_invalid_player_id_upperbound(self):
        board = FishBoard(4, 3)
        game_state = FishGameState(board,
                                   3,
                                   TestGameState.player_list,
                                   current_player=TestGameState.player_list[0],
                                   phase=GameStatePhase.FINAL)
        is_valid, error_msg = game_state.validate_input("", 0, 0)
        self.assertFalse(is_valid)
        self.assertEqual(error_msg, "Player color does not exist")

    def test_validate_input_invalid_row_and_col_out_of_bounds(self):
        board = FishBoard(4, 3)
        game_state = FishGameState(board,
                                   3,
                                   TestGameState.player_list,
                                   current_player=TestGameState.player_list[0],
                                   phase=GameStatePhase.FINAL)
        is_valid, error_msg = game_state.validate_input("red", 6, 6)
        self.assertFalse(is_valid)
        self.assertEqual(error_msg,
                         "Row and column do not exist in this board")

    def test_validate_input_invalid_row_and_col_has_hole(self):
        board = FishBoard(4, 3)
        board = board.create_hole(1, 1)
        game_state = FishGameState(board,
                                   3,
                                   TestGameState.player_list,
                                   current_player=TestGameState.player_list[0],
                                   phase=GameStatePhase.FINAL)
        is_valid, error_msg = game_state.validate_input("red", 1, 1)
        self.assertFalse(is_valid)
        self.assertEqual(error_msg, "Row and column is a hole")

    def test_validate_input_invalid_row_and_col_has_penguin(self):
        board = FishBoard(4, 3)
        factory = FishGameState(board=board,
                                num_players=2,
                                players=TestGameState.player_list,
                                phase=GameStatePhase.INITIAL,
                                current_player=TestGameState.player_list[0])
        factory = factory.add_penguin(2, 2, "red")
        is_valid, error_msg = factory.validate_input("white", 2, 2)
        self.assertFalse(is_valid)
        self.assertEqual(error_msg, "This tile already has a penguin")

    def test_add_penguin_success(self):
        board = FishBoard(4, 3)
        factory = FishGameState(board=board,
                                num_players=2,
                                players=TestGameState.player_list,
                                phase=GameStatePhase.INITIAL,
                                current_player=TestGameState.player_list[0])
        factory = factory.add_penguin(0, 0, "red")
        expected_penguins = [Coordinate(0, 0)]
        self.assertEqual(factory.players["red"].penguins, expected_penguins)

    def test_add_penguin_failure(self):
        board = FishBoard(4, 3)
        factory = FishGameState(board=board,
                                num_players=2,
                                players=TestGameState.player_list,
                                phase=GameStatePhase.INITIAL,
                                current_player=TestGameState.player_list[0])
        factory = factory.add_penguin(0, 0, "red")
        factory = factory.add_penguin(0, 1, "white")
        with self.assertRaises(ValueError):
            factory = factory.add_penguin(0, 2, "white")

    def test_move_penguin(self):
        board = FishBoard(4, 3)
        factory = FishGameState(board=board,
                                num_players=3,
                                players=TestGameState.player_list,
                                phase=GameStatePhase.INITIAL,
                                current_player=TestGameState.player_list[0])
        factory = factory.add_penguin(row=0, col=1, color="red")
        factory = factory.add_penguin(row=0, col=0, color="white")
        factory = factory.add_penguin(row=3, col=0, color="brown")

        game_state = factory.finalize()
        game_state = game_state.move_penguin("red", 0, 1, 2, 1)
        expected_penguins = [Coordinate(row=2, col=1)]
        self.assertEqual(game_state.players["red"].penguins, expected_penguins)

    def test_move_penguin_failure(self):
        board = FishBoard(4, 3)
        factory = FishGameState(board=board,
                                num_players=3,
                                players=TestGameState.player_list,
                                phase=GameStatePhase.INITIAL,
                                current_player=TestGameState.player_list[0])
        factory = factory.add_penguin(row=0, col=0, color="red")
        factory = factory.add_penguin(row=0, col=1, color="white")
        factory = factory.add_penguin(row=0, col=2, color="brown")
        game_state = factory.finalize()
        with self.assertRaises(ValueError):
            game_state.move_penguin("red", 0, 0, 0, 2)

    def test_has_penguin(self):
        board = FishBoard(4, 3)
        factory = FishGameState(board=board,
                                num_players=3,
                                players=TestGameState.player_list,
                                phase=GameStatePhase.INITIAL,
                                current_player=TestGameState.player_list[0])
        factory = factory.add_penguin(0, 0, "red")
        self.assertTrue(factory.has_penguin(0, 0))
        self.assertFalse(factory.has_penguin(1, 1))

    def test_any_player_can_move_success(self):
        board = FishBoard(4, 3)
        factory = FishGameState(board=board,
                                num_players=3,
                                players=TestGameState.player_list,
                                phase=GameStatePhase.INITIAL,
                                current_player=TestGameState.player_list[0])
        factory = factory.add_penguin(0, 0, "red")
        factory = factory.add_penguin(1, 1, "white")
        game_state = factory.finalize()
        self.assertTrue(game_state.check_any_player_can_move())

    def test_any_player_can_move_failure(self):
        board = FishBoard(4, 3)
        board = board.create_hole(2, 0)
        board = board.create_hole(3, 0)
        board = board.create_hole(2, 1)
        board = board.create_hole(0, 1)
        factory = FishGameState(board=board,
                                num_players=3,
                                players=TestGameState.player_list,
                                phase=GameStatePhase.INITIAL,
                                current_player=TestGameState.player_list[0])
        factory = factory.add_penguin(0, 0, "red")
        factory = factory.add_penguin(1, 0, "white")
        game_state = factory.finalize()
        self.assertFalse(game_state.check_any_player_can_move())

    def test_get_player_id_success(self):
        board = FishBoard(4, 3)
        board = board.create_hole(2, 0)
        board = board.create_hole(3, 0)
        board = board.create_hole(2, 1)
        board = board.create_hole(0, 1)
        factory = FishGameState(board=board,
                                num_players=3,
                                players=TestGameState.player_list,
                                phase=GameStatePhase.INITIAL,
                                current_player=TestGameState.player_list[0])
        factory = factory.add_penguin(0, 0, "red")
        factory = factory.add_penguin(1, 0, "white")

        game_state = factory.finalize()
        self.assertEqual(game_state.get_player_color(0, 0), "red")
        self.assertEqual(game_state.get_player_color(3, 2), "")

    def test_is_equal(self):
        board1 = FishBoard(4, 3)
        board1 = board1.create_hole(2, 0)
        board1 = board1.create_hole(3, 0)
        board1 = board1.create_hole(2, 1)
        board1 = board1.create_hole(0, 1)

        board2 = FishBoard(4, 3)
        board2 = board2.create_hole(2, 0)
        board2 = board2.create_hole(3, 0)
        board2 = board2.create_hole(2, 1)
        board2 = board2.create_hole(0, 1)

        factory1 = FishGameState(board=board1,
                                 num_players=3,
                                 players=TestGameState.player_list,
                                 phase=GameStatePhase.INITIAL,
                                 current_player=TestGameState.player_list[0])
        factory1 = factory1.add_penguin(0, 0, "red")
        factory1 = factory1.add_penguin(1, 0, "white")

        factory2 = FishGameState(board=board2,
                                 num_players=3,
                                 players=TestGameState.player_list,
                                 phase=GameStatePhase.INITIAL,
                                 current_player=TestGameState.player_list[0])
        factory2 = factory2.add_penguin(0, 0, "red")
        factory2 = factory2.add_penguin(1, 0, "white")

        state1 = factory1.finalize()
        state2 = factory2.finalize()

        self.assertTrue(state1.is_equal(state2))

    def test_is_equal_false_players_only(self):
        board1 = FishBoard(4, 3)
        board2 = FishBoard(4, 3)

        state1 = FishGameState(board=board1,
                               num_players=3,
                               players=TestGameState.player_list[0:2],
                               phase=GameStatePhase.INITIAL,
                               current_player=TestGameState.player_list[0])
        state2 = FishGameState(board=board2,
                               num_players=3,
                               players=TestGameState.player_list,
                               phase=GameStatePhase.INITIAL,
                               current_player=TestGameState.player_list[0])

        self.assertFalse(state1.is_equal(state2))

    def test_is_equal_false_board_only(self):
        board1 = FishBoard(5, 3)
        board2 = FishBoard(4, 3)

        state1 = FishGameState(board=board1,
                               num_players=3,
                               players=TestGameState.player_list[0:2],
                               phase=GameStatePhase.INITIAL,
                               current_player=TestGameState.player_list[0])
        state2 = FishGameState(board=board2,
                               num_players=3,
                               players=TestGameState.player_list,
                               phase=GameStatePhase.INITIAL,
                               current_player=TestGameState.player_list[0])

        self.assertFalse(state1.is_equal(state2))