def __init__(self,
              universe_id: int = UNIVERSE_NUMBER,
              community: str = UNIVERSE_COMMUNITY):
     """
     TODO: remove hardcoding of universe to plot, and galaxies per universe and so on
           these values depend on the specific universe
     """
     self.galaxies_range = list(
         range(GALAXY_NUMBER_MIN, GALAXY_NUMBER_MAX + 1))
     self.systems_range = list(
         range(SOLAR_SYSTEM_NUMBER_MIN, SOLAR_SYSTEM_NUMBER_MAX + 1))
     self.planets_range = list(
         range(PLANET_NUMBER_MIN, PLANET_NUMBER_MAX + 1))
     self.planet_increment = 1 / (max(self.planets_range) * 2)
     self.galaxy_increment = (2 * math.pi) / max(self.galaxies_range)
     self.system_increment = self.galaxy_increment / max(self.systems_range)
     self.minimum_distance = 1
     self.shift_to_yaxis = math.pi / 2
     self.highscore_data = HighScoreData(universe_id=universe_id,
                                         community=community)
     self.universe_data = UniverseQuestions(universe_id=universe_id,
                                            community=community)
     self.df_dummy = self.get_dummy_universe_df()
     self.df = self.get_dummy_universe_df()
     self.df = self.insert_universe_data(self.df)
 def test_get_planets_of_alliance(self, mock_requests):
     q = UniverseQuestions(self.universe_id, self.community)
     results = q.get_planets_of_alliance("Amitabha")
     expected = [
         "1:125:5", "1:178:8", "1:193:10", "1:214:7", "1:359:9", "1:375:4",
         "1:60:8", "2:125:12", "2:159:4", "2:178:8", "2:285:11", "2:285:12",
         "2:289:12", "2:310:8", "2:317:8", "2:319:6", "2:340:7", "2:341:6",
         "2:342:7", "2:355:8", "2:35:4", "2:361:8", "2:375:12", "2:81:8",
         "3:102:4", "3:13:9", "3:213:10", "3:213:11", "3:213:12",
         "3:213:14", "3:213:15", "3:213:9", "3:214:10", "3:214:11",
         "3:231:6", "3:239:11", "3:239:12", "3:239:13", "3:239:14",
         "3:239:15", "3:239:4", "3:239:5", "3:239:6", "3:239:7", "3:265:4",
         "3:295:12", "3:300:11", "3:317:9", "3:36:8", "3:374:8", "3:378:8",
         "3:65:4", "3:82:8", "4:125:5", "4:13:9", "4:159:8", "4:214:13",
         "4:214:7", "4:214:9", "4:219:10", "4:219:11", "4:219:12",
         "4:219:15", "4:219:5", "4:220:10", "4:220:11", "4:220:12",
         "4:220:8", "4:220:9", "4:221:7", "4:226:11", "4:23:9", "4:262:10",
         "4:262:12", "4:262:7", "4:262:9", "4:289:7", "4:327:12", "4:341:8",
         "4:342:8", "4:374:4", "4:378:6", "4:382:8", "4:398:12", "4:407:4",
         "4:407:5", "4:407:7", "4:449:8", "4:63:9", "4:78:6", "4:99:4",
         "5:135:8", "5:135:9", "5:13:9", "5:214:7", "5:215:6", "5:235:8",
         "5:238:12", "5:270:9", "5:95:8", "6:13:9", "6:278:1", "6:278:15",
         "6:280:10", "6:280:6", "6:280:9", "6:281:7", "6:289:7", "6:378:8",
         "7:13:9", "7:236:8", "7:246:8", "7:257:1", "7:257:8", "7:285:4",
         "7:285:5", "7:335:8", "7:380:8", "8:168:8", "8:301:8", "9:252:8",
         "9:267:8", "9:285:7", "9:285:8", "9:285:9", "9:399:1"
     ]
     assert results == expected
 def test_get_planets_of_player_as_json(self, mock_requests):
     q = UniverseQuestions(self.universe_id, self.community)
     result = q.get_planets_of_player_as_json(self.player_name)
     expected = ('[{"coords": "3:54:6", "name": "Colony"},'
                 ' {"coords": "3:54:14", "name": "Colony"},'
                 ' {"coords": "3:54:15", "name": "Colony"},'
                 ' {"coords": "3:54:1", "name": "Colony"},'
                 ' {"coords": "4:288:8", "name": "Colony"},'
                 ' {"coords": "2:436:9", "name": "Colony"}]')
     assert result == expected
 def test_get_planets_distribution_by_galaxy(self, mock_requests):
     q = UniverseQuestions(self.universe_id, self.community)
     results = q.get_planets_distribution_by_galaxy("Amitabha")
     expected = {
         "1": 7,
         "2": 17,
         "3": 29,
         "4": 38,
         "5": 9,
         "6": 9,
         "7": 9,
         "8": 2,
         "9": 6
     }
     assert results == expected
 def test_get_player_data_as_json(self, mock_requests):
     q = UniverseQuestions(self.universe_id, self.community)
     result = q.get_player_data_as_json(self.player_name)
     expected = (
         '{"playerData": {"id": "110008", "name": "TS X0X0", '
         '"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", '
         '"xsi:noNamespaceSchemaLocation": "https://s162-en.ogame.gameforge.com/api/xsd/playerData.xsd", '
         '"timestamp": "1587724187", "serverId": "en162", "positions": '
         '{"position": ['
         '{"type": "0", "score": "1806", "#text": "585"}, '
         '{"type": "1", "score": "1465", "#text": "582"}, '
         '{"type": "2", "score": "214", "#text": "595"}, '
         '{"type": "3", "score": "160", "ships": "28", "#text": "536"}, '
         '{"type": "4", "score": "0", "#text": "626"}, '
         '{"type": "5", "score": "160", "#text": "596"}, '
         '{"type": "6", "score": "36", "#text": "571"}, '
         '{"type": "7", "score": "0", "#text": "555"}]}, '
         '"planets": {"planet": {"id": "33749193", "name": "hangry", "coords": "5:215:6"}}, '
         '"alliance": {"id": "500234", "name": "NamoAmitabha", "tag": "Amitabha"}}}'
     )
     assert result == expected
 def test_get_player_id_fails(self, mock_requests):
     q = UniverseQuestions(self.universe_id, self.community)
     with pytest.raises(IndexError):
         q.get_player_id(self.player_name + "suffix")
 def test_get_player_id(self, mock_requests):
     q = UniverseQuestions(self.universe_id, self.community)
     result = q.get_player_id(self.player_name)
     assert result == self.play_id
 def test_is_planet_taken_fails(self, mock_requests):
     q = UniverseQuestions(self.universe_id, self.community)
     result = q.is_planet_taken("3:54:4")
     assert result is False
 def test_get_players_of_alliances_by_name(self, mock_requests):
     q = UniverseQuestions(self.universe_id, self.community)
     result = q.get_players_of_alliances_by_name(self.alliance_name)
     result = result.to_dict(orient="records")
     assert result == self.alliance_members
 def test_get_alliance_id_by_name(self, mock_requests):
     q = UniverseQuestions(self.universe_id, self.community)
     result = q.get_alliance_id_by_name(self.alliance_name)
     assert result == self.alliance_id
 def test_get_player_status(self, mock_requests):
     q = UniverseQuestions(self.universe_id, self.community)
     result = q.get_player_status(self.player_name)
     expected = "vI"
     assert result == expected
