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