From b4b16416604f592453f5c5cad247ad5e67b76c24 Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Tue, 20 Jun 2023 11:43:43 +0100 Subject: [PATCH 01/13] added init gen framework --- src/p2lab/__main__.py | 2 ++ src/p2lab/pokemon/poke_factory.py | 21 +++++++++++++++++++++ tests/test_pokedex.py | 14 ++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 src/p2lab/pokemon/poke_factory.py create mode 100644 tests/test_pokedex.py diff --git a/src/p2lab/__main__.py b/src/p2lab/__main__.py index 755e7f4..b0f734b 100644 --- a/src/p2lab/__main__.py +++ b/src/p2lab/__main__.py @@ -6,12 +6,14 @@ import numpy as np from p2lab.genetic.genetic import genetic_algorithm +from p2lab.pokemon.poke_factory import PokeFactory from p2lab.pokemon.premade import gen_1_pokemon from p2lab.pokemon.teams import generate_teams, import_pool async def main_loop(num_teams, team_size, num_generations, unique): # generate the pool + PokeFactory() pool = import_pool(gen_1_pokemon()) seed_teams = generate_teams(pool, num_teams, team_size, unique=unique) # crossover_fn = build_crossover_fn(locus_swap, locus=0) diff --git a/src/p2lab/pokemon/poke_factory.py b/src/p2lab/pokemon/poke_factory.py new file mode 100644 index 0000000..0834422 --- /dev/null +++ b/src/p2lab/pokemon/poke_factory.py @@ -0,0 +1,21 @@ +""" +This class is used to generate Pokemon teams using the pokedex provided from +Pokemon Showdown (via poke-env) + +This is directly inspired by poke-env's diagostic_tools folder +""" +from __future__ import annotations + +from poke_env.data import GenData as POKEDEX + + +class PokeFactory: + def __init__(self, gen=1): + self.dex = POKEDEX(gen) + self.dex2mon = { + self.dex.pokedex[m]["num"]: self.dex.pokedex[m]["num"] + for m in self.dex.pokedex + } + + def get_pokemon_by_dexnum(self, dexnum): + return self.dex.pokedex[self.dex2mon[dexnum]] diff --git a/tests/test_pokedex.py b/tests/test_pokedex.py new file mode 100644 index 0000000..2854540 --- /dev/null +++ b/tests/test_pokedex.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from p2lab.pokemon import poke_factory + + +def test_pokedex(): + p = poke_factory.PokeFactory() + assert p is not None + + +def test_eevee_fetch(): + p = poke_factory.PokeFactory() + eevee = p.get_pokemon_by_dexnum(133) + assert eevee["species"] == "Eevee" From df935d328991f660e755a41f1dfa389eecd7cf8a Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Tue, 20 Jun 2023 12:56:11 +0100 Subject: [PATCH 02/13] passing --- src/p2lab/pokemon/poke_factory.py | 5 +---- tests/test_pokedex.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/p2lab/pokemon/poke_factory.py b/src/p2lab/pokemon/poke_factory.py index 0834422..311f4bd 100644 --- a/src/p2lab/pokemon/poke_factory.py +++ b/src/p2lab/pokemon/poke_factory.py @@ -12,10 +12,7 @@ class PokeFactory: def __init__(self, gen=1): self.dex = POKEDEX(gen) - self.dex2mon = { - self.dex.pokedex[m]["num"]: self.dex.pokedex[m]["num"] - for m in self.dex.pokedex - } + self.dex2mon = {int(self.dex.pokedex[m]["num"]): m for m in self.dex.pokedex} def get_pokemon_by_dexnum(self, dexnum): return self.dex.pokedex[self.dex2mon[dexnum]] diff --git a/tests/test_pokedex.py b/tests/test_pokedex.py index 2854540..0bee0ec 100644 --- a/tests/test_pokedex.py +++ b/tests/test_pokedex.py @@ -11,4 +11,4 @@ def test_pokedex(): def test_eevee_fetch(): p = poke_factory.PokeFactory() eevee = p.get_pokemon_by_dexnum(133) - assert eevee["species"] == "Eevee" + assert eevee["species"].lower() == "eevee" From 4265adbb029fdf2d039acd9623a871139fb55603 Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Tue, 20 Jun 2023 12:59:31 +0100 Subject: [PATCH 03/13] added tests to drive dev --- tests/test_pokedex.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_pokedex.py b/tests/test_pokedex.py index 0bee0ec..6b4e2e7 100644 --- a/tests/test_pokedex.py +++ b/tests/test_pokedex.py @@ -12,3 +12,15 @@ def test_eevee_fetch(): p = poke_factory.PokeFactory() eevee = p.get_pokemon_by_dexnum(133) assert eevee["species"].lower() == "eevee" + + +def test_eevee_moves(): + assert 1 == 0 + + +def test_eevee_stats(): + assert 1 == 0 + + +def test_impossible_pokemon(): + assert 1 == 0 From 61b6b28445816c44bf639d41a9e249fd835ae507 Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Tue, 20 Jun 2023 14:39:03 +0100 Subject: [PATCH 04/13] Updated poke-env to load learnsets --- poke-env | 2 +- tests/test_pokedex.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/poke-env b/poke-env index b882c62..e2f4098 160000 --- a/poke-env +++ b/poke-env @@ -1 +1 @@ -Subproject commit b882c626b1382f1cda67aaee50fff9a4cc7eedb4 +Subproject commit e2f40982554440c79f46830dec256970db5c26e1 diff --git a/tests/test_pokedex.py b/tests/test_pokedex.py index 6b4e2e7..fd65903 100644 --- a/tests/test_pokedex.py +++ b/tests/test_pokedex.py @@ -15,7 +15,9 @@ def test_eevee_fetch(): def test_eevee_moves(): - assert 1 == 0 + p = poke_factory.PokeFactory() + eevee = p.get_pokemon_by_dexnum(133) + assert eevee is not None def test_eevee_stats(): From bae34ca406d607a7f4c52951dec66915d545bf86 Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Tue, 20 Jun 2023 16:08:17 +0100 Subject: [PATCH 05/13] Added more features and tests --- src/p2lab/pokemon/poke_factory.py | 32 ++++++++++++++++++++++++++++--- tests/test_pokedex.py | 20 +++++++++++++------ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/p2lab/pokemon/poke_factory.py b/src/p2lab/pokemon/poke_factory.py index 311f4bd..f6c1805 100644 --- a/src/p2lab/pokemon/poke_factory.py +++ b/src/p2lab/pokemon/poke_factory.py @@ -10,9 +10,35 @@ class PokeFactory: - def __init__(self, gen=1): - self.dex = POKEDEX(gen) - self.dex2mon = {int(self.dex.pokedex[m]["num"]): m for m in self.dex.pokedex} + def __init__(self, gen=1, drop_forms=True): + self.gen = ( + gen if gen > 4 else 3 + ) # because this seems to be the minimum gen for the pokedex + self.dex = POKEDEX(self.gen) + if drop_forms: + self.dex2mon = { + int(self.dex.pokedex[m]["num"]): m + for m in self.dex.pokedex + if "forme" not in self.dex.pokedex[m].keys() + } + else: + self.dex2mon = { + int(self.dex.pokedex[m]["num"]): m for m in self.dex.pokedex + } def get_pokemon_by_dexnum(self, dexnum): return self.dex.pokedex[self.dex2mon[dexnum]] + + def get_allowed_moves(self, dexnum, level=100): + pot_moves = self.dex.learnset[self.dex2mon[dexnum]]["learnset"] + allowed_moves = [] + for move, lims in pot_moves.items(): + gens = [int(lim[0]) for lim in lims] + # TODO: write logic here to check if level is allowed + if level != 100: + msg = "Level checking not implemented yet" + raise NotImplementedError(msg) + # lvls = [int(l[1:]) for l in lims if l[1:].isdigit()] + if self.gen in gens: + allowed_moves.append(move) + return allowed_moves diff --git a/tests/test_pokedex.py b/tests/test_pokedex.py index fd65903..0e933d5 100644 --- a/tests/test_pokedex.py +++ b/tests/test_pokedex.py @@ -11,18 +11,26 @@ def test_pokedex(): def test_eevee_fetch(): p = poke_factory.PokeFactory() eevee = p.get_pokemon_by_dexnum(133) - assert eevee["species"].lower() == "eevee" + assert eevee["baseSpecies"].lower() == "eevee" + + +def test_bulbasaur_fetch(): + p = poke_factory.PokeFactory() + eevee = p.get_pokemon_by_dexnum(1) + assert eevee["baseSpecies"].lower() == "bulbasaur" def test_eevee_moves(): p = poke_factory.PokeFactory() - eevee = p.get_pokemon_by_dexnum(133) - assert eevee is not None + eevee_moves = p.get_allowed_moves(133) + assert len(eevee_moves) > 0 -def test_eevee_stats(): - assert 1 == 0 +def test_bulbasaur_moves(): + p = poke_factory.PokeFactory() + bulb_moves = p.get_allowed_moves(1) + assert len(bulb_moves) > 0 -def test_impossible_pokemon(): +def test_eevee_stats(): assert 1 == 0 From 066234752ba017640fbb9ece25232c553cac2438 Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Tue, 20 Jun 2023 19:18:24 +0100 Subject: [PATCH 06/13] pokemon creation is live --- src/p2lab/pokemon/poke_factory.py | 4 ++++ tests/test_pokedex.py | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/p2lab/pokemon/poke_factory.py b/src/p2lab/pokemon/poke_factory.py index f6c1805..8077608 100644 --- a/src/p2lab/pokemon/poke_factory.py +++ b/src/p2lab/pokemon/poke_factory.py @@ -7,6 +7,7 @@ from __future__ import annotations from poke_env.data import GenData as POKEDEX +from poke_env.teambuilder import TeambuilderPokemon class PokeFactory: @@ -42,3 +43,6 @@ def get_allowed_moves(self, dexnum, level=100): if self.gen in gens: allowed_moves.append(move) return allowed_moves + + def make_pokemon(self, dexnum, **kwargs): + return TeambuilderPokemon(species=self.dex2mon[dexnum], **kwargs) diff --git a/tests/test_pokedex.py b/tests/test_pokedex.py index 0e933d5..8d88131 100644 --- a/tests/test_pokedex.py +++ b/tests/test_pokedex.py @@ -16,8 +16,8 @@ def test_eevee_fetch(): def test_bulbasaur_fetch(): p = poke_factory.PokeFactory() - eevee = p.get_pokemon_by_dexnum(1) - assert eevee["baseSpecies"].lower() == "bulbasaur" + bulb = p.get_pokemon_by_dexnum(1) + assert bulb["baseSpecies"].lower() == "bulbasaur" def test_eevee_moves(): @@ -32,5 +32,13 @@ def test_bulbasaur_moves(): assert len(bulb_moves) > 0 -def test_eevee_stats(): - assert 1 == 0 +def test_eevee_is_created(): + p = poke_factory.PokeFactory() + eevee = p.make_pokemon(133) + assert eevee is not None + + +def test_eevee_is_created_with_moves(): + p = poke_factory.PokeFactory() + eevee = p.make_pokemon(133, moves=["tackle", "growl"]) + assert eevee is not None From 905f0f1ffec6aa4d5ebbb18931b9877c79efd04b Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Wed, 21 Jun 2023 07:22:12 +0100 Subject: [PATCH 07/13] Added move generation --- src/p2lab/pokemon/poke_factory.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/p2lab/pokemon/poke_factory.py b/src/p2lab/pokemon/poke_factory.py index 8077608..7866d02 100644 --- a/src/p2lab/pokemon/poke_factory.py +++ b/src/p2lab/pokemon/poke_factory.py @@ -6,6 +6,7 @@ """ from __future__ import annotations +import numpy as np from poke_env.data import GenData as POKEDEX from poke_env.teambuilder import TeambuilderPokemon @@ -44,5 +45,23 @@ def get_allowed_moves(self, dexnum, level=100): allowed_moves.append(move) return allowed_moves - def make_pokemon(self, dexnum, **kwargs): - return TeambuilderPokemon(species=self.dex2mon[dexnum], **kwargs) + def make_pokemon(self, dexnum=None, generate_moveset=False, **kwargs): + """ + kwargs are passed to the TeambuilderPokemon constructor and can include: + - nickname + - item + - ability + - moves + - nature + - evs + - ivs + - level + - happiness + - hiddenpowertype + - gmax + """ + if dexnum is None: + dexnum = np.random.choice(list(self.dex2mon.keys())) + if generate_moveset or "moves" not in kwargs.keys(): + moves = np.random.choice(self.get_allowed_moves(dexnum), 4, replace=False) + return TeambuilderPokemon(species=self.dex2mon[dexnum], moves=moves**kwargs) From e442412cef6365b560a37d7311c89534ec9b9c51 Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Wed, 21 Jun 2023 07:38:45 +0100 Subject: [PATCH 08/13] More testing and move generation --- src/p2lab/pokemon/poke_factory.py | 14 +++++++++-- tests/test_pokedex.py | 40 +++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/p2lab/pokemon/poke_factory.py b/src/p2lab/pokemon/poke_factory.py index 7866d02..4fa0755 100644 --- a/src/p2lab/pokemon/poke_factory.py +++ b/src/p2lab/pokemon/poke_factory.py @@ -60,8 +60,18 @@ def make_pokemon(self, dexnum=None, generate_moveset=False, **kwargs): - hiddenpowertype - gmax """ + if dexnum < 1: + msg = "Dex number must be greater than 0" + raise ValueError(msg) if dexnum is None: dexnum = np.random.choice(list(self.dex2mon.keys())) if generate_moveset or "moves" not in kwargs.keys(): - moves = np.random.choice(self.get_allowed_moves(dexnum), 4, replace=False) - return TeambuilderPokemon(species=self.dex2mon[dexnum], moves=moves**kwargs) + poss_moves = self.get_allowed_moves(dexnum) + moves = ( + np.random.choice(poss_moves, 4, replace=False) + if len(poss_moves) > 3 + else poss_moves + ) + elif "moves" in kwargs: + return TeambuilderPokemon(species=self.dex2mon[dexnum], **kwargs) + return TeambuilderPokemon(species=self.dex2mon[dexnum], moves=moves, **kwargs) diff --git a/tests/test_pokedex.py b/tests/test_pokedex.py index 8d88131..f39fcd6 100644 --- a/tests/test_pokedex.py +++ b/tests/test_pokedex.py @@ -1,5 +1,7 @@ from __future__ import annotations +import numpy as np + from p2lab.pokemon import poke_factory @@ -42,3 +44,41 @@ def test_eevee_is_created_with_moves(): p = poke_factory.PokeFactory() eevee = p.make_pokemon(133, moves=["tackle", "growl"]) assert eevee is not None + + +def test_random_pokemon_is_created_with_moves(): + p = poke_factory.PokeFactory() + dexnum = np.random.randint(1, 151) + while dexnum == 132: + dexnum = np.random.randint(1, 151) + poke = p.make_pokemon(dexnum=dexnum, generate_moveset=True) + assert len(poke.moves) == 4 + + +def test_ditto_is_created_with_moves(): + p = poke_factory.PokeFactory() + ditto = p.make_pokemon(132) + assert len(ditto.moves) == 1 + + +def test_all_gen1_pokemon_can_be_created(): + p = poke_factory.PokeFactory() + for dexnum in range(1, 152): + poke = p.make_pokemon(dexnum=dexnum, generate_moveset=True) + assert len(poke.moves) > 0 + + +def test_invalid_dex_raised(): + p = poke_factory.PokeFactory() + try: + p.make_pokemon(dexnum=0) + except ValueError: + assert True + else: + raise AssertionError() + + +def test_adding_item_to_pokemon(): + p = poke_factory.PokeFactory() + ditto = p.make_pokemon(132, item="choice scarf") + assert ditto.item == "choice scarf" From 67c8512a495547a5c335829c4bda38c0bf496eea Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Wed, 21 Jun 2023 09:00:50 +0100 Subject: [PATCH 09/13] Minor refactoring --- examples/poke_1v1_fighter.py | 2 +- src/p2lab/__main__.py | 2 -- src/p2lab/{pokemon => battling}/battles.py | 0 src/p2lab/genetic/genetic.py | 2 +- .../{poke_factory.py => pokefactory.py} | 0 tests/test_battles.py | 15 +++++++++++ tests/test_pokedex.py | 26 +++++++++---------- 7 files changed, 30 insertions(+), 17 deletions(-) rename src/p2lab/{pokemon => battling}/battles.py (100%) rename src/p2lab/pokemon/{poke_factory.py => pokefactory.py} (100%) create mode 100644 tests/test_battles.py diff --git a/ examples/poke_1v1_fighter.py b/ examples/poke_1v1_fighter.py index e9a6428..1f6d003 100644 --- a/ examples/poke_1v1_fighter.py +++ b/ examples/poke_1v1_fighter.py @@ -21,7 +21,7 @@ from poke_env import PlayerConfiguration from poke_env.player import SimpleHeuristicsPlayer -from p2lab.pokemon.battles import run_battles +from p2lab.battling.battles import run_battles from p2lab.pokemon.premade import gen_1_pokemon from p2lab.pokemon.teams import generate_teams, import_pool diff --git a/src/p2lab/__main__.py b/src/p2lab/__main__.py index b0f734b..755e7f4 100644 --- a/src/p2lab/__main__.py +++ b/src/p2lab/__main__.py @@ -6,14 +6,12 @@ import numpy as np from p2lab.genetic.genetic import genetic_algorithm -from p2lab.pokemon.poke_factory import PokeFactory from p2lab.pokemon.premade import gen_1_pokemon from p2lab.pokemon.teams import generate_teams, import_pool async def main_loop(num_teams, team_size, num_generations, unique): # generate the pool - PokeFactory() pool = import_pool(gen_1_pokemon()) seed_teams = generate_teams(pool, num_teams, team_size, unique=unique) # crossover_fn = build_crossover_fn(locus_swap, locus=0) diff --git a/src/p2lab/pokemon/battles.py b/src/p2lab/battling/battles.py similarity index 100% rename from src/p2lab/pokemon/battles.py rename to src/p2lab/battling/battles.py diff --git a/src/p2lab/genetic/genetic.py b/src/p2lab/genetic/genetic.py index 9c11d1b..64049f5 100644 --- a/src/p2lab/genetic/genetic.py +++ b/src/p2lab/genetic/genetic.py @@ -7,10 +7,10 @@ from poke_env import PlayerConfiguration from poke_env.player import SimpleHeuristicsPlayer +from p2lab.battling.battles import run_battles from p2lab.genetic.fitness import win_percentages from p2lab.genetic.matching import dense from p2lab.genetic.operations import fitness_mutate, mutate, selection -from p2lab.pokemon.battles import run_battles if TYPE_CHECKING: from p2lab.pokemon.teams import Team diff --git a/src/p2lab/pokemon/poke_factory.py b/src/p2lab/pokemon/pokefactory.py similarity index 100% rename from src/p2lab/pokemon/poke_factory.py rename to src/p2lab/pokemon/pokefactory.py diff --git a/tests/test_battles.py b/tests/test_battles.py new file mode 100644 index 0000000..8cd3112 --- /dev/null +++ b/tests/test_battles.py @@ -0,0 +1,15 @@ +from __future__ import annotations + + +def test_battle_random_pokes(): + assert True + + +# p = pokefactory.PokeFactory() +# eevee = p.make_pokemon(133, moves=["tackle", "growl"], level=5) +# pikachu = p.make_pokemon(25, moves=["thundershock", "growl"], level=5) +# team1 = Team([eevee]) +# team2 = Team([pikachu]) +# #matches = + +# res = battle.run_battles(matches, teams, player_1, player_2, battles_per_match=10) diff --git a/tests/test_pokedex.py b/tests/test_pokedex.py index f39fcd6..94acce4 100644 --- a/tests/test_pokedex.py +++ b/tests/test_pokedex.py @@ -2,52 +2,52 @@ import numpy as np -from p2lab.pokemon import poke_factory +from p2lab.pokemon import pokefactory def test_pokedex(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() assert p is not None def test_eevee_fetch(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() eevee = p.get_pokemon_by_dexnum(133) assert eevee["baseSpecies"].lower() == "eevee" def test_bulbasaur_fetch(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() bulb = p.get_pokemon_by_dexnum(1) assert bulb["baseSpecies"].lower() == "bulbasaur" def test_eevee_moves(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() eevee_moves = p.get_allowed_moves(133) assert len(eevee_moves) > 0 def test_bulbasaur_moves(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() bulb_moves = p.get_allowed_moves(1) assert len(bulb_moves) > 0 def test_eevee_is_created(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() eevee = p.make_pokemon(133) assert eevee is not None def test_eevee_is_created_with_moves(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() eevee = p.make_pokemon(133, moves=["tackle", "growl"]) assert eevee is not None def test_random_pokemon_is_created_with_moves(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() dexnum = np.random.randint(1, 151) while dexnum == 132: dexnum = np.random.randint(1, 151) @@ -56,20 +56,20 @@ def test_random_pokemon_is_created_with_moves(): def test_ditto_is_created_with_moves(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() ditto = p.make_pokemon(132) assert len(ditto.moves) == 1 def test_all_gen1_pokemon_can_be_created(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() for dexnum in range(1, 152): poke = p.make_pokemon(dexnum=dexnum, generate_moveset=True) assert len(poke.moves) > 0 def test_invalid_dex_raised(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() try: p.make_pokemon(dexnum=0) except ValueError: @@ -79,6 +79,6 @@ def test_invalid_dex_raised(): def test_adding_item_to_pokemon(): - p = poke_factory.PokeFactory() + p = pokefactory.PokeFactory() ditto = p.make_pokemon(132, item="choice scarf") assert ditto.item == "choice scarf" From 3326f2ccee1b71786af51b5250879078b21ad3c9 Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Wed, 21 Jun 2023 09:50:12 +0100 Subject: [PATCH 10/13] Added initial battle testing test --- src/p2lab/pokemon/pokefactory.py | 22 ++++++++++++++++++--- tests/test_battles.py | 34 +++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/p2lab/pokemon/pokefactory.py b/src/p2lab/pokemon/pokefactory.py index 4fa0755..9837e49 100644 --- a/src/p2lab/pokemon/pokefactory.py +++ b/src/p2lab/pokemon/pokefactory.py @@ -45,6 +45,9 @@ def get_allowed_moves(self, dexnum, level=100): allowed_moves.append(move) return allowed_moves + def get_allowed_abilities(self, dexnum): + return self.dex.pokedex[self.dex2mon[dexnum]]["abilities"] + def make_pokemon(self, dexnum=None, generate_moveset=False, **kwargs): """ kwargs are passed to the TeambuilderPokemon constructor and can include: @@ -72,6 +75,19 @@ def make_pokemon(self, dexnum=None, generate_moveset=False, **kwargs): if len(poss_moves) > 3 else poss_moves ) - elif "moves" in kwargs: - return TeambuilderPokemon(species=self.dex2mon[dexnum], **kwargs) - return TeambuilderPokemon(species=self.dex2mon[dexnum], moves=moves, **kwargs) + kwargs["moves"] = moves + if "ivs" not in kwargs.keys(): + ivs = [31] * 6 + kwargs["ivs"] = ivs + if "evs" not in kwargs.keys(): + # TODO: implement EV generation better + evs = [510 // 6] * 6 + kwargs["evs"] = evs + if "level" not in kwargs.keys(): + kwargs["level"] = 100 + if "ability" not in kwargs.keys(): + kwargs["ability"] = np.random.choice( + list(self.get_allowed_abilities(dexnum).values()) + ) + + return TeambuilderPokemon(species=self.dex2mon[dexnum], **kwargs) diff --git a/tests/test_battles.py b/tests/test_battles.py index 8cd3112..0ed5cfc 100644 --- a/tests/test_battles.py +++ b/tests/test_battles.py @@ -1,15 +1,31 @@ from __future__ import annotations +import pytest +from poke_env import PlayerConfiguration +from poke_env.player import SimpleHeuristicsPlayer -def test_battle_random_pokes(): - assert True +from p2lab.battling.battles import run_battles +from p2lab.pokemon import pokefactory +from p2lab.pokemon.teams import Team +pytest_plugins = ("pytest_asyncio",) -# p = pokefactory.PokeFactory() -# eevee = p.make_pokemon(133, moves=["tackle", "growl"], level=5) -# pikachu = p.make_pokemon(25, moves=["thundershock", "growl"], level=5) -# team1 = Team([eevee]) -# team2 = Team([pikachu]) -# #matches = -# res = battle.run_battles(matches, teams, player_1, player_2, battles_per_match=10) +@pytest.mark.asyncio() +async def test_battle_random_pokes(): + p = pokefactory.PokeFactory() + eevee = p.make_pokemon(133, moves=["tackle", "growl"], level=5) + pikachu = p.make_pokemon(25, moves=["thundershock", "growl"], level=5) + team1 = Team([eevee]) + team2 = Team([pikachu]) + teams = [team1, team2] + matches = [[0, 1]] + + player_1 = SimpleHeuristicsPlayer( + PlayerConfiguration("Player 1", None), battle_format="gen7anythinggoes" + ) + player_2 = SimpleHeuristicsPlayer( + PlayerConfiguration("Player 2", None), battle_format="gen7anythinggoes" + ) + res = await run_battles(matches, teams, player_1, player_2, battles_per_match=1) + assert res is not None From 3bcab1b36403841ca7a14330997e694290374dc2 Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Wed, 21 Jun 2023 10:25:28 +0100 Subject: [PATCH 11/13] Funny, player names need unique for async tests --- .gitignore | 1 + tests/test_battles.py | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8c35753..28963d3 100644 --- a/.gitignore +++ b/.gitignore @@ -168,3 +168,4 @@ pokemon-showdown/ # Outputs outputs/* +simple_match.py diff --git a/tests/test_battles.py b/tests/test_battles.py index 0ed5cfc..bdf9e6f 100644 --- a/tests/test_battles.py +++ b/tests/test_battles.py @@ -12,7 +12,7 @@ @pytest.mark.asyncio() -async def test_battle_random_pokes(): +async def test_battle_eevee_pikachu_pokes(): p = pokefactory.PokeFactory() eevee = p.make_pokemon(133, moves=["tackle", "growl"], level=5) pikachu = p.make_pokemon(25, moves=["thundershock", "growl"], level=5) @@ -29,3 +29,24 @@ async def test_battle_random_pokes(): ) res = await run_battles(matches, teams, player_1, player_2, battles_per_match=1) assert res is not None + + +@pytest.mark.asyncio() +async def test_battle_mewtwo_obliterates_eevee(): + p = pokefactory.PokeFactory() + eevee = p.make_pokemon(133, moves=["tackle", "growl"], level=5) + mewtwo = p.make_pokemon(151, moves=["psychic"], level=100) + team1 = Team([eevee]) + team2 = Team([mewtwo]) + teams = [team1, team2] + matches = [[0, 1]] + player_1 = SimpleHeuristicsPlayer( + PlayerConfiguration("Player 3", None), battle_format="gen7anythinggoes" + ) + player_2 = SimpleHeuristicsPlayer( + PlayerConfiguration("Player 4", None), battle_format="gen7anythinggoes" + ) + res = await run_battles(matches, teams, player_1, player_2, battles_per_match=10) + mewtwo_wins = res[0][1] + eevee_wins = res[0][0] + assert mewtwo_wins > eevee_wins From 03fc924ce7d0fe05ae6c097e77a139a1fe2f2b7f Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Wed, 21 Jun 2023 11:02:50 +0100 Subject: [PATCH 12/13] added extra test and fixed dexnum issue --- tests/test_battles.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/test_battles.py b/tests/test_battles.py index bdf9e6f..340185b 100644 --- a/tests/test_battles.py +++ b/tests/test_battles.py @@ -35,7 +35,7 @@ async def test_battle_eevee_pikachu_pokes(): async def test_battle_mewtwo_obliterates_eevee(): p = pokefactory.PokeFactory() eevee = p.make_pokemon(133, moves=["tackle", "growl"], level=5) - mewtwo = p.make_pokemon(151, moves=["psychic"], level=100) + mewtwo = p.make_pokemon(150, moves=["psychic"], level=100) team1 = Team([eevee]) team2 = Team([mewtwo]) teams = [team1, team2] @@ -50,3 +50,32 @@ async def test_battle_mewtwo_obliterates_eevee(): mewtwo_wins = res[0][1] eevee_wins = res[0][0] assert mewtwo_wins > eevee_wins + + +@pytest.mark.asyncio() +async def test_battle_eevee_pikachu_formats(): + p = pokefactory.PokeFactory() + eevee = p.make_pokemon(133, moves=["tackle", "growl"], level=5) + pikachu = p.make_pokemon(25, moves=["thundershock", "growl"], level=5) + team1 = Team([eevee]) + team2 = Team([pikachu]) + teams = [team1, team2] + matches = [[0, 1]] + counter = iter(range(10, 20)) + battle_formats = [ + "gen4anythinggoes", + "gen6anythinggoes", + "gen7anythinggoes", + "gen8anythinggoes", + ] + for battle_format in battle_formats: + player_1 = SimpleHeuristicsPlayer( + PlayerConfiguration(f"Player {next(counter)}", None), + battle_format=battle_format, + ) + player_2 = SimpleHeuristicsPlayer( + PlayerConfiguration(f"Player {next(counter)}", None), + battle_format=battle_format, + ) + res = await run_battles(matches, teams, player_1, player_2, battles_per_match=1) + assert res is not None From a904641f32440c45c0e6f50c46f8027f0eb6d364 Mon Sep 17 00:00:00 2001 From: AoifeHughes Date: Wed, 21 Jun 2023 12:59:04 +0100 Subject: [PATCH 13/13] Adding nice team generation --- src/p2lab/__main__.py | 18 ++++++--- src/p2lab/pokemon/teams.py | 82 ++++++++++++++++++++++++++------------ 2 files changed, 68 insertions(+), 32 deletions(-) diff --git a/src/p2lab/__main__.py b/src/p2lab/__main__.py index c45c241..a43d08f 100644 --- a/src/p2lab/__main__.py +++ b/src/p2lab/__main__.py @@ -7,12 +7,16 @@ from p2lab.genetic.genetic import genetic_algorithm from p2lab.pokemon.premade import gen_1_pokemon -from p2lab.pokemon.teams import generate_teams, import_pool +from p2lab.pokemon.teams import generate_pool, generate_teams, import_pool -async def main_loop(num_teams, team_size, num_generations, unique): +async def main_loop(num_teams, team_size, num_generations, unique, use_premade=False): # generate the pool - pool = import_pool(gen_1_pokemon()) + pool = ( + import_pool(gen_1_pokemon()) + if use_premade + else generate_pool(151, use_showdown=False, dexids=np.arange(1, 152)) + ) seed_teams = generate_teams(pool, num_teams, team_size, unique=unique) # crossover_fn = build_crossover_fn(locus_swap, locus=0) # run the genetic algorithm @@ -74,11 +78,13 @@ def parse_args(): def main(): args = parse_args() - if args["s"] is not None: - np.random.seed(args["s"]) + if args["seed"] is not None: + np.random.seed(args["seed"]) asyncio.get_event_loop().run_until_complete( - main_loop(args["n"], args["t"], args["g"], args["u"]) + main_loop( + args["numteams"], args["teamsize"], args["generations"], args["unique"] + ) ) diff --git a/src/p2lab/pokemon/teams.py b/src/p2lab/pokemon/teams.py index e11f0fc..f656461 100644 --- a/src/p2lab/pokemon/teams.py +++ b/src/p2lab/pokemon/teams.py @@ -9,6 +9,7 @@ "import_pool", ) +import subprocess import sys from pathlib import Path from subprocess import check_output @@ -17,6 +18,8 @@ from poke_env.teambuilder import Teambuilder from tqdm import tqdm +from p2lab.pokemon.pokefactory import PokeFactory + class Team: def __init__(self, pokemon) -> None: @@ -32,41 +35,68 @@ def yield_team(self): pass +def validate(poke_str, format="gen7anythinggoes"): + try: + check_output( + f"pokemon-showdown validate-team {format}", + shell=True, + input=poke_str, + stderr=subprocess.DEVNULL, + ) + except Exception: + return False + return True + + def generate_pool( - num_pokemon, format="gen7anythinggoes", export=False, filename="pool.txt" + num_pokemon, + format="gen7anythinggoes", + export=False, + filename="pool.txt", + use_showdown=True, + dexids=None, ): teams = [] print("Generating pokemon in batches of 6 to form pool...") # teams are produced in batches of 6, so we need to generate # a multiple of 6 teams that's greater than the number of pokemon N_seed_teams = num_pokemon // 6 + 1 - for _ in tqdm(range(N_seed_teams), desc="Generating teams!"): - poss_team = check_output(f"pokemon-showdown generate-team {format}", shell=True) - try: - check_output( - f"pokemon-showdown validate-team {format} ", - shell=True, - input=poss_team, + + if use_showdown: + for _ in tqdm(range(N_seed_teams), desc="Generating teams!"): + poss_team = check_output( + f"pokemon-showdown generate-team {format}", shell=True ) - except Exception as e: - print("Error validating team... skipping to next") - print(f"Error: {e}") - continue - n_team = _Builder().parse_showdown_team( - check_output( - "pokemon-showdown export-team ", input=poss_team, shell=True - ).decode(sys.stdout.encoding) - ) - if len(n_team) != 6: - msg = "pokemon showdown generated a team not of length 6" + if not validate(poss_team, format): + continue + n_team = _Builder().parse_showdown_team( + check_output( + "pokemon-showdown export-team ", input=poss_team, shell=True + ).decode(sys.stdout.encoding) + ) + if len(n_team) != 6: + msg = "pokemon showdown generated a team not of length 6" + raise Exception(msg) + teams.append(n_team) + pool = np.array(teams).flatten() + # trim the pool to the desired number of pokemon + pool = pool[:num_pokemon] + else: # assumption is that we homegrow some teams + # TODO: Perform validation here for this! + # TODO: Set gens here for later + pokefactory = PokeFactory(7) + if dexids is None: + msg = "dexids must be provided if not using showdown" + raise Exception() + if len(dexids) != num_pokemon: + msg = "dexids must be the same length as num_pokemon" raise Exception(msg) - teams.append(n_team) - - pool = np.array(teams).flatten() - - # trim the pool to the desired number of pokemon - pool = pool[:num_pokemon] - + pool = [] + for dexid in tqdm(dexids): + pot_poke = pokefactory.make_pokemon(dexid) + while not validate(bytes(pot_poke.formatted, "utf-8"), format): + pot_poke = pokefactory.make_pokemon(dexid) + pool.append(pot_poke) if export: with Path.open(filename, "w") as f: packed = "\n".join([mon.formatted for mon in pool])