annotate org/types.org @ 22:8992278bf399 tip

typo corrected, thanks to a reader.
author Robert McIntyre <rlm@mit.edu>
date Sun, 20 Sep 2015 11:25:30 -0700
parents 0f6ace87343a
children
rev   line source
rlm@7 1 #+TITLE: Best-First Search for Effective Pokemon Types
rlm@0 2 #+AUTHOR: Robert McIntyre & Dylan Holmes
rlm@0 3 #+EMAIL: rlm@mit.edu
rlm@7 4 #+description: Finding interesting pokemon type combinations through Best-First search in clojure.
rlm@10 5 #+keywords: Pokemon, clojure, best-first search, optimization
rlm@4 6 #+SETUPFILE: ../../aurellem/org/setup.org
rlm@4 7 #+INCLUDE: ../../aurellem/org/level-0.org
rlm@0 8
rlm@0 9 * The Pok\eacute{}mon Type System
rlm@0 10
rlm@0 11 The Pok\eacute{}mon type system consists of seventeen different
rlm@9 12 /types/ (Rock, Grass, Ice, Psychic, Ground, Bug, Flying, Fire,
rlm@9 13 Fighting, Dark, Dragon, Poison, Water, Ghost, Normal, Electric, and
rlm@9 14 Steel) that interact like an extended version of Rock-Paper-Scissors:
rlm@9 15 for example, the Fire type is strong against the Grass type but weak
rlm@9 16 against the Water type. In the table below, we've recorded the
rlm@9 17 relative strengths of each of the types in the Pok\eacute{}mon type
rlm@9 18 system; the number in each cell indicates how effective an attack of
rlm@9 19 the type in the row is against a Pok\eacute{}mon of the type in the
rlm@9 20 column. We call these numbers /susceptibilities/.
rlm@0 21
rlm@0 22 In the Pok\eacute{}mon games, only four susceptibility values (two,
rlm@0 23 one, one-half, and zero) occur. These numbers indicate particularly
rlm@0 24 high susceptibility, average susceptibility, particularly low
rlm@5 25 susceptibility, and no susceptibility (immunity).
rlm@0 26
rlm@10 27 - The susceptibility of Flying types /against/ Ground is 0, because Ground
rlm@5 28 attacks cannot hurt Flying pok\eacute{}mon at all. The damage that
rlm@5 29 a Ground type attack normally does is /multiplied/ by 0 when it is
rlm@10 30 used against a Flying type pok\eacute{}mon.
rlm@0 31
rlm@10 32 - The susceptibility of Fire types against Water attacks
rlm@5 33 is 2, because Water type attacks are strong against Fire type
rlm@5 34 Pok\eacute{}mon. The damage that a Water type attack normally does
rlm@5 35 is doubled when it is used against a Fire type pok\eacute{}mon.
rlm@0 36
rlm@10 37 - The susceptibility of Water types against Water attacks is
rlm@22 38 $\frac{1}{2}$, because Water type attacks are weak against Water
rlm@5 39 type Pok\eacute{}mon. The damage that a Water type attack normally
rlm@5 40 does is halved when it is used against a Water type
rlm@5 41 pok\eacute{}mon.
rlm@5 42
rlm@5 43 There are two pok\eacute{}mon type systems in use. The first is the
rlm@5 44 classic system which was used for the very first pok\eacute{}mon
rlm@5 45 games, Red, Yellow, and Blue. This old system was used from 1998 to
rlm@5 46 2000 in America, and is known as the /Generation I Type System/. The
rlm@5 47 modern pok\eacute{}mon type system was introduced in 2000 with the
rlm@5 48 introduction of pok\eacute{}mon Gold and Silver, and has been in use
rlm@5 49 ever since. It is called the /Generation II Type System/.
rlm@5 50
rlm@9 51 The definitions of the two Type Systems are included below.
rlm@5 52
rlm@7 53 * Generation I and II Type System Data
rlm@0 54
rlm@6 55 ** Generation II Type System
rlm@0 56 #+label: pokemon-matchups
rlm@0 57 #+tblname: pokemon-table-gen-two
rlm@0 58 | | normal | fire | water | electric | grass | ice | fighting | poison | ground | flying | psychic | bug | rock | ghost | dragon | dark | steel |
rlm@0 59 |----------+--------+------+-------+----------+-------+-----+----------+--------+--------+--------+---------+-----+------+-------+--------+------+-------|
rlm@0 60 | normal | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 0 | 1 | 1 | .5 |
rlm@0 61 | fire | 1 | .5 | .5 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 1 | 2 |
rlm@0 62 | water | 1 | 2 | .5 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | 1 |
rlm@0 63 | electric | 1 | 1 | 2 | .5 | .5 | 1 | 1 | 1 | 0 | 2 | 1 | 1 | 1 | 1 | .5 | 1 | 1 |
rlm@0 64 | grass | 1 | .5 | 2 | 1 | .5 | 1 | 1 | .5 | 2 | .5 | 1 | .5 | 2 | 1 | .5 | 1 | .5 |
rlm@0 65 | ice | 1 | .5 | .5 | 1 | 2 | .5 | 1 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 |
rlm@0 66 | fighting | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | .5 | .5 | .5 | 2 | 0 | 1 | 2 | 2 |
rlm@0 67 | poison | 1 | 1 | 1 | 1 | 2 | 1 | 1 | .5 | .5 | 1 | 1 | 1 | .5 | .5 | 1 | 1 | 0 |
rlm@0 68 | ground | 1 | 2 | 1 | 2 | .5 | 1 | 1 | 2 | 1 | 0 | 1 | .5 | 2 | 1 | 1 | 1 | 2 |
rlm@0 69 | flying | 1 | 1 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | 1 | 1 | .5 |
rlm@0 70 | psychic | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 1 | 1 | .5 | 1 | 1 | 1 | 1 | 0 | .5 |
rlm@0 71 | bug | 1 | .5 | 1 | 1 | 2 | 1 | .5 | .5 | 1 | .5 | 2 | 1 | 1 | .5 | 1 | 2 | .5 |
rlm@0 72 | rock | 1 | 2 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | .5 |
rlm@0 73 | ghost | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 1 | 2 | 1 | .5 | .5 |
rlm@0 74 | dragon | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | .5 |
rlm@0 75 | dark | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 2 | 1 | .5 | .5 |
rlm@0 76 | steel | 1 | .5 | .5 | .5 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | .5 |
rlm@0 77
rlm@6 78 The rows are attack types, while the columns are defense types. To
rlm@8 79 see the multiplier for a pok\eacute{}mon attack against a certain type, follow
rlm@6 80 the row for the attack type to the column of the defending type.
rlm@6 81
rlm@6 82 ** Generation I Type System
rlm@7 83 #+label: pokemon-matchups-gen-1
rlm@7 84 #+tblname: pokemon-table-gen-one
rlm@0 85 | | normal | fire | water | electric | grass | ice | fighting | poison | ground | flying | psychic | bug | rock | ghost | dragon |
rlm@0 86 |----------+--------+------+-------+----------+-------+-----+----------+--------+--------+--------+---------+-----+------+-------+--------|
rlm@0 87 | normal | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 0 | 1 |
rlm@0 88 | fire | 1 | .5 | .5 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | .5 |
rlm@0 89 | water | 1 | 2 | .5 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | 2 | 1 | .5 |
rlm@0 90 | electric | 1 | 1 | 2 | .5 | .5 | 1 | 1 | 1 | 0 | 2 | 1 | 1 | 1 | 1 | .5 |
rlm@0 91 | grass | 1 | .5 | 2 | 1 | .5 | 1 | 1 | .5 | 2 | .5 | 1 | .5 | 2 | 1 | .5 |
rlm@0 92 | ice | 1 | 1 | .5 | 1 | 2 | .5 | 1 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 2 |
rlm@0 93 | fighting | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | .5 | .5 | .5 | 2 | 0 | 1 |
rlm@0 94 | poison | 1 | 1 | 1 | 1 | 2 | 1 | 1 | .5 | .5 | 1 | 1 | 2 | .5 | .5 | 1 |
rlm@0 95 | ground | 1 | 2 | 1 | 2 | .5 | 1 | 1 | 2 | 1 | 0 | 1 | .5 | 2 | 1 | 1 |
rlm@0 96 | flying | 1 | 1 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | 1 |
rlm@0 97 | psychic | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 1 | 1 | .5 | 1 | 1 | 1 | 1 |
rlm@0 98 | bug | 1 | .5 | 1 | 1 | 2 | 1 | .5 | 2 | 1 | .5 | 2 | 1 | 1 | 0 | 1 |
rlm@0 99 | rock | 1 | 2 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 |
rlm@0 100 | ghost | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 2 | 1 |
rlm@0 101 | dragon | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 |
rlm@0 102
rlm@6 103
rlm@6 104 This is the old table from Generation I. The differences from
rlm@6 105 Generation II are:
rlm@7 106 - Dark and Steel types are missing (these were introduced in
rlm@7 107 Generation II).
rlm@7 108 - Bug is super-effective against Poison (not-very-effective in
rlm@7 109 Generation II).
rlm@7 110 - Poison is super-effective against Bug (normal in Generation II).
rlm@7 111 - Bug is regularly effective against Ghost (super-effective in
rlm@7 112 Generation II).
rlm@7 113 - Ice is normally effective against Fire, (not-very-effective in
rlm@6 114 Generation II).
rlm@9 115 - Ghost is completely ineffective against Psychic, even though the
rlm@9 116 pok\eacute{}mon anime ran [[http://bulbapedia.bulbagarden.net/wiki/EP022][a three-part series]] about how Ghost
rlm@9 117 pok\eacute{}mon are the best way to defeat Psychic pok\eacute{}mon,
rlm@9 118 and the Red, Blue, and Yellow games each have a character who
rlm@9 119 states "The only thing Psychic pok\eacute{}mon fear are Bugs and
rlm@10 120 Ghosts!" This is considered to be a programming glitch. Ghost is
rlm@9 121 super-effective against Psychic in Generation II.
rlm@6 122
rlm@0 123 * Representing the Data
rlm@0 124
rlm@7 125 After creating the Pok\eacute{}mon types namespace, we store the
rlm@7 126 tables of susceptibilities above in =pokemon-table-gen-one= and
rlm@0 127 =pokemon-table-gen-two=, each of which is a simple vector of
rlm@0 128 vectors. Because a vector of vectors can be cumbersome, we do not
rlm@0 129 access the tables directly; instead, we use the derivative structures
rlm@0 130 =attack-strengths= and =defense-strengths=, which are functions which
rlm@0 131 return hash-maps associating each row (respectively column) of the
rlm@0 132 table with its corresponding Pok\eacute{}mon type.
rlm@0 133
rlm@0 134
rlm@16 135 #+name: header
rlm@0 136 #+begin_src clojure :results silent
rlm@0 137 (ns pokemon.types
rlm@7 138 (:use clojure.set)
rlm@16 139 ;; (:use clojure.contrib.combinatorics)
rlm@16 140 (:use clojure.math.combinatorics)
rlm@16 141 (:use clojure.math.numeric-tower)
rlm@16 142 ;; (:use clojure.contrib.def)
rlm@13 143 (:use rlm.rlm-commands)
rlm@13 144 (:require rlm.map-utils))
rlm@0 145 #+end_src
rlm@0 146
rlm@16 147 #+name: data
rlm@0 148 #+begin_src clojure :results silent
rlm@0 149 (in-ns 'pokemon.types)
rlm@0 150 ;; record type strengths as a vector of vectors
rlm@7 151 ;; the variables pokemon-table-gen-one and pokemon-table-gen-two
rlm@7 152 ;; are replaced with the tables above when this file is tangled.
rlm@0 153 (def pokemon-gen-one pokemon-table-gen-one)
rlm@0 154 (def pokemon-gen-two pokemon-table-gen-two)
rlm@0 155
rlm@17 156 (defn type-names []
rlm@17 157 (vec (doall (map (comp keyword first) pokemon-gen-two))))
rlm@0 158
rlm@0 159 (defn attack-strengths []
rlm@0 160 (zipmap
rlm@0 161 (type-names)
rlm@0 162 (map (comp vec rest) pokemon-gen-two)))
rlm@0 163
rlm@0 164 (defn defense-strengths []
rlm@0 165 (zipmap (type-names)
rlm@0 166 (map
rlm@0 167 (apply juxt (map (attack-strengths) (type-names)))
rlm@0 168 (range (count (type-names))))))
rlm@0 169 #+end_src
rlm@0 170
rlm@7 171 The two statements
rlm@7 172
rlm@7 173 #+begin_src clojure :exports code
rlm@7 174 (def pokemon-gen-one pokemon-table-gen-one)
rlm@7 175 (def pokemon-gen-two pokemon-table-gen-two)
rlm@7 176 #+end_src
rlm@7 177
rlm@7 178 probably look weird. When the actual source file is created, those
rlm@7 179 variables are replaced with the data from the tables above.
rlm@7 180
rlm@7 181 See [[../src/pokemon/types.clj][types.clj]] to look at the final tangled output.
rlm@7 182
rlm@7 183
rlm@0 184 #+begin_src clojure :results output :exports both
rlm@0 185 (clojure.pprint/pprint pokemon.types/pokemon-gen-two)
rlm@0 186 #+end_src
rlm@0 187
rlm@0 188 #+results:
rlm@0 189 #+begin_example
rlm@0 190 (("normal" 1 1 1 1 1 1 1 1 1 1 1 1 0.5 0 1 1 0.5)
rlm@0 191 ("fire" 1 0.5 0.5 1 2 2 1 1 1 1 1 2 0.5 1 0.5 1 2)
rlm@0 192 ("water" 1 2 0.5 1 0.5 1 1 1 2 1 1 1 2 1 0.5 1 1)
rlm@0 193 ("electric" 1 1 2 0.5 0.5 1 1 1 0 2 1 1 1 1 0.5 1 1)
rlm@0 194 ("grass" 1 0.5 2 1 0.5 1 1 0.5 2 0.5 1 0.5 2 1 0.5 1 0.5)
rlm@0 195 ("ice" 1 0.5 0.5 1 2 0.5 1 1 2 2 1 1 1 1 2 1 0.5)
rlm@0 196 ("fighting" 2 1 1 1 1 2 1 0.5 1 0.5 0.5 0.5 2 0 1 2 2)
rlm@0 197 ("poison" 1 1 1 1 2 1 1 0.5 0.5 1 1 1 0.5 0.5 1 1 0)
rlm@0 198 ("ground" 1 2 1 2 0.5 1 1 2 1 0 1 0.5 2 1 1 1 2)
rlm@0 199 ("flying" 1 1 1 0.5 2 1 2 1 1 1 1 2 0.5 1 1 1 0.5)
rlm@0 200 ("psychic" 1 1 1 1 1 1 2 2 1 1 0.5 1 1 1 1 0 0.5)
rlm@0 201 ("bug" 1 0.5 1 1 2 1 0.5 0.5 1 0.5 2 1 1 0.5 1 2 0.5)
rlm@0 202 ("rock" 1 2 1 1 1 2 0.5 1 0.5 2 1 2 1 1 1 1 0.5)
rlm@0 203 ("ghost" 0 1 1 1 1 1 1 1 1 1 2 1 1 2 1 0.5 0.5)
rlm@0 204 ("dragon" 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 0.5)
rlm@0 205 ("dark" 1 1 1 1 1 1 0.5 1 1 1 2 1 1 2 1 0.5 0.5)
rlm@0 206 ("steel" 1 0.5 0.5 0.5 1 2 1 1 1 1 1 1 2 1 1 1 0.5))
rlm@0 207 #+end_example
rlm@0 208
rlm@7 209 =pokemon-gen-two= is a simple list-of-lists data structure.
rlm@0 210
rlm@0 211 #+begin_src clojure :results output :exports both
rlm@0 212 (clojure.pprint/pprint (pokemon.types/defense-strengths))
rlm@0 213 #+end_src
rlm@0 214
rlm@0 215 #+results:
rlm@0 216 #+begin_example
rlm@0 217 {:water [1 0.5 0.5 2 2 0.5 1 1 1 1 1 1 1 1 1 1 0.5],
rlm@0 218 :psychic [1 1 1 1 1 1 0.5 1 1 1 0.5 2 1 2 1 2 1],
rlm@0 219 :dragon [1 0.5 0.5 0.5 0.5 2 1 1 1 1 1 1 1 1 2 1 1],
rlm@0 220 :fire [1 0.5 2 1 0.5 0.5 1 1 2 1 1 0.5 2 1 1 1 0.5],
rlm@0 221 :ice [1 2 1 1 1 0.5 2 1 1 1 1 1 2 1 1 1 2],
rlm@0 222 :grass [1 2 0.5 0.5 0.5 2 1 2 0.5 2 1 2 1 1 1 1 1],
rlm@0 223 :ghost [0 1 1 1 1 1 0 0.5 1 1 1 0.5 1 2 1 2 1],
rlm@0 224 :poison [1 1 1 1 0.5 1 0.5 0.5 2 1 2 0.5 1 1 1 1 1],
rlm@0 225 :flying [1 1 1 2 0.5 2 0.5 1 0 1 1 0.5 2 1 1 1 1],
rlm@0 226 :normal [1 1 1 1 1 1 2 1 1 1 1 1 1 0 1 1 1],
rlm@0 227 :rock [0.5 0.5 2 1 2 1 2 0.5 2 0.5 1 1 1 1 1 1 2],
rlm@0 228 :electric [1 1 1 0.5 1 1 1 1 2 0.5 1 1 1 1 1 1 0.5],
rlm@0 229 :ground [1 1 2 0 2 2 1 0.5 1 1 1 1 0.5 1 1 1 1],
rlm@0 230 :fighting [1 1 1 1 1 1 1 1 1 2 2 0.5 0.5 1 1 0.5 1],
rlm@0 231 :dark [1 1 1 1 1 1 2 1 1 1 0 2 1 0.5 1 0.5 1],
rlm@0 232 :steel [0.5 2 1 1 0.5 0.5 2 0 2 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5],
rlm@0 233 :bug [1 2 1 1 0.5 1 0.5 1 0.5 2 1 1 2 1 1 1 1]}
rlm@0 234 #+end_example
rlm@0 235
rlm@7 236 =defense-strengths= is a more convenient form of =pokemon-gen-two=,
rlm@7 237 with key/value pair access.
rlm@0 238
rlm@0 239 * Interfacing with the Data
rlm@7 240
rlm@7 241 In the pok\eacute{}mon games, a pok\eacute{}mon can have up to two
rlm@9 242 types at the same time. For example, [[http://bulbapedia.bulbagarden.net/wiki/Zapdos][Zapdos]], the fearsome legendary
rlm@9 243 bird that can control lightning, has both the Electric and Flying
rlm@9 244 types. A pok\eacute{}mon with more than one type gains the advantages
rlm@10 245 and disadvantages of both types. The susceptibilities of each type are
rlm@7 246 multiplied together to produce the hybrid type's susceptibilities. For
rlm@7 247 example, Electric is weak to Ground (susceptibility of 2), but Flying
rlm@10 248 is immune to Ground (susceptibility of 0). [[http://bulbapedia.bulbagarden.net/wiki/Zapdos][Zapdos']] type,
rlm@10 249 Electric/Flying, is immune to Ground because $2 \times 0 = 0$.
rlm@7 250
rlm@16 251 #+name: types
rlm@0 252 #+begin_src clojure :results silent
rlm@0 253 (in-ns 'pokemon.types)
rlm@0 254
rlm@0 255 (defn multitypes "All combinations of up to n types" [n]
rlm@0 256 (vec
rlm@0 257 (map vec
rlm@0 258 (reduce concat
rlm@0 259 (map (partial combinations (type-names))
rlm@0 260 (range 1 (inc n)))))))
rlm@0 261
rlm@7 262 (defn susceptibility
rlm@0 263 "Hash-map of the susceptibilities of the given type combination
rlm@0 264 to each type of attack"
rlm@0 265 [pkmn-types]
rlm@0 266 (rlm.map-utils/map-vals
rlm@0 267 clojure.core/rationalize
rlm@0 268 (apply hash-map
rlm@0 269 (interleave (type-names)
rlm@0 270 (apply (partial map *)
rlm@0 271 (map (defense-strengths) pkmn-types))))))
rlm@0 272
rlm@7 273 (defn susceptance
rlm@0 274 "The cumulative susceptibility of the given type combination"
rlm@0 275 [types]
rlm@7 276 (reduce + (map #(expt % 2) (vals (susceptibility types)))))
rlm@0 277 #+end_src
rlm@0 278
rlm@10 279 Now we can work out the susceptibility of [[http://bulbapedia.bulbagarden.net/wiki/Zapdos][Zapdos]] automatically.
rlm@7 280
rlm@7 281 Electric is weak to Ground.
rlm@7 282 #+begin_src clojure :exports both
rlm@7 283 (:ground (pokemon.types/susceptibility [:electric]))
rlm@7 284 #+end_src
rlm@7 285
rlm@7 286 #+results:
rlm@7 287 : 2
rlm@7 288
rlm@7 289 Flying is immune to Ground.
rlm@7 290 #+begin_src clojure :exports both
rlm@7 291 (:ground (pokemon.types/susceptibility [:flying]))
rlm@7 292 #+end_src
rlm@7 293
rlm@7 294 #+results:
rlm@7 295 : 0
rlm@7 296
rlm@7 297 Together, they are immune to Ground.
rlm@7 298 #+begin_src clojure :exports both
rlm@7 299 (:ground (pokemon.types/susceptibility [:electric :flying]))
rlm@7 300 #+end_src
rlm@7 301
rlm@7 302 #+results:
rlm@7 303 : 0
rlm@7 304
rlm@7 305
rlm@7 306
rlm@7 307
rlm@0 308 * Best-First Search
rlm@0 309
rlm@0 310 I'd like to find type combinations that are interesting, but the total
rlm@0 311 number of combinations gets huge as we begin to consider more
rlm@0 312 types. For example, the total possible number of type combinations
rlm@7 313 given just 8 possible types is: 17^{8} = 6,975,757,441 combinations.
rlm@0 314 Therefore, it's prudent to use search.
rlm@0 315
rlm@0 316 These functions are a simple implementation of best-first search in
rlm@15 317 clojure. The idea is to start off with a collection of nodes and some
rlm@15 318 way of finding the best node, and to always expand the best node at
rlm@15 319 every step.
rlm@0 320
rlm@16 321 #+name: search
rlm@0 322 #+begin_src clojure :results silent
rlm@0 323 (in-ns 'pokemon.types)
rlm@0 324
rlm@0 325 (defn comparatize
rlm@17 326 "Define a comparator which uses the numerical outputs of
rlm@17 327 fn as its criterion. Objects are sorted in increasing
rlm@17 328 numerical order. Objects with the same fn-value are
rlm@17 329 further compared by clojure.core/compare."
rlm@0 330 [fun]
rlm@0 331 (fn [a b]
rlm@0 332 (let [val-a (fun a)
rlm@0 333 val-b (fun b)]
rlm@0 334 (cond
rlm@0 335 ;; if the function cannot differentiate the two values
rlm@0 336 ;; then compare the two values using clojure.core/compare
rlm@0 337 (= val-a val-b) (compare a b)
rlm@0 338 true
rlm@0 339 ;; LOWER values of the function are preferred
rlm@0 340 (compare (- val-a val-b) 0)))))
rlm@0 341
rlm@16 342 (defn best-first-step [successors [visited unvisited]]
rlm@0 343 (cond (empty? unvisited) nil
rlm@0 344 true
rlm@0 345 (let [best-node (first unvisited)
rlm@0 346 visited* (conj visited best-node)
rlm@0 347 unvisited*
rlm@0 348 (difference
rlm@0 349 (union unvisited (set (successors best-node)))
rlm@0 350 visited*)]
rlm@0 351 (println best-node)
rlm@0 352 [visited* unvisited*])))
rlm@16 353 (alter-var-root #'best-first-step memoize)
rlm@0 354
rlm@0 355 ;; memoize partial from core so that for example
rlm@0 356 ;; (= (partial + 1) (partial + 1))
rlm@0 357 ;; this way, best first search can take advantage of the memoization
rlm@0 358 ;; of best-first step
rlm@0 359 (undef partial)
rlm@0 360 (def partial (memoize clojure.core/partial))
rlm@0 361
rlm@0 362 (defn best-first-search
rlm@0 363 "Searches through a network of alternatives, pursuing
rlm@17 364 initially-promising positions first. Comparator defines
rlm@17 365 which positions are more promising, successors produces a
rlm@17 366 list of improved positions from the given position (if
rlm@17 367 any exist), and initial-nodes is a list of starting
rlm@17 368 positions. Returns a lazy sequence of search results
rlm@17 369 [visited-nodes unvisited-nodes], which terminates when
rlm@17 370 there are no remaining unvisited positions."
rlm@0 371 [comparator successors initial-nodes]
rlm@0 372 (let [initial-nodes
rlm@0 373 (apply (partial sorted-set-by comparator) initial-nodes)
rlm@0 374 initial-visited-nodes (sorted-set-by comparator)
rlm@0 375 step (partial best-first-step successors)]
rlm@0 376 (take-while
rlm@0 377 (comp not nil?)
rlm@0 378 (iterate step [initial-visited-nodes initial-nodes]))))
rlm@0 379
rlm@0 380 #+end_src
rlm@0 381
rlm@0 382
rlm@0 383 Now that we have a basic best-first-search, it's convenient to write a
rlm@8 384 few pok\eacute{}mon-type specific convenience functions.
rlm@0 385
rlm@16 386 #+name: pokemon-search
rlm@0 387 #+begin_src clojure :results silent
rlm@0 388 (in-ns 'pokemon.types)
rlm@16 389 (def type-compare
rlm@16 390 "compare two type combinations W.R.T. their susceptibilities"
rlm@16 391 (comparatize susceptance))
rlm@0 392
rlm@0 393 (defn type-successors
rlm@17 394 "Return the set of types that can be made by appending a
rlm@17 395 single type to the given combination."
rlm@0 396 [type]
rlm@0 397 (if (nil? type) '()
rlm@0 398 (set (map (comp vec sort (partial into type)) (multitypes 1)))))
rlm@0 399
rlm@0 400 (defn immortal?
rlm@17 401 "A type combo is immortal if it is resistant or
rlm@17 402 invulnerable to every pokemon type. This is because that
rlm@17 403 set of types can just be repeated to achieve as low a
rlm@17 404 susceptance as desired"
rlm@0 405 [type]
rlm@0 406 (every? (partial > 1) (vals (susceptibility type))))
rlm@0 407
rlm@0 408 (defn type-successors*
rlm@17 409 "Stop expanding a type if it's immortal, or if it is
rlm@17 410 longer than or equal to limit-size. Also, only return type
rlm@17 411 additions that are strictly better than the initial type."
rlm@0 412 [limit-size type]
rlm@0 413 (if (or (<= limit-size (count type)) (immortal? type)) '()
rlm@17 414 (set (filter #(< 0 (type-compare type %))
rlm@17 415 (type-successors type)))))
rlm@0 416
rlm@0 417 (defn pokemon-type-search
rlm@17 418 "Search among type-combos no greater than length n,
rlm@17 419 limited by limit steps of best-first-search."
rlm@0 420 ([n] (pokemon-type-search n Integer/MAX_VALUE))
rlm@0 421 ([n limit]
rlm@0 422 (first (last
rlm@0 423 (take
rlm@0 424 limit
rlm@0 425 (best-first-search
rlm@0 426 type-compare
rlm@0 427 (partial type-successors* n)
rlm@0 428 (multitypes 1)))))))
rlm@0 429
rlm@16 430 (def immortals
rlm@16 431 "find all the immortal pokemon types."
rlm@16 432 (comp (partial filter immortal?) pokemon-type-search))
rlm@0 433
rlm@0 434 #+end_src
rlm@0 435
rlm@0 436 Because there are so many type combinations, it's important to narrow
rlm@0 437 down the results as much as possible. That is why =type-successors*=
rlm@0 438 only returns types that are actually better than the type it is given.
rlm@0 439
rlm@0 440 Best-first search can get caught optimizing a single type forever, so
rlm@0 441 it's also important to limit the search space to be finite by setting
rlm@0 442 an upper bound on the length of a type combo.
rlm@0 443
rlm@0 444 * Results
rlm@0 445 ** The best dual-type combo
rlm@0 446
rlm@0 447 #+begin_src clojure :results cache verbatim :exports both
rlm@0 448 (first (pokemon.types/pokemon-type-search 2))
rlm@0 449 #+end_src
rlm@0 450
rlm@0 451 #+results:
rlm@0 452 : [:dark :ghost]
rlm@0 453
rlm@0 454 Dark and Ghost, which additionally has the property of having no
rlm@9 455 weaknesses to any other type, is the best type combo in terms of
rlm@9 456 susceptance.
rlm@0 457
rlm@0 458 The Dark and Steel types were introduced many years after
rlm@0 459 pok\eacute{}mon started. In addition to the additional types, the
rlm@0 460 pok\eacute{}mon games gained a few new rules concerning some of the
rlm@9 461 matchups of the original types. Therefore, it's also interesting to
rlm@9 462 see what type combination was most powerful before those types and new
rlm@9 463 rules were introduced.
rlm@0 464
rlm@0 465 The easiest way to do this with my setup is to just rebind the
rlm@0 466 =pokemon-gen-two= table to the =pokemon-gen-one= table. Since
rlm@0 467 everything that references this variable is a function and we're not
rlm@0 468 doing anything too crazy with lazy-sequences and late-binding, this
rlm@0 469 simple macro will do the job.
rlm@0 470
rlm@16 471 #+name: old-school
rlm@0 472 #+begin_src clojure :results silent
rlm@0 473 (in-ns 'pokemon.types)
rlm@0 474
rlm@0 475 (defmacro old-school
rlm@0 476 [& forms]
rlm@0 477 `(binding [pokemon-gen-two pokemon-gen-one] ~@forms))
rlm@0 478 #+end_src
rlm@0 479
rlm@0 480 Using the =old-school= macro, it's easy to find answers for the
rlm@0 481 original 15 pokemon types as well as the expanded pokemon types
rlm@0 482 introduced later.
rlm@0 483
rlm@0 484 #+begin_src clojure :results verbatim :exports both :cache yes
rlm@0 485 (pokemon.types/old-school (first (pokemon.types/pokemon-type-search 2)))
rlm@0 486 #+end_src
rlm@0 487
rlm@0 488 #+results[f43470fdf460ed546e9c57879abc9eda56da129f]:
rlm@0 489 : [:ghost :psychic]
rlm@0 490
rlm@0 491 Ghost and Psychic also manages to have no weaknesses to any of the original
rlm@8 492 types, using the old Generation I rules.
rlm@0 493
rlm@0 494 #+begin_src clojure :results output :exports both
rlm@0 495 (clojure.pprint/pprint
rlm@0 496 (pokemon.types/old-school
rlm@0 497 (pokemon.types/susceptibility [:ghost :psychic])))
rlm@0 498 #+end_src
rlm@0 499
rlm@0 500 #+results:
rlm@0 501 #+begin_example
rlm@0 502 {:water 1,
rlm@0 503 :psychic 1/2,
rlm@0 504 :dragon 1,
rlm@0 505 :fire 1,
rlm@0 506 :ice 1,
rlm@0 507 :grass 1,
rlm@0 508 :ghost 0,
rlm@0 509 :poison 1/2,
rlm@0 510 :flying 1,
rlm@0 511 :normal 0,
rlm@0 512 :rock 1,
rlm@0 513 :electric 1,
rlm@0 514 :ground 1,
rlm@0 515 :fighting 0,
rlm@0 516 :bug 0}
rlm@0 517 #+end_example
rlm@0 518
rlm@0 519 ** An Immortal Type
rlm@0 520 It's possible to quickly find an immortal type by giving the search
rlm@0 521 a long enough maximum type length. 50 rounds of search with a max
rlm@0 522 type limit of 10 is enough to find an immortal type.
rlm@0 523
rlm@0 524 #+begin_src clojure :results scalar :exports both
rlm@0 525 (first (pokemon.types/pokemon-type-search 10 50))
rlm@0 526 #+end_src
rlm@0 527
rlm@0 528 #+results:
rlm@0 529 : [:dragon :fire :flying :ghost :grass :ground :steel :steel :water :water]
rlm@0 530
rlm@0 531
rlm@0 532 #+begin_src clojure :results output :exports both
rlm@0 533 (clojure.pprint/pprint
rlm@0 534 (pokemon.types/susceptibility
rlm@17 535 [:dragon :fire :flying :ghost :grass :ground
rlm@17 536 :steel :steel :water :water]))
rlm@0 537 #+end_src
rlm@0 538
rlm@0 539 #+results:
rlm@0 540 #+begin_example
rlm@0 541 {:water 1/4,
rlm@0 542 :psychic 1/4,
rlm@0 543 :dragon 1/2,
rlm@0 544 :fire 1/2,
rlm@0 545 :ice 1/2,
rlm@0 546 :grass 1/8,
rlm@0 547 :ghost 1/2,
rlm@0 548 :poison 0,
rlm@0 549 :flying 1/2,
rlm@0 550 :normal 0,
rlm@0 551 :rock 1/2,
rlm@0 552 :electric 0,
rlm@0 553 :ground 0,
rlm@0 554 :fighting 0,
rlm@0 555 :dark 1/2,
rlm@0 556 :steel 1/32,
rlm@0 557 :bug 1/16}
rlm@0 558 #+end_example
rlm@0 559
rlm@0 560 ** Explanations for Common Pok\eacute{}mon Strategies
rlm@0 561
rlm@8 562 Many people start out a battle with either a Normal pok\eacute{}mon or an
rlm@8 563 Electric pok\eacute{}mon. Here's some justification for that choice.
rlm@0 564
rlm@16 565 #+name: weaknesses
rlm@0 566 #+begin_src clojure :results silent
rlm@0 567 (in-ns 'pokemon.types)
rlm@0 568 (defn critical-weaknesses [type]
rlm@0 569 (count (filter #(> % 1) (vals (susceptibility type)))))
rlm@0 570 #+end_src
rlm@0 571
rlm@0 572 #+begin_src clojure :exports both :results output
rlm@0 573 (clojure.pprint/pprint
rlm@0 574 (sort-by pokemon.types/critical-weaknesses (pokemon.types/multitypes 1)))
rlm@0 575 #+end_src
rlm@0 576
rlm@0 577 #+results:
rlm@0 578 #+begin_example
rlm@0 579 ([:normal]
rlm@0 580 [:electric]
rlm@0 581 [:water]
rlm@0 582 [:fighting]
rlm@0 583 [:poison]
rlm@0 584 [:ghost]
rlm@0 585 [:dragon]
rlm@0 586 [:dark]
rlm@0 587 [:fire]
rlm@0 588 [:ground]
rlm@0 589 [:flying]
rlm@0 590 [:psychic]
rlm@0 591 [:bug]
rlm@0 592 [:steel]
rlm@0 593 [:ice]
rlm@0 594 [:grass]
rlm@0 595 [:rock])
rlm@0 596 #+end_example
rlm@0 597
rlm@0 598 Electric and Normal are among the best types with which to start the
rlm@0 599 game, since they have the fewest weaknesses among all the types.
rlm@0 600
rlm@9 601 At the beginning of the pok\eacute{}mon games, players are given a
rlm@9 602 choice between the Fire pok\eacute{}mon [[http://bulbapedia.bulbagarden.net/wiki/Charmander][Charmander]], the Water
rlm@9 603 pok\eacute{}mon [[http://bulbapedia.bulbagarden.net/wiki/Squirtle][Squirtle]], or the Grass/Poison pok\eacute{}mon
rlm@9 604 [[http://bulbapedia.bulbagarden.net/wiki/Bulbasaur][Bulbasaur]].
rlm@0 605
rlm@0 606 #+begin_src clojure :exports both :results verbatim
rlm@0 607 (sort-by pokemon.types/susceptance [[:fire] [:water] [:grass :poison]])
rlm@0 608 #+end_src
rlm@0 609
rlm@0 610 #+results:
rlm@0 611 : ([:water] [:fire] [:grass :poison])
rlm@0 612
rlm@9 613 As can be seen, the Water pok\eacute{}mon [[http://bulbapedia.bulbagarden.net/wiki/Squirtle][Squirtle]] is the most solid
rlm@0 614 choice starting out, insofar as susceptance is concerned.
rlm@0 615
rlm@0 616 ** The Worst Pok\eacute{}mon Types
rlm@0 617
rlm@16 618 #+name: weak-types
rlm@0 619 #+begin_src clojure :results silent
rlm@0 620 (in-ns 'pokemon.types)
rlm@0 621
rlm@0 622 (defn type-compare-weak
rlm@0 623 "compare first by total number of critical-weaknesses,
rlm@0 624 then by overall susceptance, favoring weaker types."
rlm@0 625 [type-1 type-2]
rlm@0 626 (let [measure (memoize (juxt critical-weaknesses susceptance))]
rlm@0 627 (if (= (measure type-2) (measure type-1))
rlm@0 628 (compare type-2 type-1)
rlm@0 629 (compare (measure type-2) (measure type-1)))))
rlm@0 630
rlm@0 631 (defn resistant?
rlm@0 632 "might as well get rid of types that are resistant to any type"
rlm@0 633 [type]
rlm@0 634 (not (every? #(< 0 %) (vals (susceptibility type)))))
rlm@0 635
rlm@0 636 (defn type-successors-weak
rlm@8 637 "Generate ways to weaken the given type combination. Discard type
rlm@8 638 combinations that either strengthen the given type combination or
rlm@8 639 that make it stronger"
rlm@0 640 [limit type]
rlm@0 641 (set (if (<= limit (count type)) '()
rlm@0 642 (filter #(< 0 (type-compare-weak type %))
rlm@0 643 (remove resistant? (type-successors type))))))
rlm@0 644
rlm@0 645 (defn pokemon-type-search-weak
rlm@0 646 "Search among type-combos no greater than length n, limited by limit
rlm@8 647 steps of best-first-search. Find the weakest type combination
rlm@8 648 possible in terms of susceptance."
rlm@0 649 ([n] (pokemon-type-search-weak n Integer/MAX_VALUE))
rlm@0 650 ([n limit]
rlm@0 651 (first (last
rlm@0 652 (take
rlm@0 653 limit
rlm@0 654 (best-first-search
rlm@0 655 type-compare-weak
rlm@0 656 (partial type-successors-weak n)
rlm@0 657 (multitypes 1)))))))
rlm@0 658 #+end_src
rlm@0 659
rlm@0 660
rlm@0 661 #+begin_src clojure :results scalar :exports both
rlm@0 662 (first (pokemon.types/pokemon-type-search-weak 1))
rlm@0 663 #+end_src
rlm@0 664
rlm@0 665 #+results:
rlm@0 666 : [:rock]
rlm@0 667
rlm@0 668 Poor Rock. It's just not that good a type. Maybe this is why Brock
rlm@0 669 (who has rock pok\eacute{}mon) is the first gym leader in the games.
rlm@0 670
rlm@0 671 #+begin_src clojure :results scalar cache :exports both
rlm@0 672 (first (pokemon.types/pokemon-type-search-weak 2))
rlm@0 673 #+end_src
rlm@0 674
rlm@0 675 #+results:
rlm@0 676 : [:grass :ice]
rlm@0 677
rlm@0 678 # ;;bonus convergently immortal type combo
rlm@0 679 # (susceptance (vec (concat (repeat 150 :water) (repeat 50 :poison) (repeat 50 :steel) [:ghost :normal :flying :ground :dark])))
rlm@0 680
rlm@0 681 #+begin_src clojure :results output :exports both
rlm@0 682 (clojure.pprint/pprint
rlm@0 683 (pokemon.types/susceptibility [:grass :ice]))
rlm@0 684 #+end_src
rlm@0 685
rlm@0 686 #+results:
rlm@0 687 #+begin_example
rlm@0 688 {:water 1/2,
rlm@0 689 :psychic 1,
rlm@0 690 :dragon 1,
rlm@0 691 :fire 4,
rlm@0 692 :ice 1,
rlm@0 693 :grass 1/2,
rlm@0 694 :ghost 1,
rlm@0 695 :poison 2,
rlm@0 696 :flying 2,
rlm@0 697 :normal 1,
rlm@0 698 :rock 2,
rlm@0 699 :electric 1/2,
rlm@0 700 :ground 1/2,
rlm@0 701 :fighting 2,
rlm@0 702 :dark 1,
rlm@0 703 :steel 2,
rlm@0 704 :bug 2}
rlm@0 705 #+end_example
rlm@0 706
rlm@0 707 This miserable combination is weak to 6 types and double-weak to
rlm@0 708 Fire. No pok\eacute{}mon in the games actually has this type.
rlm@0 709
rlm@0 710 * Conclusion
rlm@0 711
rlm@0 712 Searching for a type that is weak to everything takes a very long time
rlm@0 713 and fails to reveal any results. That's the problem with a search
rlm@0 714 over this large problem space --- if there's an easy solution, the
rlm@0 715 search will find it quickly, but it can be very hard to determine
rlm@0 716 whether there is actually a solution.
rlm@0 717
rlm@0 718 In the [[./lpsolve.org][next installment]], I'll use =lp_solve= to solve this problem in
rlm@0 719 a different way.
rlm@0 720
rlm@0 721 * COMMENT main program
rlm@0 722 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :exports none
rlm@0 723 <<header>>
rlm@0 724 #+end_src
rlm@0 725
rlm@0 726 ## this is necessary to define pokemon-table inside the source code.
rlm@0 727
rlm@0 728 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :var pokemon-table-gen-one=pokemon-table-gen-one :var pokemon-table-gen-two=pokemon-table-gen-two :exports none
rlm@0 729 <<data>>
rlm@0 730 #+end_src
rlm@0 731
rlm@0 732 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :exports none
rlm@0 733 <<types>>
rlm@0 734 <<search>>
rlm@0 735 <<pokemon-search>>
rlm@0 736 <<old-school>>
rlm@0 737 <<weaknesses>>
rlm@0 738 <<weak-types>>
rlm@0 739 #+end_src
rlm@0 740
rlm@0 741
rlm@0 742
rlm@0 743
rlm@0 744
rlm@0 745
rlm@0 746
rlm@0 747
rlm@0 748
rlm@0 749
rlm@0 750
rlm@0 751
rlm@0 752
rlm@0 753
rlm@0 754
rlm@0 755
rlm@0 756
rlm@0 757