view org/types.org @ 6:3f26fc68ffbc

give better descriptions to the tables
author Robert McIntyre <rlm@mit.edu>
date Wed, 02 Nov 2011 05:53:43 -0700
parents ff9655688ddb
children d6b8dab05d9d
line wrap: on
line source
1 #+TITLE: Breadth-first Search for Effective Pokemon Types
2 #+AUTHOR: Robert McIntyre & Dylan Holmes
3 #+EMAIL: rlm@mit.edu
4 #+SETUPFILE: ../../aurellem/org/setup.org
5 #+INCLUDE: ../../aurellem/org/level-0.org
7 * The Pok\eacute{}mon Type System
9 The Pok\eacute{}mon type system consists of seventeen different
10 \ldquo{}types\rdquo{} (Rock, Grass, Ice, Psychic, Ground, Bug, Flying,
11 Fire, Fighting, Dark, Dragon, Poison, Water, Ghost, Normal, Electric,
12 and Steel) that interact like an extended version of
13 Rock-Paper-Scissors: for example, the Fire type is strong against the
14 Grass type but weak against the Water type. In the table below, we've
15 recorded the relative strengths of each of the types in the
16 Pok\eacute{}mon type system; the number in each cell indicates how
17 effective an attack of the type in the row is against a
18 Pok\eacute{}mon of the type in the column. We call these numbers
19 /susceptibilities/.
21 In the Pok\eacute{}mon games, only four susceptibility values (two,
22 one, one-half, and zero) occur. These numbers indicate particularly
23 high susceptibility, average susceptibility, particularly low
24 susceptibility, and no susceptibility (immunity).
26 - The suceptability of Flying types /against/ Ground is 0, because Ground
27 attacks cannot hurt Flying pok\eacute{}mon at all. The damage that
28 a Ground type attack normally does is /multiplied/ by 0 when it is
29 uesd against a Flying type pok\eacute{}mon.
31 - The susceptability of Fire types against Water attacks
32 is 2, because Water type attacks are strong against Fire type
33 Pok\eacute{}mon. The damage that a Water type attack normally does
34 is doubled when it is used against a Fire type pok\eacute{}mon.
36 - The susceptability of Water types against Water attacks is
37 $\frac{1}{2}$, because Water type attacks are strong against Water
38 type Pok\eacute{}mon. The damage that a Water type attack normally
39 does is halved when it is used against a Water type
40 pok\eacute{}mon.
42 There are two pok\eacute{}mon type systems in use. The first is the
43 classic system which was used for the very first pok\eacute{}mon
44 games, Red, Yellow, and Blue. This old system was used from 1998 to
45 2000 in America, and is known as the /Generation I Type System/. The
46 modern pok\eacute{}mon type system was introduced in 2000 with the
47 introduction of pok\eacute{}mon Gold and Silver, and has been in use
48 ever since. It is called the /Generation II Type System/.
50 Here are the the definitions of the two type systems.
52 * The Pok\eacute{}mon Type Systems
54 ** Generation II Type System
55 #+label: pokemon-matchups
56 #+tblname: pokemon-table-gen-two
57 | | normal | fire | water | electric | grass | ice | fighting | poison | ground | flying | psychic | bug | rock | ghost | dragon | dark | steel |
58 |----------+--------+------+-------+----------+-------+-----+----------+--------+--------+--------+---------+-----+------+-------+--------+------+-------|
59 | normal | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 0 | 1 | 1 | .5 |
60 | fire | 1 | .5 | .5 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 1 | 2 |
61 | water | 1 | 2 | .5 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | 1 |
62 | electric | 1 | 1 | 2 | .5 | .5 | 1 | 1 | 1 | 0 | 2 | 1 | 1 | 1 | 1 | .5 | 1 | 1 |
63 | grass | 1 | .5 | 2 | 1 | .5 | 1 | 1 | .5 | 2 | .5 | 1 | .5 | 2 | 1 | .5 | 1 | .5 |
64 | ice | 1 | .5 | .5 | 1 | 2 | .5 | 1 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 |
65 | fighting | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | .5 | .5 | .5 | 2 | 0 | 1 | 2 | 2 |
66 | poison | 1 | 1 | 1 | 1 | 2 | 1 | 1 | .5 | .5 | 1 | 1 | 1 | .5 | .5 | 1 | 1 | 0 |
67 | ground | 1 | 2 | 1 | 2 | .5 | 1 | 1 | 2 | 1 | 0 | 1 | .5 | 2 | 1 | 1 | 1 | 2 |
68 | flying | 1 | 1 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | 1 | 1 | .5 |
69 | psychic | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 1 | 1 | .5 | 1 | 1 | 1 | 1 | 0 | .5 |
70 | bug | 1 | .5 | 1 | 1 | 2 | 1 | .5 | .5 | 1 | .5 | 2 | 1 | 1 | .5 | 1 | 2 | .5 |
71 | rock | 1 | 2 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | .5 |
72 | ghost | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 1 | 2 | 1 | .5 | .5 |
73 | dragon | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | .5 |
74 | dark | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 2 | 1 | .5 | .5 |
75 | steel | 1 | .5 | .5 | .5 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | .5 |
77 The rows are attack types, while the columns are defense types. To
78 see the multiplier for a pokemon attack against a certain type, follow
79 the row for the attack type to the column of the defending type.
81 ** Generation I Type System
82 #+label: pokemon-matchups-gen-1 +tblname: pokemon-table-gen-one
83 | | normal | fire | water | electric | grass | ice | fighting | poison | ground | flying | psychic | bug | rock | ghost | dragon |
84 |----------+--------+------+-------+----------+-------+-----+----------+--------+--------+--------+---------+-----+------+-------+--------|
85 | normal | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 0 | 1 |
86 | fire | 1 | .5 | .5 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | .5 |
87 | water | 1 | 2 | .5 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | 2 | 1 | .5 |
88 | electric | 1 | 1 | 2 | .5 | .5 | 1 | 1 | 1 | 0 | 2 | 1 | 1 | 1 | 1 | .5 |
89 | grass | 1 | .5 | 2 | 1 | .5 | 1 | 1 | .5 | 2 | .5 | 1 | .5 | 2 | 1 | .5 |
90 | ice | 1 | 1 | .5 | 1 | 2 | .5 | 1 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 2 |
91 | fighting | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | .5 | .5 | .5 | 2 | 0 | 1 |
92 | poison | 1 | 1 | 1 | 1 | 2 | 1 | 1 | .5 | .5 | 1 | 1 | 2 | .5 | .5 | 1 |
93 | ground | 1 | 2 | 1 | 2 | .5 | 1 | 1 | 2 | 1 | 0 | 1 | .5 | 2 | 1 | 1 |
94 | flying | 1 | 1 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | 1 |
95 | psychic | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 1 | 1 | .5 | 1 | 1 | 1 | 1 |
96 | bug | 1 | .5 | 1 | 1 | 2 | 1 | .5 | 2 | 1 | .5 | 2 | 1 | 1 | 0 | 1 |
97 | rock | 1 | 2 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 |
98 | ghost | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 2 | 1 |
99 | dragon | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 |
102 This is the old table from Generation I. The differences from
103 Generation II are:
104 - Dark and Steel are missing.
105 - Bug is super-effective against Poison.
106 - Poison is super-effective against Bug.
107 - Bug is regularly effective against Ghost (instead of
108 super-effective like in Generation II).
109 - Ice is normally effective against Fire, (it's not-very-effective in
110 Generation II).
111 - Ghost is completely ineffective against Psychic. This is considered
112 to be a programning glitch.
116 * Representing the Data
118 After creating the Pok\eacute{}mon types namespace, we store the table
119 of susceptibilities in =pokemon-table-gen-one= and
120 =pokemon-table-gen-two=, each of which is a simple vector of
121 vectors. Because a vector of vectors can be cumbersome, we do not
122 access the tables directly; instead, we use the derivative structures
123 =attack-strengths= and =defense-strengths=, which are functions which
124 return hash-maps associating each row (respectively column) of the
125 table with its corresponding Pok\eacute{}mon type.
129 #+srcname: header
130 #+begin_src clojure :results silent
131 (ns pokemon.types
132 (:use rlm.ns-rlm))
133 (rlm.ns-rlm/ns-clone rlm.light-base)
134 (use 'clojure.set)
135 #+end_src
137 #+srcname: data(pokemon-table-gen-one=pokemon-table-gen-one, pokemon-table-gen-two=pokemon-table-gen-two)
138 #+begin_src clojure :results silent
139 (in-ns 'pokemon.types)
140 ;; record type strengths as a vector of vectors
141 (def pokemon-gen-one pokemon-table-gen-one)
142 (def pokemon-gen-two pokemon-table-gen-two)
144 (defn type-names [] (vec (doall (map (comp keyword first) pokemon-gen-two))))
146 (defn attack-strengths []
147 (zipmap
148 (type-names)
149 (map (comp vec rest) pokemon-gen-two)))
151 (defn defense-strengths []
152 (zipmap (type-names)
153 (map
154 (apply juxt (map (attack-strengths) (type-names)))
155 (range (count (type-names))))))
156 #+end_src
158 #+begin_src clojure :results output :exports both
159 (clojure.pprint/pprint pokemon.types/pokemon-gen-two)
160 #+end_src
162 #+results:
163 #+begin_example
164 (("normal" 1 1 1 1 1 1 1 1 1 1 1 1 0.5 0 1 1 0.5)
165 ("fire" 1 0.5 0.5 1 2 2 1 1 1 1 1 2 0.5 1 0.5 1 2)
166 ("water" 1 2 0.5 1 0.5 1 1 1 2 1 1 1 2 1 0.5 1 1)
167 ("electric" 1 1 2 0.5 0.5 1 1 1 0 2 1 1 1 1 0.5 1 1)
168 ("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)
169 ("ice" 1 0.5 0.5 1 2 0.5 1 1 2 2 1 1 1 1 2 1 0.5)
170 ("fighting" 2 1 1 1 1 2 1 0.5 1 0.5 0.5 0.5 2 0 1 2 2)
171 ("poison" 1 1 1 1 2 1 1 0.5 0.5 1 1 1 0.5 0.5 1 1 0)
172 ("ground" 1 2 1 2 0.5 1 1 2 1 0 1 0.5 2 1 1 1 2)
173 ("flying" 1 1 1 0.5 2 1 2 1 1 1 1 2 0.5 1 1 1 0.5)
174 ("psychic" 1 1 1 1 1 1 2 2 1 1 0.5 1 1 1 1 0 0.5)
175 ("bug" 1 0.5 1 1 2 1 0.5 0.5 1 0.5 2 1 1 0.5 1 2 0.5)
176 ("rock" 1 2 1 1 1 2 0.5 1 0.5 2 1 2 1 1 1 1 0.5)
177 ("ghost" 0 1 1 1 1 1 1 1 1 1 2 1 1 2 1 0.5 0.5)
178 ("dragon" 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 0.5)
179 ("dark" 1 1 1 1 1 1 0.5 1 1 1 2 1 1 2 1 0.5 0.5)
180 ("steel" 1 0.5 0.5 0.5 1 2 1 1 1 1 1 1 2 1 1 1 0.5))
181 #+end_example
183 =pokemon-gen-two= is a simple list-of-list data structure.
185 #+begin_src clojure :results output :exports both
186 (clojure.pprint/pprint (pokemon.types/defense-strengths))
187 #+end_src
189 #+results:
190 #+begin_example
191 {:water [1 0.5 0.5 2 2 0.5 1 1 1 1 1 1 1 1 1 1 0.5],
192 :psychic [1 1 1 1 1 1 0.5 1 1 1 0.5 2 1 2 1 2 1],
193 :dragon [1 0.5 0.5 0.5 0.5 2 1 1 1 1 1 1 1 1 2 1 1],
194 :fire [1 0.5 2 1 0.5 0.5 1 1 2 1 1 0.5 2 1 1 1 0.5],
195 :ice [1 2 1 1 1 0.5 2 1 1 1 1 1 2 1 1 1 2],
196 :grass [1 2 0.5 0.5 0.5 2 1 2 0.5 2 1 2 1 1 1 1 1],
197 :ghost [0 1 1 1 1 1 0 0.5 1 1 1 0.5 1 2 1 2 1],
198 :poison [1 1 1 1 0.5 1 0.5 0.5 2 1 2 0.5 1 1 1 1 1],
199 :flying [1 1 1 2 0.5 2 0.5 1 0 1 1 0.5 2 1 1 1 1],
200 :normal [1 1 1 1 1 1 2 1 1 1 1 1 1 0 1 1 1],
201 :rock [0.5 0.5 2 1 2 1 2 0.5 2 0.5 1 1 1 1 1 1 2],
202 :electric [1 1 1 0.5 1 1 1 1 2 0.5 1 1 1 1 1 1 0.5],
203 :ground [1 1 2 0 2 2 1 0.5 1 1 1 1 0.5 1 1 1 1],
204 :fighting [1 1 1 1 1 1 1 1 1 2 2 0.5 0.5 1 1 0.5 1],
205 :dark [1 1 1 1 1 1 2 1 1 1 0 2 1 0.5 1 0.5 1],
206 :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],
207 :bug [1 2 1 1 0.5 1 0.5 1 0.5 2 1 1 2 1 1 1 1]}
208 #+end_example
210 =defense-strengths= is a more convenient form of =pokemon-gen-two=, with key/value pair access.
212 * Interfacing with the Data
213 #+srcname: types
214 #+begin_src clojure :results silent
215 (in-ns 'pokemon.types)
217 (defn multitypes "All combinations of up to n types" [n]
218 (vec
219 (map vec
220 (reduce concat
221 (map (partial combinations (type-names))
222 (range 1 (inc n)))))))
224 (defn susceptibility ;; susceptibility-map
225 "Hash-map of the susceptibilities of the given type combination
226 to each type of attack"
227 [pkmn-types]
228 (rlm.map-utils/map-vals
229 clojure.core/rationalize
230 (apply hash-map
231 (interleave (type-names)
232 (apply (partial map *)
233 (map (defense-strengths) pkmn-types))))))
235 (defn susceptance ;; susceptibility
236 "The cumulative susceptibility of the given type combination"
237 [types]
238 (reduce + (map sqr (vals (susceptibility types)))))
239 #+end_src
241 * Best-First Search
243 I'd like to find type combinations that are interesting, but the total
244 number of combinations gets huge as we begin to consider more
245 types. For example, the total possible number of type combinations
246 given just 8 possible types is: 17^{8} = 6975757441 combinations.
247 Therefore, it's prudent to use search.
249 These functions are a simple implementation of best-first search in
250 clojure. The idea to start off with a collection of nodes and some way
251 of finding the best node, and to always expand the best node at every
252 step.
254 #+srcname: search
255 #+begin_src clojure :results silent
256 (in-ns 'pokemon.types)
258 (defn comparatize
259 "Define a comparator which uses the numerical outputs of fn as its criterion.
260 Objects are sorted in increasing numerical order. Objects with the same fn-value
261 are further compared by clojure.core/compare."
262 [fun]
263 (fn [a b]
264 (let [val-a (fun a)
265 val-b (fun b)]
266 (cond
267 ;; if the function cannot differentiate the two values
268 ;; then compare the two values using clojure.core/compare
269 (= val-a val-b) (compare a b)
270 true
271 ;; LOWER values of the function are preferred
272 (compare (- val-a val-b) 0)))))
274 (defn-memo best-first-step [successors [visited unvisited]]
275 (cond (empty? unvisited) nil
276 true
277 (let [best-node (first unvisited)
278 visited* (conj visited best-node)
279 unvisited*
280 (difference
281 (union unvisited (set (successors best-node)))
282 visited*)]
283 (println best-node)
284 [visited* unvisited*])))
286 ;; memoize partial from core so that for example
287 ;; (= (partial + 1) (partial + 1))
288 ;; this way, best first search can take advantage of the memoization
289 ;; of best-first step
290 (undef partial)
291 (def partial (memoize clojure.core/partial))
293 (defn best-first-search
294 "Searches through a network of alternatives, pursuing
295 initially-promising positions first. Comparator defines which
296 positions are more promising, successors produces a list of improved
297 positions from the given position (if any exist), and initial-nodes is
298 a list of starting positions. Returns a lazy sequence of search results
299 [visited-nodes unvisited-nodes], which terminates when
300 there are no remaining unvisited positions."
301 [comparator successors initial-nodes]
302 (let [initial-nodes
303 (apply (partial sorted-set-by comparator) initial-nodes)
304 initial-visited-nodes (sorted-set-by comparator)
305 step (partial best-first-step successors)]
306 (take-while
307 (comp not nil?)
308 (iterate step [initial-visited-nodes initial-nodes]))))
310 #+end_src
313 Now that we have a basic best-first-search, it's convenient to write a
314 few pokemon-type specific convenience functions.
316 #+srcname: pokemon-search
317 #+begin_src clojure :results silent
318 (in-ns 'pokemon.types)
319 (defvar type-compare (comparatize susceptance)
320 "compare two type combinations wrt their susceptibilities")
322 (defn type-successors
323 "Return the set of types that can be made by appending a single type
324 to the given combination."
325 [type]
326 (if (nil? type) '()
327 (set (map (comp vec sort (partial into type)) (multitypes 1)))))
329 (defn immortal?
330 "A type combo is immortal if it is resistant or invulnerable to
331 every pokemon type. This is because that set of types can just be
332 repeated to achieve as low a susceptance as desired"
333 [type]
334 (every? (partial > 1) (vals (susceptibility type))))
336 (defn type-successors*
337 "Stop expanding a type if it's immortal, or if it is longer than or
338 equal to limit-size. Also, only return type additions that are
339 strictly better than the initial type."
340 [limit-size type]
341 (if (or (<= limit-size (count type)) (immortal? type)) '()
342 (set (filter #(< 0 (type-compare type %)) (type-successors type)))))
344 (defn pokemon-type-search
345 "Search among type-combos no greater than length n, limited by limit
346 steps of best-first-search."
347 ([n] (pokemon-type-search n Integer/MAX_VALUE))
348 ([n limit]
349 (first (last
350 (take
351 limit
352 (best-first-search
353 type-compare
354 (partial type-successors* n)
355 (multitypes 1)))))))
357 (defvar immortals
358 (comp (partial filter immortal?) pokemon-type-search)
359 "find all the immortal pokemon types ")
361 #+end_src
363 Because there are so many type combinations, it's important to narrow
364 down the results as much as possible. That is why =type-successors*=
365 only returns types that are actually better than the type it is given.
367 Best-first search can get caught optimizing a single type forever, so
368 it's also important to limit the search space to be finite by setting
369 an upper bound on the length of a type combo.
371 * Results
372 ** The best dual-type combo
374 #+begin_src clojure :results cache verbatim :exports both
375 (first (pokemon.types/pokemon-type-search 2))
376 #+end_src
378 #+results:
379 : [:dark :ghost]
381 Dark and Ghost, which additionally has the property of having no
382 weaknesses to any other type, is the best type combo in terms of
383 susceptance.
385 The Dark and Steel types were introduced many years after
386 pok\eacute{}mon started. In addition to the additional types, the
387 pok\eacute{}mon games gained a few new rules concerning some of the
388 matchups of the original types. Therefore, it's also interesting to see what
389 type combination was most powerful before those types and new rules were introduced.
391 The easiest way to do this with my setup is to just rebind the
392 =pokemon-gen-two= table to the =pokemon-gen-one= table. Since
393 everything that references this variable is a function and we're not
394 doing anything too crazy with lazy-sequences and late-binding, this
395 simple macro will do the job.
397 #+srcname: old-school
398 #+begin_src clojure :results silent
399 (in-ns 'pokemon.types)
401 (defmacro old-school
402 [& forms]
403 `(binding [pokemon-gen-two pokemon-gen-one] ~@forms))
404 #+end_src
406 Using the =old-school= macro, it's easy to find answers for the
407 original 15 pokemon types as well as the expanded pokemon types
408 introduced later.
410 #+begin_src clojure :results verbatim :exports both :cache yes
411 (pokemon.types/old-school (first (pokemon.types/pokemon-type-search 2)))
412 #+end_src
414 #+results[f43470fdf460ed546e9c57879abc9eda56da129f]:
415 : [:ghost :psychic]
417 Ghost and Psychic also manages to have no weaknesses to any of the original
418 types.
420 #+begin_src clojure :results output :exports both
421 (clojure.pprint/pprint
422 (pokemon.types/old-school
423 (pokemon.types/susceptibility [:ghost :psychic])))
424 #+end_src
426 #+results:
427 #+begin_example
428 {:water 1,
429 :psychic 1/2,
430 :dragon 1,
431 :fire 1,
432 :ice 1,
433 :grass 1,
434 :ghost 0,
435 :poison 1/2,
436 :flying 1,
437 :normal 0,
438 :rock 1,
439 :electric 1,
440 :ground 1,
441 :fighting 0,
442 :bug 0}
443 #+end_example
445 ** An Immortal Type
446 It's possible to quickly find an immortal type by giving the search
447 a long enough maximum type length. 50 rounds of search with a max
448 type limit of 10 is enough to find an immortal type.
450 #+begin_src clojure :results scalar :exports both
451 (first (pokemon.types/pokemon-type-search 10 50))
452 #+end_src
454 #+results:
455 : [:dragon :fire :flying :ghost :grass :ground :steel :steel :water :water]
458 #+begin_src clojure :results output :exports both
459 (clojure.pprint/pprint
460 (pokemon.types/susceptibility
461 [:dragon :fire :flying :ghost :grass :ground :steel :steel :water :water]))
462 #+end_src
464 #+results:
465 #+begin_example
466 {:water 1/4,
467 :psychic 1/4,
468 :dragon 1/2,
469 :fire 1/2,
470 :ice 1/2,
471 :grass 1/8,
472 :ghost 1/2,
473 :poison 0,
474 :flying 1/2,
475 :normal 0,
476 :rock 1/2,
477 :electric 0,
478 :ground 0,
479 :fighting 0,
480 :dark 1/2,
481 :steel 1/32,
482 :bug 1/16}
483 #+end_example
485 ** Explanations for Common Pok\eacute{}mon Strategies
487 Many people start out a battle with either a normal pok\eacute{}mon or an
488 electric pok\eacute{}mon, and here's some justification for that choice.
490 #+srcname: weaknesses
491 #+begin_src clojure :results silent
492 (in-ns 'pokemon.types)
493 (defn critical-weaknesses [type]
494 (count (filter #(> % 1) (vals (susceptibility type)))))
495 #+end_src
497 #+begin_src clojure :exports both :results output
498 (clojure.pprint/pprint
499 (sort-by pokemon.types/critical-weaknesses (pokemon.types/multitypes 1)))
500 #+end_src
502 #+results:
503 #+begin_example
504 ([:normal]
505 [:electric]
506 [:water]
507 [:fighting]
508 [:poison]
509 [:ghost]
510 [:dragon]
511 [:dark]
512 [:fire]
513 [:ground]
514 [:flying]
515 [:psychic]
516 [:bug]
517 [:steel]
518 [:ice]
519 [:grass]
520 [:rock])
521 #+end_example
523 Electric and Normal are among the best types with which to start the
524 game, since they have the fewest weaknesses among all the types.
526 At the beginning of the pok\eacute{}mon games, players are given a choice
527 between the Fire pok\eacute{}mon Charmander, the Water pok\eacute{}mon Squirtle, or
528 the Grass/Poison pok\eacute{}mon Bulbasaur.
530 #+begin_src clojure :exports both :results verbatim
531 (sort-by pokemon.types/susceptance [[:fire] [:water] [:grass :poison]])
532 #+end_src
534 #+results:
535 : ([:water] [:fire] [:grass :poison])
537 As can be seen, the Water pok\eacute{}mon Squirtle is the most solid
538 choice starting out, insofar as susceptance is concerned.
540 ** The Worst Pok\eacute{}mon Types
542 #+srcname: weak-types
543 #+begin_src clojure :results silent
544 (in-ns 'pokemon.types)
546 (defn type-compare-weak
547 "compare first by total number of critical-weaknesses,
548 then by overall susceptance, favoring weaker types."
549 [type-1 type-2]
550 (let [measure (memoize (juxt critical-weaknesses susceptance))]
551 (if (= (measure type-2) (measure type-1))
552 (compare type-2 type-1)
553 (compare (measure type-2) (measure type-1)))))
555 (defn resistant?
556 "might as well get rid of types that are resistant to any type"
557 [type]
558 (not (every? #(< 0 %) (vals (susceptibility type)))))
560 (defn type-successors-weak
561 [limit type]
562 (set (if (<= limit (count type)) '()
563 (filter #(< 0 (type-compare-weak type %))
564 (remove resistant? (type-successors type))))))
566 (defn pokemon-type-search-weak
567 "Search among type-combos no greater than length n, limited by limit
568 steps of best-first-search."
569 ([n] (pokemon-type-search-weak n Integer/MAX_VALUE))
570 ([n limit]
571 (first (last
572 (take
573 limit
574 (best-first-search
575 type-compare-weak
576 (partial type-successors-weak n)
577 (multitypes 1)))))))
578 #+end_src
581 #+begin_src clojure :results scalar :exports both
582 (first (pokemon.types/pokemon-type-search-weak 1))
583 #+end_src
585 #+results:
586 : [:rock]
588 Poor Rock. It's just not that good a type. Maybe this is why Brock
589 (who has rock pok\eacute{}mon) is the first gym leader in the games.
591 #+begin_src clojure :results scalar cache :exports both
592 (first (pokemon.types/pokemon-type-search-weak 2))
593 #+end_src
595 #+results:
596 : [:grass :ice]
598 # ;;bonus convergently immortal type combo
599 # (susceptance (vec (concat (repeat 150 :water) (repeat 50 :poison) (repeat 50 :steel) [:ghost :normal :flying :ground :dark])))
601 #+begin_src clojure :results output :exports both
602 (clojure.pprint/pprint
603 (pokemon.types/susceptibility [:grass :ice]))
604 #+end_src
606 #+results:
607 #+begin_example
608 {:water 1/2,
609 :psychic 1,
610 :dragon 1,
611 :fire 4,
612 :ice 1,
613 :grass 1/2,
614 :ghost 1,
615 :poison 2,
616 :flying 2,
617 :normal 1,
618 :rock 2,
619 :electric 1/2,
620 :ground 1/2,
621 :fighting 2,
622 :dark 1,
623 :steel 2,
624 :bug 2}
625 #+end_example
627 This miserable combination is weak to 6 types and double-weak to
628 Fire. No pok\eacute{}mon in the games actually has this type.
630 * Conclusion
632 Searching for a type that is weak to everything takes a very long time
633 and fails to reveal any results. That's the problem with a search
634 over this large problem space --- if there's an easy solution, the
635 search will find it quickly, but it can be very hard to determine
636 whether there is actually a solution.
638 In the [[./lpsolve.org][next installment]], I'll use =lp_solve= to solve this problem in
639 a different way.
643 * COMMENT main program
644 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :exports none
645 <<header>>
646 #+end_src
648 ## this is necessary to define pokemon-table inside the source code.
650 #+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
651 <<data>>
652 #+end_src
654 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :exports none
655 <<types>>
656 <<search>>
657 <<pokemon-search>>
658 <<old-school>>
659 <<weaknesses>>
660 <<weak-types>>
661 #+end_src