Home Person Projekte Kontakt

Pokemon Team

Worum es geht


In dem Videospiel Pokemon werden die gegnerischen Trainer in rundenbasierten Kämpfen bezwungen. Dabei schickt der Spieler seine eigenen Pokemon gegen die des Gegners in den Kampf. Nun geht es darum, abwechselnd Attacken einzusetzen, die die Lebenspunkte des gegnerischen Pokemon senken, um so die gegnerischen Pokemon zu besiegen. Sind alle Pokemon des gegnerischen Trainers besiegt, so hat dieser den Kampf verloren.

Fig. 01: Feuer-Pokemon Glumanda (engl. Charmander) kämpft gegen das gegnerische Pokemon Shiggy (engl. Squirtle)

In Fig. 01 sehen wir ein Glumanda (engl. Charmander) und ein Shiggy (engl. Squirtle). Der Spieler hat die Möglichkeit eine Attacke einzusetzen, ein Item aus dem Inventar zu benutzen, Glumanda gegen ein anderes Pokemon zu tauschen, oder die Flucht zu ergreifen. Wie alle Pokemon haben sie einen bestimmten Typen: Glumanda ist vom Typ Feuer und Shiggy hat den Typ Wasser. Es gibt eine ganze Reihe von Typen: Käfer, Feuer, Wasser, Boden, Pflanze, Stein, Geister, etc. ...
Manche Typen sind besonders stark gegen andere. Zum Beispiel ist Feuer schwach gegen Wasser und Wasser ist stark gegen Feuer. Eben wie im echten Leben: Ein Feuer löscht man mit Wasser. Im Spiel bedeutet das, eine Feuer-Attacke wird gegen Shiggy nur wenig Schaden anrichten. Eine Wasser-Attacke gegen unser Glumanda hingegen schon.

Das Ziel


Am Ende des Spiels wartet die Poke-Liga auf uns. Sie besteht aus den vier Stärksten Trainern im Spiel; ein Trainer kann dabei bis zu sechs Pokemon in den Kampf schicken. Erst wenn diese Top 4 Trainer besiegt sind, haben wir das Spiel gewonnen. Der Clue dabei ist, dass wir unser Team zusammen stellen müssen bevor wir die Poke-Liga betreten. Wir müssen uns also Gedanken machen, welche Pokemon wir mitnehmen wollen, um auf alle Möglichkeiten vorbereitet zu sein. Wenn wir auf ein Wasser-Pokemon treffen, ziehen wir unser Glumanda am besten zurück und schicken ein Pokemon in den Kampf, welches effektiver gegen Wasser ist.

Fig. 02: Die erste Trainerin der Top 4 mit ihren Wasser- und Eis-Pokemon Slowbro, Dewgong, Lapras, Jynx, Cloyster (engl.)

Unser Ziel ist es also, uns auf die Poke-Liga optimal vorzubereiten. Mit unserem Feuer-Pokemon Glumanda werden wir gegen Lorelei mit ihren Wasser- und Eis-Pokemon nicht viel ausrichten können. Aber es gibt ja noch weitere Trainer in den Top 4 und wir werden im Laufe des Spiels weitere Pokemon fangen.

Welche Daten uns vorliegen


Es gibt einen Pokemon-Index, auch genannt Pokedex. Der beinhaltet alle Pokemon mit ihren Eigenschaften. Im Spiel stehen nur die Einträge zur Verfügung über die Pokemon, denen wir schon einmal begegnet sind. Wir können uns den Pokedex aber im Internet herunterladen oder wir stellen uns vor, wir hätten das Spiel schon einige Male durch gespielt und den Pokedex in irgendeiner Form abgespeichert. Dementsprechend laden wir uns die Datei des Pokedex herunter, um Informationen über alle Pokemon zu haben, die wir fangen können und die uns in der Poke-Liga begegnen.

Fig. 03: Der Pokemon-Index (Pokedex), in der Spalte '#' sind die Indexnummern der ersten Generation, in bold die Indexnummern des neuesten Pokedex

Ein weiterer Datensatz, der im Internet als herunterladbare Datei sehr leicht zu finden ist, sind die Stärken und Schwächen der jeweiligen Typen. Das Prinzip wurde weiter oben schon erklärt. Die Tabelle brauchen wir also einfach nur herunterladen und können auf die Einträge mithilfe eines Pandas Dataframes zugreifen.

