Mercurial > pokemon-types
view org/types.org @ 16:7698e9bdff2b
upgraded pokemon-types to clojure version 1.3
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Mon, 06 Aug 2012 17:22:39 -0400 |
parents | da4c47650d38 |
children | 0f6ace87343a |
line wrap: on
line source
1 #+TITLE: Best-First Search for Effective Pokemon Types2 #+AUTHOR: Robert McIntyre & Dylan Holmes3 #+EMAIL: rlm@mit.edu4 #+description: Finding interesting pokemon type combinations through Best-First search in clojure.5 #+keywords: Pokemon, clojure, best-first search, optimization6 #+SETUPFILE: ../../aurellem/org/setup.org7 #+INCLUDE: ../../aurellem/org/level-0.org9 * The Pok\eacute{}mon Type System11 The Pok\eacute{}mon type system consists of seventeen different12 /types/ (Rock, Grass, Ice, Psychic, Ground, Bug, Flying, Fire,13 Fighting, Dark, Dragon, Poison, Water, Ghost, Normal, Electric, and14 Steel) that interact like an extended version of Rock-Paper-Scissors:15 for example, the Fire type is strong against the Grass type but weak16 against the Water type. In the table below, we've recorded the17 relative strengths of each of the types in the Pok\eacute{}mon type18 system; the number in each cell indicates how effective an attack of19 the type in the row is against a Pok\eacute{}mon of the type in the20 column. We call these numbers /susceptibilities/.22 In the Pok\eacute{}mon games, only four susceptibility values (two,23 one, one-half, and zero) occur. These numbers indicate particularly24 high susceptibility, average susceptibility, particularly low25 susceptibility, and no susceptibility (immunity).27 - The susceptibility of Flying types /against/ Ground is 0, because Ground28 attacks cannot hurt Flying pok\eacute{}mon at all. The damage that29 a Ground type attack normally does is /multiplied/ by 0 when it is30 used against a Flying type pok\eacute{}mon.32 - The susceptibility of Fire types against Water attacks33 is 2, because Water type attacks are strong against Fire type34 Pok\eacute{}mon. The damage that a Water type attack normally does35 is doubled when it is used against a Fire type pok\eacute{}mon.37 - The susceptibility of Water types against Water attacks is38 $\frac{1}{2}$, because Water type attacks are strong against Water39 type Pok\eacute{}mon. The damage that a Water type attack normally40 does is halved when it is used against a Water type41 pok\eacute{}mon.43 There are two pok\eacute{}mon type systems in use. The first is the44 classic system which was used for the very first pok\eacute{}mon45 games, Red, Yellow, and Blue. This old system was used from 1998 to46 2000 in America, and is known as the /Generation I Type System/. The47 modern pok\eacute{}mon type system was introduced in 2000 with the48 introduction of pok\eacute{}mon Gold and Silver, and has been in use49 ever since. It is called the /Generation II Type System/.51 The definitions of the two Type Systems are included below.53 * Generation I and II Type System Data55 ** Generation II Type System56 #+label: pokemon-matchups57 #+tblname: pokemon-table-gen-two58 | | normal | fire | water | electric | grass | ice | fighting | poison | ground | flying | psychic | bug | rock | ghost | dragon | dark | steel |59 |----------+--------+------+-------+----------+-------+-----+----------+--------+--------+--------+---------+-----+------+-------+--------+------+-------|60 | normal | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 0 | 1 | 1 | .5 |61 | fire | 1 | .5 | .5 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 1 | 2 |62 | water | 1 | 2 | .5 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | 1 |63 | electric | 1 | 1 | 2 | .5 | .5 | 1 | 1 | 1 | 0 | 2 | 1 | 1 | 1 | 1 | .5 | 1 | 1 |64 | grass | 1 | .5 | 2 | 1 | .5 | 1 | 1 | .5 | 2 | .5 | 1 | .5 | 2 | 1 | .5 | 1 | .5 |65 | ice | 1 | .5 | .5 | 1 | 2 | .5 | 1 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 |66 | fighting | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | .5 | .5 | .5 | 2 | 0 | 1 | 2 | 2 |67 | poison | 1 | 1 | 1 | 1 | 2 | 1 | 1 | .5 | .5 | 1 | 1 | 1 | .5 | .5 | 1 | 1 | 0 |68 | ground | 1 | 2 | 1 | 2 | .5 | 1 | 1 | 2 | 1 | 0 | 1 | .5 | 2 | 1 | 1 | 1 | 2 |69 | flying | 1 | 1 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | 1 | 1 | .5 |70 | psychic | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 1 | 1 | .5 | 1 | 1 | 1 | 1 | 0 | .5 |71 | bug | 1 | .5 | 1 | 1 | 2 | 1 | .5 | .5 | 1 | .5 | 2 | 1 | 1 | .5 | 1 | 2 | .5 |72 | rock | 1 | 2 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | .5 |73 | ghost | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 1 | 2 | 1 | .5 | .5 |74 | dragon | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | .5 |75 | dark | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 2 | 1 | .5 | .5 |76 | steel | 1 | .5 | .5 | .5 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | .5 |78 The rows are attack types, while the columns are defense types. To79 see the multiplier for a pok\eacute{}mon attack against a certain type, follow80 the row for the attack type to the column of the defending type.82 ** Generation I Type System83 #+label: pokemon-matchups-gen-184 #+tblname: pokemon-table-gen-one85 | | normal | fire | water | electric | grass | ice | fighting | poison | ground | flying | psychic | bug | rock | ghost | dragon |86 |----------+--------+------+-------+----------+-------+-----+----------+--------+--------+--------+---------+-----+------+-------+--------|87 | normal | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 0 | 1 |88 | fire | 1 | .5 | .5 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | .5 |89 | water | 1 | 2 | .5 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | 2 | 1 | .5 |90 | electric | 1 | 1 | 2 | .5 | .5 | 1 | 1 | 1 | 0 | 2 | 1 | 1 | 1 | 1 | .5 |91 | grass | 1 | .5 | 2 | 1 | .5 | 1 | 1 | .5 | 2 | .5 | 1 | .5 | 2 | 1 | .5 |92 | ice | 1 | 1 | .5 | 1 | 2 | .5 | 1 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 2 |93 | fighting | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | .5 | .5 | .5 | 2 | 0 | 1 |94 | poison | 1 | 1 | 1 | 1 | 2 | 1 | 1 | .5 | .5 | 1 | 1 | 2 | .5 | .5 | 1 |95 | ground | 1 | 2 | 1 | 2 | .5 | 1 | 1 | 2 | 1 | 0 | 1 | .5 | 2 | 1 | 1 |96 | flying | 1 | 1 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | 1 |97 | psychic | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 1 | 1 | .5 | 1 | 1 | 1 | 1 |98 | bug | 1 | .5 | 1 | 1 | 2 | 1 | .5 | 2 | 1 | .5 | 2 | 1 | 1 | 0 | 1 |99 | rock | 1 | 2 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 |100 | ghost | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 2 | 1 |101 | dragon | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 |104 This is the old table from Generation I. The differences from105 Generation II are:106 - Dark and Steel types are missing (these were introduced in107 Generation II).108 - Bug is super-effective against Poison (not-very-effective in109 Generation II).110 - Poison is super-effective against Bug (normal in Generation II).111 - Bug is regularly effective against Ghost (super-effective in112 Generation II).113 - Ice is normally effective against Fire, (not-very-effective in114 Generation II).115 - Ghost is completely ineffective against Psychic, even though the116 pok\eacute{}mon anime ran [[http://bulbapedia.bulbagarden.net/wiki/EP022][a three-part series]] about how Ghost117 pok\eacute{}mon are the best way to defeat Psychic pok\eacute{}mon,118 and the Red, Blue, and Yellow games each have a character who119 states "The only thing Psychic pok\eacute{}mon fear are Bugs and120 Ghosts!" This is considered to be a programming glitch. Ghost is121 super-effective against Psychic in Generation II.123 * Representing the Data125 After creating the Pok\eacute{}mon types namespace, we store the126 tables of susceptibilities above in =pokemon-table-gen-one= and127 =pokemon-table-gen-two=, each of which is a simple vector of128 vectors. Because a vector of vectors can be cumbersome, we do not129 access the tables directly; instead, we use the derivative structures130 =attack-strengths= and =defense-strengths=, which are functions which131 return hash-maps associating each row (respectively column) of the132 table with its corresponding Pok\eacute{}mon type.135 #+name: header136 #+begin_src clojure :results silent137 (ns pokemon.types138 (:use clojure.set)139 ;; (:use clojure.contrib.combinatorics)140 (:use clojure.math.combinatorics)141 (:use clojure.math.numeric-tower)142 ;; (:use clojure.contrib.def)143 (:use rlm.rlm-commands)144 (:require rlm.map-utils))145 #+end_src147 #+name: data148 #+begin_src clojure :results silent149 (in-ns 'pokemon.types)150 ;; record type strengths as a vector of vectors151 ;; the variables pokemon-table-gen-one and pokemon-table-gen-two152 ;; are replaced with the tables above when this file is tangled.153 (def pokemon-gen-one pokemon-table-gen-one)154 (def pokemon-gen-two pokemon-table-gen-two)156 (defn type-names [] (vec (doall (map (comp keyword first) pokemon-gen-two))))158 (defn attack-strengths []159 (zipmap160 (type-names)161 (map (comp vec rest) pokemon-gen-two)))163 (defn defense-strengths []164 (zipmap (type-names)165 (map166 (apply juxt (map (attack-strengths) (type-names)))167 (range (count (type-names))))))168 #+end_src170 The two statements172 #+begin_src clojure :exports code173 (def pokemon-gen-one pokemon-table-gen-one)174 (def pokemon-gen-two pokemon-table-gen-two)175 #+end_src177 probably look weird. When the actual source file is created, those178 variables are replaced with the data from the tables above.180 See [[../src/pokemon/types.clj][types.clj]] to look at the final tangled output.183 #+begin_src clojure :results output :exports both184 (clojure.pprint/pprint pokemon.types/pokemon-gen-two)185 #+end_src187 #+results:188 #+begin_example189 (("normal" 1 1 1 1 1 1 1 1 1 1 1 1 0.5 0 1 1 0.5)190 ("fire" 1 0.5 0.5 1 2 2 1 1 1 1 1 2 0.5 1 0.5 1 2)191 ("water" 1 2 0.5 1 0.5 1 1 1 2 1 1 1 2 1 0.5 1 1)192 ("electric" 1 1 2 0.5 0.5 1 1 1 0 2 1 1 1 1 0.5 1 1)193 ("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)194 ("ice" 1 0.5 0.5 1 2 0.5 1 1 2 2 1 1 1 1 2 1 0.5)195 ("fighting" 2 1 1 1 1 2 1 0.5 1 0.5 0.5 0.5 2 0 1 2 2)196 ("poison" 1 1 1 1 2 1 1 0.5 0.5 1 1 1 0.5 0.5 1 1 0)197 ("ground" 1 2 1 2 0.5 1 1 2 1 0 1 0.5 2 1 1 1 2)198 ("flying" 1 1 1 0.5 2 1 2 1 1 1 1 2 0.5 1 1 1 0.5)199 ("psychic" 1 1 1 1 1 1 2 2 1 1 0.5 1 1 1 1 0 0.5)200 ("bug" 1 0.5 1 1 2 1 0.5 0.5 1 0.5 2 1 1 0.5 1 2 0.5)201 ("rock" 1 2 1 1 1 2 0.5 1 0.5 2 1 2 1 1 1 1 0.5)202 ("ghost" 0 1 1 1 1 1 1 1 1 1 2 1 1 2 1 0.5 0.5)203 ("dragon" 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 0.5)204 ("dark" 1 1 1 1 1 1 0.5 1 1 1 2 1 1 2 1 0.5 0.5)205 ("steel" 1 0.5 0.5 0.5 1 2 1 1 1 1 1 1 2 1 1 1 0.5))206 #+end_example208 =pokemon-gen-two= is a simple list-of-lists data structure.210 #+begin_src clojure :results output :exports both211 (clojure.pprint/pprint (pokemon.types/defense-strengths))212 #+end_src214 #+results:215 #+begin_example216 {:water [1 0.5 0.5 2 2 0.5 1 1 1 1 1 1 1 1 1 1 0.5],217 :psychic [1 1 1 1 1 1 0.5 1 1 1 0.5 2 1 2 1 2 1],218 :dragon [1 0.5 0.5 0.5 0.5 2 1 1 1 1 1 1 1 1 2 1 1],219 :fire [1 0.5 2 1 0.5 0.5 1 1 2 1 1 0.5 2 1 1 1 0.5],220 :ice [1 2 1 1 1 0.5 2 1 1 1 1 1 2 1 1 1 2],221 :grass [1 2 0.5 0.5 0.5 2 1 2 0.5 2 1 2 1 1 1 1 1],222 :ghost [0 1 1 1 1 1 0 0.5 1 1 1 0.5 1 2 1 2 1],223 :poison [1 1 1 1 0.5 1 0.5 0.5 2 1 2 0.5 1 1 1 1 1],224 :flying [1 1 1 2 0.5 2 0.5 1 0 1 1 0.5 2 1 1 1 1],225 :normal [1 1 1 1 1 1 2 1 1 1 1 1 1 0 1 1 1],226 :rock [0.5 0.5 2 1 2 1 2 0.5 2 0.5 1 1 1 1 1 1 2],227 :electric [1 1 1 0.5 1 1 1 1 2 0.5 1 1 1 1 1 1 0.5],228 :ground [1 1 2 0 2 2 1 0.5 1 1 1 1 0.5 1 1 1 1],229 :fighting [1 1 1 1 1 1 1 1 1 2 2 0.5 0.5 1 1 0.5 1],230 :dark [1 1 1 1 1 1 2 1 1 1 0 2 1 0.5 1 0.5 1],231 :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],232 :bug [1 2 1 1 0.5 1 0.5 1 0.5 2 1 1 2 1 1 1 1]}233 #+end_example235 =defense-strengths= is a more convenient form of =pokemon-gen-two=,236 with key/value pair access.238 * Interfacing with the Data240 In the pok\eacute{}mon games, a pok\eacute{}mon can have up to two241 types at the same time. For example, [[http://bulbapedia.bulbagarden.net/wiki/Zapdos][Zapdos]], the fearsome legendary242 bird that can control lightning, has both the Electric and Flying243 types. A pok\eacute{}mon with more than one type gains the advantages244 and disadvantages of both types. The susceptibilities of each type are245 multiplied together to produce the hybrid type's susceptibilities. For246 example, Electric is weak to Ground (susceptibility of 2), but Flying247 is immune to Ground (susceptibility of 0). [[http://bulbapedia.bulbagarden.net/wiki/Zapdos][Zapdos']] type,248 Electric/Flying, is immune to Ground because $2 \times 0 = 0$.250 #+name: types251 #+begin_src clojure :results silent252 (in-ns 'pokemon.types)254 (defn multitypes "All combinations of up to n types" [n]255 (vec256 (map vec257 (reduce concat258 (map (partial combinations (type-names))259 (range 1 (inc n)))))))261 (defn susceptibility262 "Hash-map of the susceptibilities of the given type combination263 to each type of attack"264 [pkmn-types]265 (rlm.map-utils/map-vals266 clojure.core/rationalize267 (apply hash-map268 (interleave (type-names)269 (apply (partial map *)270 (map (defense-strengths) pkmn-types))))))272 (defn susceptance273 "The cumulative susceptibility of the given type combination"274 [types]275 (reduce + (map #(expt % 2) (vals (susceptibility types)))))276 #+end_src278 Now we can work out the susceptibility of [[http://bulbapedia.bulbagarden.net/wiki/Zapdos][Zapdos]] automatically.280 Electric is weak to Ground.281 #+begin_src clojure :exports both282 (:ground (pokemon.types/susceptibility [:electric]))283 #+end_src285 #+results:286 : 2288 Flying is immune to Ground.289 #+begin_src clojure :exports both290 (:ground (pokemon.types/susceptibility [:flying]))291 #+end_src293 #+results:294 : 0296 Together, they are immune to Ground.297 #+begin_src clojure :exports both298 (:ground (pokemon.types/susceptibility [:electric :flying]))299 #+end_src301 #+results:302 : 0307 * Best-First Search309 I'd like to find type combinations that are interesting, but the total310 number of combinations gets huge as we begin to consider more311 types. For example, the total possible number of type combinations312 given just 8 possible types is: 17^{8} = 6,975,757,441 combinations.313 Therefore, it's prudent to use search.315 These functions are a simple implementation of best-first search in316 clojure. The idea is to start off with a collection of nodes and some317 way of finding the best node, and to always expand the best node at318 every step.320 #+name: search321 #+begin_src clojure :results silent322 (in-ns 'pokemon.types)324 (defn comparatize325 "Define a comparator which uses the numerical outputs of fn as its criterion.326 Objects are sorted in increasing numerical order. Objects with the same fn-value327 are further compared by clojure.core/compare."328 [fun]329 (fn [a b]330 (let [val-a (fun a)331 val-b (fun b)]332 (cond333 ;; if the function cannot differentiate the two values334 ;; then compare the two values using clojure.core/compare335 (= val-a val-b) (compare a b)336 true337 ;; LOWER values of the function are preferred338 (compare (- val-a val-b) 0)))))340 (defn best-first-step [successors [visited unvisited]]341 (cond (empty? unvisited) nil342 true343 (let [best-node (first unvisited)344 visited* (conj visited best-node)345 unvisited*346 (difference347 (union unvisited (set (successors best-node)))348 visited*)]349 (println best-node)350 [visited* unvisited*])))351 (alter-var-root #'best-first-step memoize)353 ;; memoize partial from core so that for example354 ;; (= (partial + 1) (partial + 1))355 ;; this way, best first search can take advantage of the memoization356 ;; of best-first step357 (undef partial)358 (def partial (memoize clojure.core/partial))360 (defn best-first-search361 "Searches through a network of alternatives, pursuing362 initially-promising positions first. Comparator defines which363 positions are more promising, successors produces a list of improved364 positions from the given position (if any exist), and initial-nodes is365 a list of starting positions. Returns a lazy sequence of search results366 [visited-nodes unvisited-nodes], which terminates when367 there are no remaining unvisited positions."368 [comparator successors initial-nodes]369 (let [initial-nodes370 (apply (partial sorted-set-by comparator) initial-nodes)371 initial-visited-nodes (sorted-set-by comparator)372 step (partial best-first-step successors)]373 (take-while374 (comp not nil?)375 (iterate step [initial-visited-nodes initial-nodes]))))377 #+end_src380 Now that we have a basic best-first-search, it's convenient to write a381 few pok\eacute{}mon-type specific convenience functions.383 #+name: pokemon-search384 #+begin_src clojure :results silent385 (in-ns 'pokemon.types)386 (def type-compare387 "compare two type combinations W.R.T. their susceptibilities"388 (comparatize susceptance))390 (defn type-successors391 "Return the set of types that can be made by appending a single type392 to the given combination."393 [type]394 (if (nil? type) '()395 (set (map (comp vec sort (partial into type)) (multitypes 1)))))397 (defn immortal?398 "A type combo is immortal if it is resistant or invulnerable to399 every pokemon type. This is because that set of types can just be400 repeated to achieve as low a susceptance as desired"401 [type]402 (every? (partial > 1) (vals (susceptibility type))))404 (defn type-successors*405 "Stop expanding a type if it's immortal, or if it is longer than or406 equal to limit-size. Also, only return type additions that are407 strictly better than the initial type."408 [limit-size type]409 (if (or (<= limit-size (count type)) (immortal? type)) '()410 (set (filter #(< 0 (type-compare type %)) (type-successors type)))))412 (defn pokemon-type-search413 "Search among type-combos no greater than length n, limited by limit414 steps of best-first-search."415 ([n] (pokemon-type-search n Integer/MAX_VALUE))416 ([n limit]417 (first (last418 (take419 limit420 (best-first-search421 type-compare422 (partial type-successors* n)423 (multitypes 1)))))))425 (def immortals426 "find all the immortal pokemon types."427 (comp (partial filter immortal?) pokemon-type-search))429 #+end_src431 Because there are so many type combinations, it's important to narrow432 down the results as much as possible. That is why =type-successors*=433 only returns types that are actually better than the type it is given.435 Best-first search can get caught optimizing a single type forever, so436 it's also important to limit the search space to be finite by setting437 an upper bound on the length of a type combo.439 * Results440 ** The best dual-type combo442 #+begin_src clojure :results cache verbatim :exports both443 (first (pokemon.types/pokemon-type-search 2))444 #+end_src446 #+results:447 : [:dark :ghost]449 Dark and Ghost, which additionally has the property of having no450 weaknesses to any other type, is the best type combo in terms of451 susceptance.453 The Dark and Steel types were introduced many years after454 pok\eacute{}mon started. In addition to the additional types, the455 pok\eacute{}mon games gained a few new rules concerning some of the456 matchups of the original types. Therefore, it's also interesting to457 see what type combination was most powerful before those types and new458 rules were introduced.460 The easiest way to do this with my setup is to just rebind the461 =pokemon-gen-two= table to the =pokemon-gen-one= table. Since462 everything that references this variable is a function and we're not463 doing anything too crazy with lazy-sequences and late-binding, this464 simple macro will do the job.466 #+name: old-school467 #+begin_src clojure :results silent468 (in-ns 'pokemon.types)470 (defmacro old-school471 [& forms]472 `(binding [pokemon-gen-two pokemon-gen-one] ~@forms))473 #+end_src475 Using the =old-school= macro, it's easy to find answers for the476 original 15 pokemon types as well as the expanded pokemon types477 introduced later.479 #+begin_src clojure :results verbatim :exports both :cache yes480 (pokemon.types/old-school (first (pokemon.types/pokemon-type-search 2)))481 #+end_src483 #+results[f43470fdf460ed546e9c57879abc9eda56da129f]:484 : [:ghost :psychic]486 Ghost and Psychic also manages to have no weaknesses to any of the original487 types, using the old Generation I rules.489 #+begin_src clojure :results output :exports both490 (clojure.pprint/pprint491 (pokemon.types/old-school492 (pokemon.types/susceptibility [:ghost :psychic])))493 #+end_src495 #+results:496 #+begin_example497 {:water 1,498 :psychic 1/2,499 :dragon 1,500 :fire 1,501 :ice 1,502 :grass 1,503 :ghost 0,504 :poison 1/2,505 :flying 1,506 :normal 0,507 :rock 1,508 :electric 1,509 :ground 1,510 :fighting 0,511 :bug 0}512 #+end_example514 ** An Immortal Type515 It's possible to quickly find an immortal type by giving the search516 a long enough maximum type length. 50 rounds of search with a max517 type limit of 10 is enough to find an immortal type.519 #+begin_src clojure :results scalar :exports both520 (first (pokemon.types/pokemon-type-search 10 50))521 #+end_src523 #+results:524 : [:dragon :fire :flying :ghost :grass :ground :steel :steel :water :water]527 #+begin_src clojure :results output :exports both528 (clojure.pprint/pprint529 (pokemon.types/susceptibility530 [:dragon :fire :flying :ghost :grass :ground :steel :steel :water :water]))531 #+end_src533 #+results:534 #+begin_example535 {:water 1/4,536 :psychic 1/4,537 :dragon 1/2,538 :fire 1/2,539 :ice 1/2,540 :grass 1/8,541 :ghost 1/2,542 :poison 0,543 :flying 1/2,544 :normal 0,545 :rock 1/2,546 :electric 0,547 :ground 0,548 :fighting 0,549 :dark 1/2,550 :steel 1/32,551 :bug 1/16}552 #+end_example554 ** Explanations for Common Pok\eacute{}mon Strategies556 Many people start out a battle with either a Normal pok\eacute{}mon or an557 Electric pok\eacute{}mon. Here's some justification for that choice.559 #+name: weaknesses560 #+begin_src clojure :results silent561 (in-ns 'pokemon.types)562 (defn critical-weaknesses [type]563 (count (filter #(> % 1) (vals (susceptibility type)))))564 #+end_src566 #+begin_src clojure :exports both :results output567 (clojure.pprint/pprint568 (sort-by pokemon.types/critical-weaknesses (pokemon.types/multitypes 1)))569 #+end_src571 #+results:572 #+begin_example573 ([:normal]574 [:electric]575 [:water]576 [:fighting]577 [:poison]578 [:ghost]579 [:dragon]580 [:dark]581 [:fire]582 [:ground]583 [:flying]584 [:psychic]585 [:bug]586 [:steel]587 [:ice]588 [:grass]589 [:rock])590 #+end_example592 Electric and Normal are among the best types with which to start the593 game, since they have the fewest weaknesses among all the types.595 At the beginning of the pok\eacute{}mon games, players are given a596 choice between the Fire pok\eacute{}mon [[http://bulbapedia.bulbagarden.net/wiki/Charmander][Charmander]], the Water597 pok\eacute{}mon [[http://bulbapedia.bulbagarden.net/wiki/Squirtle][Squirtle]], or the Grass/Poison pok\eacute{}mon598 [[http://bulbapedia.bulbagarden.net/wiki/Bulbasaur][Bulbasaur]].600 #+begin_src clojure :exports both :results verbatim601 (sort-by pokemon.types/susceptance [[:fire] [:water] [:grass :poison]])602 #+end_src604 #+results:605 : ([:water] [:fire] [:grass :poison])607 As can be seen, the Water pok\eacute{}mon [[http://bulbapedia.bulbagarden.net/wiki/Squirtle][Squirtle]] is the most solid608 choice starting out, insofar as susceptance is concerned.610 ** The Worst Pok\eacute{}mon Types612 #+name: weak-types613 #+begin_src clojure :results silent614 (in-ns 'pokemon.types)616 (defn type-compare-weak617 "compare first by total number of critical-weaknesses,618 then by overall susceptance, favoring weaker types."619 [type-1 type-2]620 (let [measure (memoize (juxt critical-weaknesses susceptance))]621 (if (= (measure type-2) (measure type-1))622 (compare type-2 type-1)623 (compare (measure type-2) (measure type-1)))))625 (defn resistant?626 "might as well get rid of types that are resistant to any type"627 [type]628 (not (every? #(< 0 %) (vals (susceptibility type)))))630 (defn type-successors-weak631 "Generate ways to weaken the given type combination. Discard type632 combinations that either strengthen the given type combination or633 that make it stronger"634 [limit type]635 (set (if (<= limit (count type)) '()636 (filter #(< 0 (type-compare-weak type %))637 (remove resistant? (type-successors type))))))639 (defn pokemon-type-search-weak640 "Search among type-combos no greater than length n, limited by limit641 steps of best-first-search. Find the weakest type combination642 possible in terms of susceptance."643 ([n] (pokemon-type-search-weak n Integer/MAX_VALUE))644 ([n limit]645 (first (last646 (take647 limit648 (best-first-search649 type-compare-weak650 (partial type-successors-weak n)651 (multitypes 1)))))))652 #+end_src655 #+begin_src clojure :results scalar :exports both656 (first (pokemon.types/pokemon-type-search-weak 1))657 #+end_src659 #+results:660 : [:rock]662 Poor Rock. It's just not that good a type. Maybe this is why Brock663 (who has rock pok\eacute{}mon) is the first gym leader in the games.665 #+begin_src clojure :results scalar cache :exports both666 (first (pokemon.types/pokemon-type-search-weak 2))667 #+end_src669 #+results:670 : [:grass :ice]672 # ;;bonus convergently immortal type combo673 # (susceptance (vec (concat (repeat 150 :water) (repeat 50 :poison) (repeat 50 :steel) [:ghost :normal :flying :ground :dark])))675 #+begin_src clojure :results output :exports both676 (clojure.pprint/pprint677 (pokemon.types/susceptibility [:grass :ice]))678 #+end_src680 #+results:681 #+begin_example682 {:water 1/2,683 :psychic 1,684 :dragon 1,685 :fire 4,686 :ice 1,687 :grass 1/2,688 :ghost 1,689 :poison 2,690 :flying 2,691 :normal 1,692 :rock 2,693 :electric 1/2,694 :ground 1/2,695 :fighting 2,696 :dark 1,697 :steel 2,698 :bug 2}699 #+end_example701 This miserable combination is weak to 6 types and double-weak to702 Fire. No pok\eacute{}mon in the games actually has this type.704 * Conclusion706 Searching for a type that is weak to everything takes a very long time707 and fails to reveal any results. That's the problem with a search708 over this large problem space --- if there's an easy solution, the709 search will find it quickly, but it can be very hard to determine710 whether there is actually a solution.712 In the [[./lpsolve.org][next installment]], I'll use =lp_solve= to solve this problem in713 a different way.715 * COMMENT main program716 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :exports none717 <<header>>718 #+end_src720 ## this is necessary to define pokemon-table inside the source code.722 #+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 none723 <<data>>724 #+end_src726 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :exports none727 <<types>>728 <<search>>729 <<pokemon-search>>730 <<old-school>>731 <<weaknesses>>732 <<weak-types>>733 #+end_src