def range_atk_params(): """Range calculations with attack parameters.""" dsc = DamageStatCalc() attacker = POKEMON_DATA["spinda"] defender = POKEMON_DATA["spinda"] move = generate_move(MOVE_DATA["tackle"]) params = {} params["atk"] = {} params["atk"]["max_evs"] = True params["atk"]["positive_nature"] = True params["def"] = {} params["hp"] = {} dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 25 assert dmg_range[1] == 30 attacker = POKEMON_DATA["exploud"] dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 32 assert dmg_range[1] == 39 params["atk"]["positive_nature"] = False defender = POKEMON_DATA["floatzel"] dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 26 assert dmg_range[1] == 32
def test_nearest_num(): """Make sure it calculates the nearest number properly.""" dsc = DamageStatCalc() assert dsc.find_closest_level(4) == (5, -1) assert dsc.find_closest_level(215) == (200, 15) assert dsc.find_closest_level(17) == (15, 2)
def range_no_params(): """Range Calculations with no parameters.""" dsc = DamageStatCalc() attacker = Pokemon(name="spinda", moves=["tackle"]) defender = Pokemon(name="spinda", moves=["tackle"]) move = generate_move(MOVE_DATA["tackle"]) params = {} params["atk"] = {} params["def"] = {} params["hp"] = {} dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 16 assert dmg_range[1] == 20 attacker = Pokemon(name="floatzel", moves=["watergun"]) move = generate_move(MOVE_DATA['watergun']) dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 21 assert dmg_range[1] == 26 defender = attacker dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 10 assert dmg_range[1] == 13
def __init__(self): """Initialize this player's internal game state.""" self.test_attr = {} self.gamestate = {} self.opp_gamestate = {} self.opp_gamestate["data"] = {} self.opp_gamestate["moves"] = {} self.opp_gamestate["investment"] = {} self.dmg_stat_calc = DamageStatCalc()
def range_status(): """Test damage range with burn status.""" dsc = DamageStatCalc() params = {} params["atk"] = {} params["def"] = {} params["hp"] = {} move = generate_move(MOVE_DATA["tackle"]) attacker = Pokemon(name="spinda", moves=["tackle"]) defender = Pokemon(name="spinda", moves=["tackle"]) attacker.status = BRN_STATUS dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 8 assert dmg_range[1] == 10
def range_hp_params(): """Test calculations when using HP Parameters.""" dsc = DamageStatCalc() attacker = POKEMON_DATA["spinda"] defender = POKEMON_DATA["spinda"] move = generate_move(MOVE_DATA["tackle"]) params = {} params["atk"] = {} params["def"] = {} params["hp"] = {} params["hp"]["max_evs"] = True dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 13 assert dmg_range[1] == 16
def __init__(self, team): """Initialize the agent.""" if not team: raise AttributeError("Team must have at least one pokemon") super().__init__(type="PokemonAgent") self.team = team self.game_state = PokemonPlayerGameState() self.dmg_stat_calc = DamageStatCalc()
def range_def_params(): """Test calculations when using defense parameters.""" dsc = DamageStatCalc() attacker = POKEMON_DATA["spinda"] defender = POKEMON_DATA["spinda"] move = generate_move(MOVE_DATA["tackle"]) params = {} params["atk"] = {} params["def"] = {} params["def"]["max_evs"] = True params["def"]["positive_nature"] = True params["hp"] = {} dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 10 assert dmg_range[1] == 13 params["def"]["positive_nature"] = False dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 11 assert dmg_range[1] == 14
def test_estimate_dmg_val(): """Test calculation of stats.""" dsc = DamageStatCalc() assert dsc.estimate_dmg_val(77) == 9.05 assert dsc.estimate_dmg_val(144, is_hp=True) == 20.43 assert dsc.estimate_dmg_val(88, is_atk=True) == 40.4 assert dsc.estimate_dmg_val(150, is_atk=True, max_evs=True, positive_nature=True) == 83.6 assert dsc.estimate_dmg_val(90, is_hp=True) == 15.29 assert dsc.estimate_dmg_val(120) == 13.14
def range_boosts(): """Make sure that boosts impact the range.""" # Setup dsc = DamageStatCalc() params = {} params["atk"] = {} params["def"] = {} params["hp"] = {} move = generate_move(MOVE_DATA["tackle"]) attacker = Pokemon(name="spinda", moves=["tackle"]) defender = Pokemon(name="spinda", moves=["tackle"]) # With attacking boosts attacker.boosts["atk"] = 1 dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 24 assert dmg_range[1] == 29 defender.boosts["def"] = -1 dmg_range = dsc.calculate_range(move, attacker, defender, params) assert dmg_range[0] == 36 assert dmg_range[1] == 44
def test_init(): """Make sure everything initializes properly.""" dsc = DamageStatCalc() assert dsc.damage_stats
class PokemonPlayerGameState: """Representation of a player's internal game state.""" def __init__(self): """Initialize this player's internal game state.""" self.test_attr = {} self.gamestate = {} self.opp_gamestate = {} self.opp_gamestate["data"] = {} self.opp_gamestate["moves"] = {} self.opp_gamestate["investment"] = {} self.dmg_stat_calc = DamageStatCalc() def reset_gamestates(self): """Reset gamestate values for a new battle.""" self.gamestate = {} self.opp_gamestate = {} self.opp_gamestate["data"] = {} self.opp_gamestate["moves"] = {} self.opp_gamestate["investment"] = {} def init_opp_gamestate(self, opp_team, opp_active): """ Initialize the investment data for the opponent's team. Args: opp_team (list): List with the opponent's Pokemon. opp_active (Pokemon): Opponent's active Pokemon. """ possible_combs = generate_all_ev_combinations() self.opp_gamestate["investment"][opp_active["name"]] = {} self.opp_gamestate["investment"][ opp_active["name"]]["hp"] = possible_combs["hp"] self.opp_gamestate["investment"][ opp_active["name"]]["atk"] = possible_combs["atk"] self.opp_gamestate["investment"][ opp_active["name"]]["def"] = possible_combs["def"] self.opp_gamestate["investment"][ opp_active["name"]]["spa"] = possible_combs["spa"] self.opp_gamestate["investment"][ opp_active["name"]]["spd"] = possible_combs["spd"] self.opp_gamestate["investment"][opp_active["name"]]["spe"] = \ calculate_spe_range(opp_active["name"]) for opp_poke in opp_team: self.opp_gamestate["investment"][opp_poke["name"]] = {} self.opp_gamestate["investment"][ opp_poke["name"]]["hp"] = possible_combs["hp"] self.opp_gamestate["investment"][ opp_poke["name"]]["atk"] = possible_combs["atk"] self.opp_gamestate["investment"][ opp_poke["name"]]["def"] = possible_combs["def"] self.opp_gamestate["investment"][ opp_poke["name"]]["spa"] = possible_combs["spa"] self.opp_gamestate["investment"][ opp_poke["name"]]["spd"] = possible_combs["spd"] self.opp_gamestate["investment"][opp_poke["name"]]["spe"] = \ calculate_spe_range(opp_poke["name"]) def update_gamestate(self, my_gamestate, opp_gamestate): """ Update internal gamestate for self. Args: my_gamestate (dict): PokemonEngine representation of player's position. Should have "active" and "team" keys. opp_gamestate (dict): PokemonEngine representation of opponent's position. Only % HP should be viewable, and has "active" and "team" keys. """ self.gamestate = my_gamestate self.opp_gamestate["data"] = opp_gamestate def new_info(self, raw_turn_info, my_id): """ Get new info for opponent's game_state. Assumes Species Clause is in effect. Args: turn_info (list): What happened on that turn, who took what damage. my_id (str): Name corresponding to the "attacker" or "defender" values of this dict. To know which values the method should be looking at in turn_info. """ turn_info = [ turn for turn in raw_turn_info if turn["type"] == "ATTACK" ] for info in turn_info: if "critical_hit" in info and info["critical_hit"]: # Modify damage for critical hits info["damage"] = info["damage"] * 2 / 3 info["pct_damage"] = info["pct_damage"] * 2 / 3 if info["attacker"] == my_id: # We attacked, infer data about defending pokemon if info.get("move_hits", True): results, combinations = self.results_attacking(info) self.update_atk_inference(info, results, combinations) else: # Just got attacked, infer data about attacking pokemon if info.get("move_hits", True): results, combinations = self.results_defending(info) self.update_def_inference(info, results, combinations) # We're the defender, just learned about a move opp_name = self.opp_gamestate["data"]["active"]["name"] if opp_name not in self.opp_gamestate["moves"]: self.opp_gamestate["moves"][opp_name] = [] if info["move"] not in self.opp_gamestate["moves"][opp_name]: self.opp_gamestate["moves"][opp_name].append(info["move"]) if len(turn_info) == 2 and not contains_switch(turn_info): self.update_speed_inference(turn_info, my_id) def update_speed_inference(self, turn_info, my_id): """ Infer speed information from the turn info. Args: turn_info (dict): Information on a single event of that turn. my_id (str): Name corresponding to the "attacker" or "defender" values of this dict. """ # Moves are different priority, no inference can be made if turn_info[0]["move"]["priority"] != turn_info[1]["move"]["priority"]: return # We're outsped; ie: we're slower opp_poke = turn_info[0]["atk_poke"] outspeed = False if turn_info[0]["attacker"] == my_id: # We outspeed; ie: we're faster opp_poke = turn_info[0]["def_poke"] outspeed = True if opp_poke not in self.opp_gamestate["investment"]: self.opp_gamestate["investment"][opp_poke] = {} if "spe" not in self.opp_gamestate["investment"][opp_poke]: # Slowest possible opponent's pokemon min_speed = Pokemon(name=opp_poke, moves=["tackle"], nature="brave").speed # Fastest possible opponent's pokemon max_speed = Pokemon(name=opp_poke, moves=["tackle"], evs={ "spe": 252 }, nature="jolly").speed self.opp_gamestate["investment"][opp_poke]["spe"] = [ min_speed, max_speed ] if outspeed: if self.opp_gamestate["investment"][opp_poke]["spe"][1] > \ self.gamestate["active"].speed: # Update maximum speed to our speed if necessary self.opp_gamestate["investment"][opp_poke]["spe"][1] = \ self.gamestate["active"].speed else: if self.opp_gamestate["investment"][opp_poke]["spe"][0] < \ self.gamestate["active"].speed: # Update minimum speed to our speed, if necessary self.opp_gamestate["investment"][opp_poke]["spe"][0] = \ self.gamestate["active"].speed def results_attacking(self, turn_info): """ Generate possible results for when we are attacking. Args: turn_info (dict): Information on a single event of that turn. Returns: Two lists contianing T/F values of opponent's defense investment. """ move = turn_info["move"] my_poke = POKEMON_DATA[turn_info["def_poke"]] opp_poke = POKEMON_DATA[turn_info["atk_poke"]] params = {} params["atk"] = {} if self.gamestate["active"].evs.get("atk", 0) > 124: params["atk"]["max_evs"] = True if self.gamestate["active"].increase_stat == "attack": params["atk"]["positive_nature"] = True params["def"] = {} params["hp"] = {} combinations = [] combinations.append((False, False, False)) combinations.append((False, False, True)) combinations.append((True, False, False)) combinations.append((True, False, True)) combinations.append((False, True, False)) combinations.append((False, True, True)) combinations.append((True, True, False)) combinations.append((True, True, True)) num_combinations = len(combinations) results = [] for comb_ind in range(num_combinations): comb = combinations[comb_ind] params["def"]["max_evs"] = comb[0] params["def"]["positive_nature"] = comb[1] params["hp"]["max_evs"] = comb[2] results.append( self.dmg_stat_calc.calculate_range(move, opp_poke, my_poke, params)) return results, combinations def results_defending(self, turn_info): """ Generate possible results for when we are defending. Args: turn_info (dict): Information on a single event of that turn. Returns: Two lists contianing T/F values of opponent's defense investment. """ move = turn_info["move"] my_poke = POKEMON_DATA[turn_info["def_poke"]] opp_poke = POKEMON_DATA[turn_info["atk_poke"]] params = {} params["atk"] = {} params["def"] = {} if self.gamestate["active"].evs.get("def", 0) > 124: params["def"]["max_evs"] = True if self.gamestate["active"].increase_stat == "defense": params["def"]["positive_nature"] = True params["hp"] = {} if self.gamestate["active"].evs.get("hp", 0) > 124: params["hp"]["max_evs"] = True combinations = [] combinations.append((False, False)) combinations.append((True, False)) combinations.append((False, True)) combinations.append((True, True)) results = [] for comb_ind in range(4): comb = combinations[comb_ind] params["atk"]["max_evs"] = comb[0] params["atk"]["positive_nature"] = comb[1] results.append( self.dmg_stat_calc.calculate_range(move, opp_poke, my_poke, params)) return results, combinations def valid_results_atk(self, poke_name, stat, dmg_pct, results, combinations): """ Decide which of potential the potential results are valid given damage dealt. Args: poke_name (str): Name of the pokemon in question. stat (str): Name of the statistic that this move's damage is calculated from, defense or special defense. dmg_pct (float): How much % damage was done this turn. results (list): Possible defense investment combinations. combinations (list): T/F values corresponding to the defense investment combinations. Returns: Subset of the results that are possible given the damage dealt. """ valid_results = [] num_results = len(results) # Initialize the data for this pokemon if not already there if poke_name not in self.opp_gamestate["investment"]: self.opp_gamestate["investment"][poke_name] = {} if stat not in self.opp_gamestate["investment"][poke_name]: self.opp_gamestate["investment"][poke_name][stat] = [] self.opp_gamestate["investment"][poke_name]["hp"] = [] for result_ind in range(num_results): result = results[result_ind] if result[0] <= dmg_pct <= result[1]: result_dict = {} result_dict[stat] = {} result_dict["hp"] = {} result_dict[stat]["max_evs"] = combinations[result_ind][0] result_dict[stat]["positive_nature"] = combinations[ result_ind][1] result_dict["hp"]["max_evs"] = combinations[result_ind][2] valid_results.append(result_dict) return valid_results def valid_results_def(self, poke_name, stat, dmg_pct, results, combinations): """ Decide which of potential the potential results are valid given damage dealt. Args: poke_name (str): Name of the pokemon in question. stat (str): Name of the statistic that this move's damage is calculated from, defense or special defense. dmg_pct (float): How much % damage was done this turn. results (list): Possible defense investment combinations. combinations (list): T/F values corresponding to the defense investment combinations. Returns: Subset of the results that are possible given the damage dealt. """ valid_results = [] num_results = len(results) if poke_name not in self.opp_gamestate["investment"]: self.opp_gamestate["investment"][poke_name] = {} if stat not in self.opp_gamestate["investment"][poke_name]: self.opp_gamestate["investment"][poke_name][stat] = [] for result_ind in range(num_results): result = results[result_ind] if result[0] <= dmg_pct <= result[1]: result_dict = {} result_dict["max_evs"] = combinations[result_ind][0] result_dict["positive_nature"] = combinations[result_ind][1] valid_results.append(result_dict) return valid_results def update_atk_inference(self, turn_info, results, combinations): """ Update the opponent's defense investment information. Args: turn_info (dict): Information on damage dealt this turn. results (list): Possible defense investment combinations. combinations (list): T/F values corresponding to the defense investment combinations. """ move = turn_info["move"] dmg_pct = turn_info["pct_damage"] stat = "def" if move["category"] != "Physical": stat = "spd" poke_name = turn_info["def_poke"] valid_results = self.valid_results_atk(poke_name, stat, dmg_pct, results, combinations) if not self.opp_gamestate["investment"][poke_name][stat]: for res in valid_results: self.opp_gamestate["investment"][poke_name][stat].append( res[stat]) self.opp_gamestate["investment"][poke_name]["hp"].append( res["hp"]) else: def_results = [res[stat] for res in valid_results] hp_results = [res["hp"] for res in valid_results] self.opp_gamestate["investment"][poke_name][stat] = [ result for result in def_results if result in self.opp_gamestate["investment"][poke_name][stat] ] self.opp_gamestate["investment"][poke_name]["hp"] = [ result for result in hp_results if result in self.opp_gamestate["investment"][poke_name]["hp"] ] # Stop it from becoming empty if not self.opp_gamestate["investment"][poke_name][stat]: self.opp_gamestate["investment"][poke_name][stat] = \ generate_all_ev_combinations()[stat] if not self.opp_gamestate["investment"][poke_name]["hp"]: self.opp_gamestate["investment"][poke_name]["hp"] = \ generate_all_ev_combinations()["hp"] def update_def_inference(self, turn_info, results, combinations): """ Update the opponent's attack investment information. Args: turn_info (dict): Information on damage dealt this turn. results (list): Possible defense investment combinations. combinations (list): T/F values corresponding to the defense investment combinations. """ move = turn_info["move"] dmg_pct = turn_info["pct_damage"] stat = "atk" if move["category"] != "Physical": stat = "spa" poke_name = turn_info["atk_poke"] valid_results = self.valid_results_def(poke_name, stat, dmg_pct, results, combinations) if not self.opp_gamestate["investment"][poke_name][stat]: self.opp_gamestate["investment"][poke_name][stat] = valid_results else: self.opp_gamestate["investment"][poke_name][stat] = [ result for result in valid_results if result in self.opp_gamestate["investment"][turn_info["atk_poke"]][stat] ] # Stop it from becoming empty if not self.opp_gamestate["investment"][poke_name][stat]: self.opp_gamestate["investment"][poke_name][stat] = \ generate_all_ev_combinations()[stat] def __getitem__(self, key): """ Define [] lookup on this object. Args: key (str): Attribute of this object to get. Returns: Attribute of this object at the key. """ return self.__getattribute__(key) def to_json(self): """Convert this instance to something the interface can use.""" output = {} # Add the player's gamestate info output["player"] = {} output["player"]["active"] = self.gamestate["active"].to_json() output["player"]["team"] = [ pkmn.to_json() for pkmn in self.gamestate["team"] ] # Add opponent's info output["opponent"] = self.opp_gamestate return output