In Fig. 05 sehen wir die Stärken und Schwächen der verschiedenen Typen. Die Daten über die gegnerischen Pokemon (Fig. 04) sind in der Theorie ebenfalls bekannt, wenn wir das Spiel schon einmal gespielt haben. Wir können sie auch dem offiziellen Lösungsratgebern zum Spiel entnehmen oder auf Fanseiten im Internet nachschauen. Hierbei war es jetzt etwas schwieriger die Daten zu sammeln, weil keine fertige Tabelle zum Download zur Verfügung steht. Weil die Menge der Informationen überschaubar ist, habe ich mich entschieden sie händisch einzutragen. Eine weitere Möglichkeit wäre es gewesen, sie von einer Internetseite mithilfe von Beautiful Soup abzugreifen. Für größere Datenmengen ist diese Methode eher zu empfehlen.

Fig. 04: Die Pokemon der Trainerin Lorelei (vergleiche Fig. 02) in Daten
Fig. 05: 2 = ehr effektiv, leeres Feld = neutral, 1/2 = nicht sehr effektiv, 0 = wirkungslos.

Die Strategie


Das Ziel ist es, eine Liste von sechs Pokemon aus dem Pokedex auszuwählen, die wir mit in die Liga nehmen wollen. Wir können uns zwei Strategien überlegen:

  1. maximale Verteidigung
  2. maximaler Angriff

Es ist sinnvoll, hier eine der beiden Strategien festzulegen, weil sie nicht zwangsläufig das selbe Ergebnis liefern. Das können wir daran sehen, dass die Matrix über Stärken und Schwächen nicht symmetrisch ist. Einfach gesagt heißt das, bei einem Angriff durch Elektro ist das sehr effektiv gegen Wasser, ähnlich wie im echten Leben: Wasser und Elektro verträgt sich nicht. Bei einem Angriff durch Wasser gegen Elektro haben wir allerdings keinen Eintrag 1/2, also nicht sehr effektiv, sondern einen neutralen Eintrag. Das heißt, nur weil A gegen B ein guter Angriff, heißt das nicht, dass B eine schlechte Verteidigung gegen A sein muss. Ich habe mich für die Strategie des maximalen Angriffs entschieden aus zwei Gründen. Erstens geht es bei dem angreifenden Typen nicht um das Pokemon, sondern um seine Attacke. Das Wasser-Pokemon Shiggy hat neben seinen Wasser-Attacken auch noch andere im Repertoire. Das macht es ungleich komplizierter bis unmöglich, eine gute Verteidigung aufzubauen, die über neutrale Einträge in der Matrix hinausgeht. Der zweite Grund ist der, dass wir besonders aufpassen müssen auf die Felder mit einem 0 Eintrag. In so einer Situation können wir überhaupt nichts ausrichten gegen den Gegner. Das wird dann im Kampf richtig teuer, wenn wir laufend das Pokemon wechseln müssen.

Im Grunde genommen ist die Strategie also sehr einfach, wir gehen die Typen der gegnerischen Pokemon durch und schauen dann in der Tabelle, welche Typen effektiv sind. Dann gehen wir die Typen durch, die effektiv sind, und wählen das stärkste Pokemon mit diesem Typen aus.

Umsetzung / Code




              import pandas as pd

              # händisches Anlegen der ersten Trainerin 
              df_Lorelei = pd.DataFrame()

              # den Dataframe mit Einträgen füllen
              df_Lorelei['Name'] = ['Dewgong', 'Cloyster', 'Slowbro', 'Jynx', 'Lapras']
              df_Lorelei['Type1'] = ['Water', 'Water', 'Water', 'Ice', 'Water']
              df_Lorelei['Type2'] = ['Ice', 'Ice', 'Psychic', 'Psychic', 'Ice']


              # das erste Pokemon hat den Index 0
              name = df_Lorelei['Name'][0]
              typ1 = df_Lorelei['Type1'][0]
              typ2 = df_Lorelei['Type2'][0]

              print(f'Das erste Pokemon {name} hat den Typ 1 {typ1} und den Typ 2 {typ2}')
              

              > Das erste Pokemon Dewgong hat den Typ 1 Water und den Typ 2 Ice
            