class UniverseFigure:
    def __init__(self,
                 universe_id: int = UNIVERSE_NUMBER,
                 community: str = UNIVERSE_COMMUNITY):
        """
        TODO: remove hardcoding of universe to plot, and galaxies per universe and so on
              these values depend on the specific universe
        """
        self.galaxies_range = list(
            range(GALAXY_NUMBER_MIN, GALAXY_NUMBER_MAX + 1))
        self.systems_range = list(
            range(SOLAR_SYSTEM_NUMBER_MIN, SOLAR_SYSTEM_NUMBER_MAX + 1))
        self.planets_range = list(
            range(PLANET_NUMBER_MIN, PLANET_NUMBER_MAX + 1))
        self.planet_increment = 1 / (max(self.planets_range) * 2)
        self.galaxy_increment = (2 * math.pi) / max(self.galaxies_range)
        self.system_increment = self.galaxy_increment / max(self.systems_range)
        self.minimum_distance = 1
        self.shift_to_yaxis = math.pi / 2
        self.highscore_data = HighScoreData(universe_id=universe_id,
                                            community=community)
        self.universe_data = UniverseQuestions(universe_id=universe_id,
                                               community=community)
        self.df_dummy = self.get_dummy_universe_df()
        self.df = self.get_dummy_universe_df()
        self.df = self.insert_universe_data(self.df)

    def get_dummy_universe_df(self):
        universe = [{
            'galaxy': galaxy,
            'system': system,
            'planet': planet,
            'taken': 0,
            'coords': f'{galaxy}:{system}:{planet}',
            'x': 0.0,
            'y': 0.0,
            'r': 0.0,
            'system_degree': 0.0
        } for galaxy in self.galaxies_range for system in self.systems_range
                    for planet in self.planets_range]
        df = pd.DataFrame(universe)
        df['n'] = self.calculate_linear_coordinate(df)
        return df

    def calculate_radius(self,
                         planet_slot: Union[int, float],
                         *,
                         minimum_distance: Union[int, float] = None,
                         planet_increment: Union[int, float] = None) -> float:
        """
        Plotting utility. Returns the the `radius` representing the distance
        of the planet form the center of the universe.
        All planets with the same slot in each solar system are modelled to
        have the same `radius`

        The values `minimum_distance` and `planet_increment` are empirical.

        Args:
            planet_slot (int, float):
            minimum_distance (int, float): the minimum distance every planet should
                                           have from the center of the universe
            planet_increment (int, float): distance between each planet slot

        Return:
            (float)
        """
        if minimum_distance is None:
            minimum_distance = self.minimum_distance
        if planet_increment is None:
            planet_increment = self.planet_increment

        return minimum_distance + planet_increment * planet_slot

    def _get_ogame_coordinate_from_linear(self,
                                          lin_coord: int) -> Dict[str, int]:
        """
        Inverse operation of `calculate_linear_coordinate`

        Args:
            lin_coord(int):

        Returns:
            dict with ``galaxy``, ``system`` and ``planet`` keys, with integer values

        """
        coords_df = self.df_dummy.query('n == @lin_coord')
        coords_list = coords_df.to_dict(orient='records')
        coords_dict = coords_list[0]
        # only return subset of dict
        coords = {}
        for key in (
                'galaxy',
                'system',
                'planet',
        ):
            coords[key] = coords_dict[key]
        return coords

    def calculate_linear_coordinate(
            self, df: Union[pd.DataFrame, Dict[str,
                                               int]]) -> Union[pd.Series, int]:
        """
        calculates a unique planet ID (integer) based on the universe configuration.

        Inverse of `_get_ogame_coordinate_from_linear`

        Args:
            df (pd.Series, Dict): contains at least `galaxy`, `system` `planet` keys with values.
        """

        value = ((df['galaxy'] - 1) * max(self.systems_range) *
                 max(self.planets_range) +
                 (df['system'] - 1) * max(self.planets_range) + df['planet'])
        return value

    def calculate_system_degree(
            self,
            df: Union[pd.DataFrame, Dict],
            *,
            galaxy_increment: float = None,
            system_increment: float = None,
            shift_to_yaxis: float = None) -> Union[pd.Series, float]:
        """
        assumes the universe is modelled clock like in a circle where each
        system corresponds to the minutes/seconds/hours expressed in a degree
        between 0 and 2 * pi.

        will prefer user supplied values in `kwargs` or look them up in the instance

        Args:
            df (pd.DataFrame, dict): dict like, with keys ``galaxy`` (int, float) and ``system`` (int, float)
            galaxy_increment (float):
            system_increment (float):
            shift_to_yaxis (float):

        Returns:
            (pd.Series, float): depending on the input

        """
        if not galaxy_increment:
            galaxy_increment = self.galaxy_increment
        if not system_increment:
            system_increment = self.system_increment
        if not shift_to_yaxis:
            shift_to_yaxis = self.shift_to_yaxis
        system_degree = ((df['galaxy'] - 1) * galaxy_increment +
                         (df['system'] - 1) * system_increment +
                         shift_to_yaxis)
        return system_degree

    def insert_universe_data(self, df: pd.DataFrame) -> pd.DataFrame:
        df['taken'] = df['coords'].apply(
            lambda x: int(self.universe_data.is_planet_taken(x)))
        df['system_degree'] = self.calculate_system_degree(df)
        df['x'] = df['system_degree'].apply(lambda x: math.cos(x))
        df['y'] = df['system_degree'].apply(lambda x: math.sin(x))
        df['r'] = df['planet'].apply(lambda x: self.calculate_radius(x))
        return df

    def _get_default_layout(self):
        return go.Layout({
            'title': {
                'text': 'inactive players in your area',
                'font': {
                    'family': 'serif',
                    'size': 18,
                    'color': '#00DA00'
                },
                'x': 0.025,
                'y': 0.975,
                'yanchor': 'top',
                'xanchor': 'left',
            },
            'autosize': False,
            'width': 650,
            'height': 500,
            'plot_bgcolor': '#005A6F',
            'paper_bgcolor': '#005A6F',
            'margin': {
                'l': 20,
                'b': 20,
                't': 20,
                'r': 20
            },
            'xaxis': {
                'range': [1.55, -1.55],
                'zeroline': False,
                'showgrid': False,
                'tickmode': 'auto',
                'nticks': 0,
                'showticklabels': False
            },
            'yaxis': {
                'range': [-1.55, 1.55],
                'zeroline': False,
                'showgrid': False,
                'tickmode': 'auto',
                'nticks': 0,
                'showticklabels': False,
                'scaleanchor': 'x',
                'scaleratio': 1
            }
        })

    def _get_figure_data(self, df: pd.DataFrame) -> List[go.Scattergl]:
        """
        casts data within a pandas object into a list of dash object.
        iterate over each all planet slots available for that universe
        and create a scatter char with for each slot (same slot -> same radius).

        TODO change color intensity with eco score of each planet to represent juiciness
        """
        query_str = 'planet == @planet_slot'
        data = [
            go.Scattergl({
                'x': (df.query(query_str)['x'] * df.query(query_str)['r']),
                'y': (df.query(query_str)['y'] * df.query(query_str)['r']),
                'mode':
                'markers',
                'marker': {
                    'symbol':
                    'circle',
                    'size': [6 for elm in range(len(df.query(query_str)))],
                    'color':
                    df.query(query_str)['taken'].apply(
                        lambda x: '#66ff66' if x == 0 else '#ff6666'),
                },
                'name':
                f'{planet_slot}. slot',
                'hoverinfo':
                'text',
                'hovertext':
                df.query(query_str)['coords'],
                'showlegend':
                False,
            }) for planet_slot in self.planets_range
        ]
        return data

    def _get_figure(self, df: pd.DataFrame) -> go.Figure:
        data = self._get_figure_data(df)
        layout = self._get_default_layout()
        return go.Figure(data=data, layout=layout)

    def get_dummy_planets_data(self):
        return self.df_dummy

    def get_taken_planets_data(self):
        query_str = 'taken == 1'
        return self.df.query(query_str)

    def get_free_planets_data(self):
        query_str = 'taken == 0'
        return self.df.query(query_str)

    def get_universe_with_player_details(self,
                                         df_raw: pd.DataFrame) -> pd.DataFrame:
        """
        Adds, player, alliance and economy score data to an initial data set within `df_raw`.

        Args:
            df_raw:

        Returns:
            (pd.DataFrame)

        """
        df_player_name = UNIVERSE_FIGURE.universe_data.players.loc[:, [
            'id', 'name', 'status', 'alliance'
        ]]
        df_player_name.set_index('alliance', inplace=True)
        df_player_name.rename(index=str,
                              columns={
                                  'id': 'player_id',
                                  'name': 'player_name'
                              },
                              inplace=True)

        alliance_names = UNIVERSE_FIGURE.universe_data.alliances.loc[:, [
            'id', 'name'
        ]]
        alliance_names.set_index('id', inplace=True)
        alliance_names.rename(index=str,
                              columns={'name': 'alliance_name'},
                              inplace=True)

        df_player = df_player_name.join(alliance_names)
        df_player.set_index('player_id', inplace=True, drop=True)

        df_eco_score = UNIVERSE_FIGURE.highscore_data.economy.loc[:, [
            'id', 'score'
        ]]
        df_eco_score.rename(index=str,
                            columns={
                                'id': 'player_id',
                                'score': 'eco_score'
                            },
                            inplace=True)
        df_eco_score.set_index('player_id', inplace=True)
        df_player_with_score = df_player.join(df_eco_score)

        df_detailed = df_raw.set_index('player').join(df_player_with_score)

        df_viz = UNIVERSE_FIGURE.df.loc[:, [
            'coords', 'x', 'y', 'r', 'n', 'taken', 'planet', 'galaxy', 'system'
        ]]
        df_viz.set_index('coords', inplace=True)
        df = df_detailed.set_index('coords').join(df_viz)
        df.reset_index(inplace=True)
        df['n2'] = df['n']  # introduce possible second filter rule
        df = df[[
            'n', 'n2', 'x', 'y', 'r', 'galaxy', 'system', 'planet', 'taken',
            'coords', 'planet_name', 'player_name', 'status', 'alliance_name',
            'eco_score'
        ]]
        return df

    def get_taken_inactive_planets_fig(self):
        df = self.get_taken_inactive_planets_data()
        return self._get_figure(df)

    def get_taken_active_planets_fig(self):
        df = self.get_taken_active_planets_data()
        return self._get_figure(df)

    def _get_active_players(self):
        return self.universe_data.players.query(
            "status != 'i' ")['id'].tolist()

    def _get_inactive_players(self):
        return self.universe_data.players.query(
            "status == 'i' ")['id'].tolist()

    def get_taken_inactive_planets_data(self):
        inactive = self._get_inactive_players()
        query_str = 'player == @inactive'
        coords = self.universe_data.universe.query(
            query_str)['coords'].to_list()
        return self.df.query('taken == 1 and coords in @coords')

    def get_taken_active_planets_data(self):
        active = self._get_active_players()
        query_str = 'player == @active'
        coords = self.universe_data.universe.query(
            query_str)['coords'].to_list()
        return self.df.query('taken == 1 and coords in @coords')

    def get_inactive_players(self):
        inactive = self._get_inactive_players()
        query_str = 'player == @inactive'
        df_raw = self.universe_data.universe.query(query_str)
        df_raw.rename(index=str, columns={'name': 'planet_name'}, inplace=True)
        df = self.get_universe_with_player_details(df_raw)
        return df

    def get_active_players(self):
        active = self._get_inactive_players()
        query_str = 'player == @active'
        df_raw = self.universe_data.universe.query(query_str)
        df_raw.rename(index=str, columns={'name': 'planet_name'}, inplace=True)
        df = self.get_universe_with_player_details(df_raw)
        return df