Es wirkt eventuell auf den ersten Blick ineffizient, uns den Typen ausgeben zu lassen, den wir zuerst händisch eingetragen haben. Aber langfristig wollen wir den Code eventuell recyclen und können manche der Prozesse automatisieren. Zusätzlich soll der Code-Block dazu dienen, das Programm nachzuprogrammieren. Deshalb möchte ich möglichst viel Code zeigen, ohne den fachfremden Leser zu verwirren. Die Anfrage für das erste Pokemon führen wir also nun für die vier verbleibenden Pokemon von Lorelei durch. Schnell wird uns klar, dass wir Wasser und Eis mehrmals erhalten werden. Das macht auch Sinn, denn viele Gegner im Spiel haben sich auf einen bestimmten Typen oder ein betimmtes Thema spezialisiert, wie in diesem Fall Wasser und Eis. Eine lange Liste mit einer handvoll verschiedener Einträge wollen wir natürlich nicht speichern. Hier bietet sich eine Datenstruktur an, die in der Python Standardbibliothek zur Verfügung steht, nämlich das Set. Eine Schwäche eines Sets ist es, dass die Reihenfolge nicht stabil ist. Das ist für uns aber erst einmal uninteressant, weil wir nur unser Team zusammen stellen wollen. Ein Set erlaubt es NICHT, einen Wert zweimal zu speichern, ähnlich wie die mathematische Menge. Das ist für uns von Vorteil, weil wir genau die doppelten Einträge filtern wolllen. Ein Set liefert auch gleich Methoden um Elemente hinzuzufügen oder zu entfernen. Wir werden das Set direkt für alle Trainer der Top 4 anlegen.


  
            df_Lorelei = pd.DataFrame()
            df_Bruno = pd.DataFrame()
            df_Agatha = pd.DataFrame()
            df_Lance = pd.DataFrame()

            # erstellen der gegnerprofile
            df_Lorelei['Name'] = ['Dewgong', 'Cloyster', 'Slowbro', 'Jynx', 'Lapras']
            df_Lorelei['Type1'] = ['Water', 'Water', 'Water', 'Ice', 'Water']
            df_Lorelei['Type2'] = ['Ice', 'Ice', 'Psychic', 'Psychic', 'Ice']

            df_Bruno['Name'] = ['Onix', 'Hitmonchan', 'Hitmonlee', 'Onix', 'Machamp']
            df_Bruno['Type1'] = ['Rock', 'Fight', 'Fight', 'Rock', 'Fight']
            df_Bruno['Type2'] = ['Ground', None, None, 'Ground', None]

            df_Agatha['Name'] = ['Gengar', 'Golbat', 'Haunter', 'Arbok', 'Gengar']
            df_Agatha['Type1'] = ['Ghost', 'Poison', 'Ghost', 'Poison', 'Ghost']
            df_Agatha['Type2'] = ['Poison', 'Flying', 'Poison', None, 'Poison']

            df_Lance['Name'] = ['Gyarados', 'Dragonair', 'Dragonair', 'Aerodactyl', 'Dragonite']
            df_Lance['Type1'] = ['Water', 'Dragon', 'Dragon', 'Rock', 'Dragon']
            df_Lance['Type2'] = ['Flying', None, None, 'Flying', 'Flying']

            # dieser Code-Block hat keinen Output
            

            >
            



          # leeres Set erstellen
          enemy_types = set()

          # Funktion schreiben um die Typen eines Trainers einem Set hinzuzufügen
          def add_types(df_enemy, type_set):
              number_of_pokemon = len(df_enemy['Type1'])
              for i in range(number_of_pokemon):
                  type_set.add(df_enemy['Type1'][i])
                  type_set.add(df_enemy['Type2'][i])
              return type_set
                              
          list_of_trainers = [df_Lorelei, df_Bruno, df_Agatha, df_Lance]

          # Funktion auf die Liste der Trainer anwenden
          for trainer in list_of_trainers:
              enemy_types = add_types(trainer, enemy_types)

          print(enemy_types)
          


          > {'Flying', 'Dragon', 'Water', 'Ground', 'Psychic', None, 'Poison', 'Rock', 'Fight', 'Ghost', 'Ice'}
          

Wir sehen, dass mit 10 verschiedenen Typen die Top 4 ziemlich breit aufgestellt sind. Die Typen sind Flug, Drache, Wasser, Boden, Psycho, Gift, Stein, Kampf, Geist und Eis. Der Begriff 'None' bedeutet so viel wie Null und rührt daher, dass manche Pokemon keinen zweiten Typen haben. Um Fehler zu vermeiden, müssen wir im weiteren Verlauf kontrollieren, dass die Typen ungleich 'None' sind. Zum Glück kann ein Typ auch sehr effektiv gegen verschiedene, verteidigende Typen sein. Deshalb sind wir nicht darauf angewiesen, 10 effektive Typen zu finden. Als erstes wollen wir eine Liste aufstellen von Typen, die uns einen Vorteil verschaffen werden. Auch dabei wollen wir keine Doppelzählungen. Wir wollen aber durchaus diverse Typen pro Gegner in Betracht ziehen. Deshalb suchen wir kein Maximum, sondern sammeln alle mit einer 2 als Eintrag, die also sehr effektiv sind.


          my_types = dict()

          #manuelle Kopie um 'Fight' in 'Fighting umzubenennen'
          enemy_types = {'Flying', 'Dragon', 'Water', 'Ground', 'Psychic', None, 'Poison', 'Rock', 'Fighting', 'Ghost', 'Ice'}
          
          # tabelle über stärken und schwächen laden
          df_strength = pd.read_csv('./PokemonData/strength.txt',sep=',')
          
          def find_my_types(my_types, enemy_type, df_strength):
            assert isinstance(enemy_type, str) and enemy_type is not None, "gegnerischer Typ sollte ein String und nicht None sein!"
            length = len(df_strength['Attacking'])
            for j in range(length):
                if df_strength[enemy_type][j] == 2:
                    my_types[j] = df_strength['Attacking'][j]
            return my_types
          
          enemy_types_array = [x for j, x in enumerate(enemy_types) if x!= None]
          
          h = len(enemy_types_array)
          for j in range(h):
            my_types = find_my_types(my_types, enemy_types_array[j], df_strength)
          
          print(my_types)
          


          > {1: 'Fire', 6: 'Fighting', 
            12: 'Rock', 16: 'Steel', 
            9: 'Flying', 10: 'Psychic', 
            17: 'Fairy', 11: 'Bug', 
            13: 'Ghost', 15: 'Dark', 
            3: 'Electric', 4: 'Grass', 
            8: 'Ground', 5: 'Ice', 
            2: 'Water', 14: 'Dragon'}
          

Wir beachten den Index der Typen, der aus der Tabelle stammt. Es kommt nicht auf die Reihenfolge an, sondern sie dienen zur Identifikation. An der Stelle müssen wir noch erwähnen, dass die Typen Steel, Fairy und Dark (Stahl, Fee und Unlicht) in der ersten Generation noch nicht existierten. Dementsprechend haben wir 13 Typen, die effektiv sind gegen unsere Gegner in der Top 4. Eine Strategie könnte nun sein, dass wir von jedem Typ das stärkste Pokemon suchen und diese Liste wiederum nach den Angriffspunkten sortieren. Eine andere könnte es sein, wenn wir für jeden Typen herausfinden, wie vielseitig er einsetzbar ist. Das zweite ist schlauer um sicherzugehen, dass wir auch gegen jeden Gegner gut aufgestellt sind anstatt gegen einige Gegner besonders gut und gegen den Rest mittelmäßig. Der Code funktioniert sehr ähnlich wie der um die Stärken zu finden.


          df_my_types = pd.DataFrame(list(my_types.items()), columns=['ID', 'Type'])

          number_effectives = dict()
          
          def how_many_powers(df_my_types, df_strength):
              df_strength02 = df_strength.transpose()
              
              for i, ID in enumerate(df_my_types["ID"]):
                  count = df_strength02[ID].value_counts().get(2,0)
                  name = df_strength["Attacking"][ID]
                  number_effectives[ID] = [count, name]
                  
              return number_effectives
                  
          nr_eff = how_many_powers(df_my_types, df_strength)
          print(nr_eff)
          

  
          > {13: [2, 'Ghost'],
          15: [2, 'Dark'],
          9: [3, 'Flying'],
          10: [2, 'Psychic'],
          17: [3, 'Fairy'],
          3: [2, 'Electric'],
          4: [3, 'Grass'],
          5: [4, 'Ice'],
          12: [4, 'Rock'],
          11: [3, 'Bug'],
          2: [3, 'Water'],
          8: [5, 'Ground'],
          1: [4, 'Fire'],
          6: [5, 'Fighting'],
          16: [3, 'Steel'],
          14: [1, 'Dragon']}
          

Der Typ Geist ist also effektiv gegen zwei Typen und Drache ist effektiv gegen einen Typen. Aufgrund der Kürze der Liste lesen wir die besten Typen einfach ab. Unter der Berücksichtigung, dass es die oben genannten Typen in der ersten Generation noch nicht gab, sind das also Kampf (5), Boden (5), Feuer (4), Stein (4), Eis (4) und Gras (3). Anstatt Gras wären Flug (3), Käfer (3) oder Wasser (3) genauso gut gewesen. Anhand des Beispiels von Wasser schauen wir, ob sich eine Vorsortierung der Stärken innerhalb des Pokedex finden lässt.


          df_index=pd.read_csv('./PokemonData/pokemon_index.csv')
          df_firstGEN = df_index.loc[df_index.index < 166].loc[df_index['Name'].str.contains('Mega',True)==False]

          def find_pokemon(mytype):
            df_atk_Type=df_firstGEN.loc[df_index['Type 1'].str.contains(mytype,True)==True]
            return df_atk_Type

          gegenwasser01 = find_pokemon('Grass')
          gegenwasser02 = find_pokemon('Electric')

          x1 = gegenwasser01.index
          x2 = gegenwasser02.index
          y1 = gegenwasser01['Attack']
          y2 = gegenwasser02['Attack']

          plt.plot(x1,y1, label='Grass', color='g', linestyle='dashed')
          plt.plot(x2,y2, label='electric', color='y', linestyle='dotted')

          plt.xlabel('Pokedex Number')
          plt.ylabel('Attack Points')
          plt.title('Strong Types against Water')
          plt.legend()

          plt.show()
          

Fig. 06: Gras und Elektro sind effektiv gegen die Wasser-Pokemon der Trainerin Lorelei, Pokedex Number bezieht sich auf die neueste Version (Stand 2023) und nicht auf die der ersten Generation

Eine Vorsortierung lässt sich in Fig. 06 nicht erkennen. Wir müssen also mindestens mit jedem Pokemon des jeweiligen Typs vergleichen, um das mit den meisten Angriffspunkte zu finden.


        mytypes =  ['Fighting', 'Ground', 'Fire', 'Rock', 'Ice', 'Grass']

        def max_attack(mytype): # fordert string und gibt df zurück
            mytypeH = find_pokemon(mytype) 
            max_entry = mytypeH[mytypeH['Attack'] == mytypeH['Attack'].max()]
            return max_entry
            
        list_of_best_pokemon = [max_attack(x) for x in mytypes]
        print(list_of_best_pokemon)
        
        df_my_team = pd.concat(list_of_best_pokemon)
        
        df_my_team
        


        > ...
        

Das Ergebnis


In der Tabelle in Fig. 07 ist der Output aus dem letzten Code-Block ergo das optimale Team. Das optimale Team besteht aus Rizeros (engl. Rhydon), Arktos (engl. Articuno), Sarzenia (engl. Victreebel), Machomei (engl. Machamp), Flamara (engl. Flareon) und Geowaz (engl. Golem). Das Team ist zu sehen in Fig. 08 (links nach rechts und oben nach unten).

Fig. 07: Das beste Team. Der Index in bold ist der aus dem gesamten Pokedex. Die Indexnummer in der Spalte '#' ist die aus der ersten Generation

Abschließend kann man sagen, dass sich mit den Daten, die öffentlich zur Verfügung stehen, schnell ein effektives Team zusammenstellen lässt. Anhand des Codes kann man auch schnell die Strategie wechseln, wie zum Beispiel auf maximale Verteidigung zu setzen. Man kann auch noch weitere Filter einstellen, wenn man zum Beispiel keine legendären Pokemon wie Arktos mitnehmen möchte.
Als Fazit lässt sich außerdem festhalten, dass sich der Code noch effizienter schreiben ließe, wenn man die Tabelle über Stärken und Schwächen im ersten Schritt auf die Generationen bereinigt hätte.

Fig. 08: Das beste Team, Fotocollage von Theo Schnell, Bildurheber Nintendo The Pokemon Company