view org/types.org @ 5:ff9655688ddb

added pokemon table
author Robert McIntyre <rlm@mit.edu>
date Wed, 02 Nov 2011 05:41:49 -0700
parents a227fe337e83
children 3f26fc68ffbc
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 * Pokemon Table Data
54 #+caption: The rows are attack types, while the columns are defense types. To see the multiplier for a pokemon attack against a certain type, follow the row for the attack type to the column of the defending type.
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 #+caption: this is the old table from generation 1. The differences are: dark and ghost are missing, Bus is super against Poison, Poison is super against Bug, Bug is regularly effective against Ghost, and Ice is normally effective against Fire. Ghost is not effective against psychic.
78 #+label: pokemon-matchups-gen-1
79 #+tblname: pokemon-table-gen-one
80 | | normal | fire | water | electric | grass | ice | fighting | poison | ground | flying | psychic | bug | rock | ghost | dragon |
81 |----------+--------+------+-------+----------+-------+-----+----------+--------+--------+--------+---------+-----+------+-------+--------|
82 | normal | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 0 | 1 |
83 | fire | 1 | .5 | .5 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | .5 |
84 | water | 1 | 2 | .5 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | 2 | 1 | .5 |
85 | electric | 1 | 1 | 2 | .5 | .5 | 1 | 1 | 1 | 0 | 2 | 1 | 1 | 1 | 1 | .5 |
86 | grass | 1 | .5 | 2 | 1 | .5 | 1 | 1 | .5 | 2 | .5 | 1 | .5 | 2 | 1 | .5 |
87 | ice | 1 | 1 | .5 | 1 | 2 | .5 | 1 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 2 |
88 | fighting | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | .5 | .5 | .5 | 2 | 0 | 1 |
89 | poison | 1 | 1 | 1 | 1 | 2 | 1 | 1 | .5 | .5 | 1 | 1 | 2 | .5 | .5 | 1 |
90 | ground | 1 | 2 | 1 | 2 | .5 | 1 | 1 | 2 | 1 | 0 | 1 | .5 | 2 | 1 | 1 |
91 | flying | 1 | 1 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | 1 |
92 | psychic | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 1 | 1 | .5 | 1 | 1 | 1 | 1 |
93 | bug | 1 | .5 | 1 | 1 | 2 | 1 | .5 | 2 | 1 | .5 | 2 | 1 | 1 | 0 | 1 |
94 | rock | 1 | 2 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 |
95 | ghost | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 2 | 1 |
96 | dragon | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 |
98 * Representing the Data
100 After creating the Pok\eacute{}mon types namespace, we store the table
101 of susceptibilities in =pokemon-table-gen-one= and
102 =pokemon-table-gen-two=, each of which is a simple vector of
103 vectors. Because a vector of vectors can be cumbersome, we do not
104 access the tables directly; instead, we use the derivative structures
105 =attack-strengths= and =defense-strengths=, which are functions which
106 return hash-maps associating each row (respectively column) of the
107 table with its corresponding Pok\eacute{}mon type.
111 #+srcname: header
112 #+begin_src clojure :results silent
113 (ns pokemon.types
114 (:use rlm.ns-rlm))
115 (rlm.ns-rlm/ns-clone rlm.light-base)
116 (use 'clojure.set)
117 #+end_src
119 #+srcname: data(pokemon-table-gen-one=pokemon-table-gen-one, pokemon-table-gen-two=pokemon-table-gen-two)
120 #+begin_src clojure :results silent
121 (in-ns 'pokemon.types)
122 ;; record type strengths as a vector of vectors
123 (def pokemon-gen-one pokemon-table-gen-one)
124 (def pokemon-gen-two pokemon-table-gen-two)
126 (defn type-names [] (vec (doall (map (comp keyword first) pokemon-gen-two))))
128 (defn attack-strengths []
129 (zipmap
130 (type-names)
131 (map (comp vec rest) pokemon-gen-two)))
133 (defn defense-strengths []
134 (zipmap (type-names)
135 (map
136 (apply juxt (map (attack-strengths) (type-names)))
137 (range (count (type-names))))))
138 #+end_src
140 #+begin_src clojure :results output :exports both
141 (clojure.pprint/pprint pokemon.types/pokemon-gen-two)
142 #+end_src
144 #+results:
145 #+begin_example
146 (("normal" 1 1 1 1 1 1 1 1 1 1 1 1 0.5 0 1 1 0.5)
147 ("fire" 1 0.5 0.5 1 2 2 1 1 1 1 1 2 0.5 1 0.5 1 2)
148 ("water" 1 2 0.5 1 0.5 1 1 1 2 1 1 1 2 1 0.5 1 1)
149 ("electric" 1 1 2 0.5 0.5 1 1 1 0 2 1 1 1 1 0.5 1 1)
150 ("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)
151 ("ice" 1 0.5 0.5 1 2 0.5 1 1 2 2 1 1 1 1 2 1 0.5)
152 ("fighting" 2 1 1 1 1 2 1 0.5 1 0.5 0.5 0.5 2 0 1 2 2)
153 ("poison" 1 1 1 1 2 1 1 0.5 0.5 1 1 1 0.5 0.5 1 1 0)
154 ("ground" 1 2 1 2 0.5 1 1 2 1 0 1 0.5 2 1 1 1 2)
155 ("flying" 1 1 1 0.5 2 1 2 1 1 1 1 2 0.5 1 1 1 0.5)
156 ("psychic" 1 1 1 1 1 1 2 2 1 1 0.5 1 1 1 1 0 0.5)
157 ("bug" 1 0.5 1 1 2 1 0.5 0.5 1 0.5 2 1 1 0.5 1 2 0.5)
158 ("rock" 1 2 1 1 1 2 0.5 1 0.5 2 1 2 1 1 1 1 0.5)
159 ("ghost" 0 1 1 1 1 1 1 1 1 1 2 1 1 2 1 0.5 0.5)
160 ("dragon" 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 0.5)
161 ("dark" 1 1 1 1 1 1 0.5 1 1 1 2 1 1 2 1 0.5 0.5)
162 ("steel" 1 0.5 0.5 0.5 1 2 1 1 1 1 1 1 2 1 1 1 0.5))
163 #+end_example
165 =pokemon-gen-two= is a simple list-of-list data structure.
167 #+begin_src clojure :results output :exports both
168 (clojure.pprint/pprint (pokemon.types/defense-strengths))
169 #+end_src
171 #+results:
172 #+begin_example
173 {:water [1 0.5 0.5 2 2 0.5 1 1 1 1 1 1 1 1 1 1 0.5],
174 :psychic [1 1 1 1 1 1 0.5 1 1 1 0.5 2 1 2 1 2 1],
175 :dragon [1 0.5 0.5 0.5 0.5 2 1 1 1 1 1 1 1 1 2 1 1],
176 :fire [1 0.5 2 1 0.5 0.5 1 1 2 1 1 0.5 2 1 1 1 0.5],
177 :ice [1 2 1 1 1 0.5 2 1 1 1 1 1 2 1 1 1 2],
178 :grass [1 2 0.5 0.5 0.5 2 1 2 0.5 2 1 2 1 1 1 1 1],
179 :ghost [0 1 1 1 1 1 0 0.5 1 1 1 0.5 1 2 1 2 1],
180 :poison [1 1 1 1 0.5 1 0.5 0.5 2 1 2 0.5 1 1 1 1 1],
181 :flying [1 1 1 2 0.5 2 0.5 1 0 1 1 0.5 2 1 1 1 1],
182 :normal [1 1 1 1 1 1 2 1 1 1 1 1 1 0 1 1 1],
183 :rock [0.5 0.5 2 1 2 1 2 0.5 2 0.5 1 1 1 1 1 1 2],
184 :electric [1 1 1 0.5 1 1 1 1 2 0.5 1 1 1 1 1 1 0.5],
185 :ground [1 1 2 0 2 2 1 0.5 1 1 1 1 0.5 1 1 1 1],
186 :fighting [1 1 1 1 1 1 1 1 1 2 2 0.5 0.5 1 1 0.5 1],
187 :dark [1 1 1 1 1 1 2 1 1 1 0 2 1 0.5 1 0.5 1],
188 :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],
189 :bug [1 2 1 1 0.5 1 0.5 1 0.5 2 1 1 2 1 1 1 1]}
190 #+end_example
192 =defense-strengths= is a more convenient form of =pokemon-gen-two=, with key/value pair access.
194 * Interfacing with the Data
195 #+srcname: types
196 #+begin_src clojure :results silent
197 (in-ns 'pokemon.types)
199 (defn multitypes "All combinations of up to n types" [n]
200 (vec
201 (map vec
202 (reduce concat
203 (map (partial combinations (type-names))
204 (range 1 (inc n)))))))
206 (defn susceptibility ;; susceptibility-map
207 "Hash-map of the susceptibilities of the given type combination
208 to each type of attack"
209 [pkmn-types]
210 (rlm.map-utils/map-vals
211 clojure.core/rationalize
212 (apply hash-map
213 (interleave (type-names)
214 (apply (partial map *)
215 (map (defense-strengths) pkmn-types))))))
217 (defn susceptance ;; susceptibility
218 "The cumulative susceptibility of the given type combination"
219 [types]
220 (reduce + (map sqr (vals (susceptibility types)))))
221 #+end_src
223 * Best-First Search
225 I'd like to find type combinations that are interesting, but the total
226 number of combinations gets huge as we begin to consider more
227 types. For example, the total possible number of type combinations
228 given just 8 possible types is: 17^{8} = 6975757441 combinations.
229 Therefore, it's prudent to use search.
231 These functions are a simple implementation of best-first search in
232 clojure. The idea to start off with a collection of nodes and some way
233 of finding the best node, and to always expand the best node at every
234 step.
236 #+srcname: search
237 #+begin_src clojure :results silent
238 (in-ns 'pokemon.types)
240 (defn comparatize
241 "Define a comparator which uses the numerical outputs of fn as its criterion.
242 Objects are sorted in increasing numerical order. Objects with the same fn-value
243 are further compared by clojure.core/compare."
244 [fun]
245 (fn [a b]
246 (let [val-a (fun a)
247 val-b (fun b)]
248 (cond
249 ;; if the function cannot differentiate the two values
250 ;; then compare the two values using clojure.core/compare
251 (= val-a val-b) (compare a b)
252 true
253 ;; LOWER values of the function are preferred
254 (compare (- val-a val-b) 0)))))
256 (defn-memo best-first-step [successors [visited unvisited]]
257 (cond (empty? unvisited) nil
258 true
259 (let [best-node (first unvisited)
260 visited* (conj visited best-node)
261 unvisited*
262 (difference
263 (union unvisited (set (successors best-node)))
264 visited*)]
265 (println best-node)
266 [visited* unvisited*])))
268 ;; memoize partial from core so that for example
269 ;; (= (partial + 1) (partial + 1))
270 ;; this way, best first search can take advantage of the memoization
271 ;; of best-first step
272 (undef partial)
273 (def partial (memoize clojure.core/partial))
275 (defn best-first-search
276 "Searches through a network of alternatives, pursuing
277 initially-promising positions first. Comparator defines which
278 positions are more promising, successors produces a list of improved
279 positions from the given position (if any exist), and initial-nodes is
280 a list of starting positions. Returns a lazy sequence of search results
281 [visited-nodes unvisited-nodes], which terminates when
282 there are no remaining unvisited positions."
283 [comparator successors initial-nodes]
284 (let [initial-nodes
285 (apply (partial sorted-set-by comparator) initial-nodes)
286 initial-visited-nodes (sorted-set-by comparator)
287 step (partial best-first-step successors)]
288 (take-while
289 (comp not nil?)
290 (iterate step [initial-visited-nodes initial-nodes]))))
292 #+end_src
295 Now that we have a basic best-first-search, it's convenient to write a
296 few pokemon-type specific convenience functions.
298 #+srcname: pokemon-search
299 #+begin_src clojure :results silent
300 (in-ns 'pokemon.types)
301 (defvar type-compare (comparatize susceptance)
302 "compare two type combinations wrt their susceptibilities")
304 (defn type-successors
305 "Return the set of types that can be made by appending a single type
306 to the given combination."
307 [type]
308 (if (nil? type) '()
309 (set (map (comp vec sort (partial into type)) (multitypes 1)))))
311 (defn immortal?
312 "A type combo is immortal if it is resistant or invulnerable to
313 every pokemon type. This is because that set of types can just be
314 repeated to achieve as low a susceptance as desired"
315 [type]
316 (every? (partial > 1) (vals (susceptibility type))))
318 (defn type-successors*
319 "Stop expanding a type if it's immortal, or if it is longer than or
320 equal to limit-size. Also, only return type additions that are
321 strictly better than the initial type."
322 [limit-size type]
323 (if (or (<= limit-size (count type)) (immortal? type)) '()
324 (set (filter #(< 0 (type-compare type %)) (type-successors type)))))
326 (defn pokemon-type-search
327 "Search among type-combos no greater than length n, limited by limit
328 steps of best-first-search."
329 ([n] (pokemon-type-search n Integer/MAX_VALUE))
330 ([n limit]
331 (first (last
332 (take
333 limit
334 (best-first-search
335 type-compare
336 (partial type-successors* n)
337 (multitypes 1)))))))
339 (defvar immortals
340 (comp (partial filter immortal?) pokemon-type-search)
341 "find all the immortal pokemon types ")
343 #+end_src
345 Because there are so many type combinations, it's important to narrow
346 down the results as much as possible. That is why =type-successors*=
347 only returns types that are actually better than the type it is given.
349 Best-first search can get caught optimizing a single type forever, so
350 it's also important to limit the search space to be finite by setting
351 an upper bound on the length of a type combo.
353 * Results
354 ** The best dual-type combo
356 #+begin_src clojure :results cache verbatim :exports both
357 (first (pokemon.types/pokemon-type-search 2))
358 #+end_src
360 #+results:
361 : [:dark :ghost]
363 Dark and Ghost, which additionally has the property of having no
364 weaknesses to any other type, is the best type combo in terms of
365 susceptance.
367 The Dark and Steel types were introduced many years after
368 pok\eacute{}mon started. In addition to the additional types, the
369 pok\eacute{}mon games gained a few new rules concerning some of the
370 matchups of the original types. Therefore, it's also interesting to see what
371 type combination was most powerful before those types and new rules were introduced.
373 The easiest way to do this with my setup is to just rebind the
374 =pokemon-gen-two= table to the =pokemon-gen-one= table. Since
375 everything that references this variable is a function and we're not
376 doing anything too crazy with lazy-sequences and late-binding, this
377 simple macro will do the job.
379 #+srcname: old-school
380 #+begin_src clojure :results silent
381 (in-ns 'pokemon.types)
383 (defmacro old-school
384 [& forms]
385 `(binding [pokemon-gen-two pokemon-gen-one] ~@forms))
386 #+end_src
388 Using the =old-school= macro, it's easy to find answers for the
389 original 15 pokemon types as well as the expanded pokemon types
390 introduced later.
392 #+begin_src clojure :results verbatim :exports both :cache yes
393 (pokemon.types/old-school (first (pokemon.types/pokemon-type-search 2)))
394 #+end_src
396 #+results[f43470fdf460ed546e9c57879abc9eda56da129f]:
397 : [:ghost :psychic]
399 Ghost and Psychic also manages to have no weaknesses to any of the original
400 types.
402 #+begin_src clojure :results output :exports both
403 (clojure.pprint/pprint
404 (pokemon.types/old-school
405 (pokemon.types/susceptibility [:ghost :psychic])))
406 #+end_src
408 #+results:
409 #+begin_example
410 {:water 1,
411 :psychic 1/2,
412 :dragon 1,
413 :fire 1,
414 :ice 1,
415 :grass 1,
416 :ghost 0,
417 :poison 1/2,
418 :flying 1,
419 :normal 0,
420 :rock 1,
421 :electric 1,
422 :ground 1,
423 :fighting 0,
424 :bug 0}
425 #+end_example
427 ** An Immortal Type
428 It's possible to quickly find an immortal type by giving the search
429 a long enough maximum type length. 50 rounds of search with a max
430 type limit of 10 is enough to find an immortal type.
432 #+begin_src clojure :results scalar :exports both
433 (first (pokemon.types/pokemon-type-search 10 50))
434 #+end_src
436 #+results:
437 : [:dragon :fire :flying :ghost :grass :ground :steel :steel :water :water]
440 #+begin_src clojure :results output :exports both
441 (clojure.pprint/pprint
442 (pokemon.types/susceptibility
443 [:dragon :fire :flying :ghost :grass :ground :steel :steel :water :water]))
444 #+end_src
446 #+results:
447 #+begin_example
448 {:water 1/4,
449 :psychic 1/4,
450 :dragon 1/2,
451 :fire 1/2,
452 :ice 1/2,
453 :grass 1/8,
454 :ghost 1/2,
455 :poison 0,
456 :flying 1/2,
457 :normal 0,
458 :rock 1/2,
459 :electric 0,
460 :ground 0,
461 :fighting 0,
462 :dark 1/2,
463 :steel 1/32,
464 :bug 1/16}
465 #+end_example
467 ** Explanations for Common Pok\eacute{}mon Strategies
469 Many people start out a battle with either a normal pok\eacute{}mon or an
470 electric pok\eacute{}mon, and here's some justification for that choice.
472 #+srcname: weaknesses
473 #+begin_src clojure :results silent
474 (in-ns 'pokemon.types)
475 (defn critical-weaknesses [type]
476 (count (filter #(> % 1) (vals (susceptibility type)))))
477 #+end_src
479 #+begin_src clojure :exports both :results output
480 (clojure.pprint/pprint
481 (sort-by pokemon.types/critical-weaknesses (pokemon.types/multitypes 1)))
482 #+end_src
484 #+results:
485 #+begin_example
486 ([:normal]
487 [:electric]
488 [:water]
489 [:fighting]
490 [:poison]
491 [:ghost]
492 [:dragon]
493 [:dark]
494 [:fire]
495 [:ground]
496 [:flying]
497 [:psychic]
498 [:bug]
499 [:steel]
500 [:ice]
501 [:grass]
502 [:rock])
503 #+end_example
505 Electric and Normal are among the best types with which to start the
506 game, since they have the fewest weaknesses among all the types.
508 At the beginning of the pok\eacute{}mon games, players are given a choice
509 between the Fire pok\eacute{}mon Charmander, the Water pok\eacute{}mon Squirtle, or
510 the Grass/Poison pok\eacute{}mon Bulbasaur.
512 #+begin_src clojure :exports both :results verbatim
513 (sort-by pokemon.types/susceptance [[:fire] [:water] [:grass :poison]])
514 #+end_src
516 #+results:
517 : ([:water] [:fire] [:grass :poison])
519 As can be seen, the Water pok\eacute{}mon Squirtle is the most solid
520 choice starting out, insofar as susceptance is concerned.
522 ** The Worst Pok\eacute{}mon Types
524 #+srcname: weak-types
525 #+begin_src clojure :results silent
526 (in-ns 'pokemon.types)
528 (defn type-compare-weak
529 "compare first by total number of critical-weaknesses,
530 then by overall susceptance, favoring weaker types."
531 [type-1 type-2]
532 (let [measure (memoize (juxt critical-weaknesses susceptance))]
533 (if (= (measure type-2) (measure type-1))
534 (compare type-2 type-1)
535 (compare (measure type-2) (measure type-1)))))
537 (defn resistant?
538 "might as well get rid of types that are resistant to any type"
539 [type]
540 (not (every? #(< 0 %) (vals (susceptibility type)))))
542 (defn type-successors-weak
543 [limit type]
544 (set (if (<= limit (count type)) '()
545 (filter #(< 0 (type-compare-weak type %))
546 (remove resistant? (type-successors type))))))
548 (defn pokemon-type-search-weak
549 "Search among type-combos no greater than length n, limited by limit
550 steps of best-first-search."
551 ([n] (pokemon-type-search-weak n Integer/MAX_VALUE))
552 ([n limit]
553 (first (last
554 (take
555 limit
556 (best-first-search
557 type-compare-weak
558 (partial type-successors-weak n)
559 (multitypes 1)))))))
560 #+end_src
563 #+begin_src clojure :results scalar :exports both
564 (first (pokemon.types/pokemon-type-search-weak 1))
565 #+end_src
567 #+results:
568 : [:rock]
570 Poor Rock. It's just not that good a type. Maybe this is why Brock
571 (who has rock pok\eacute{}mon) is the first gym leader in the games.
573 #+begin_src clojure :results scalar cache :exports both
574 (first (pokemon.types/pokemon-type-search-weak 2))
575 #+end_src
577 #+results:
578 : [:grass :ice]
580 # ;;bonus convergently immortal type combo
581 # (susceptance (vec (concat (repeat 150 :water) (repeat 50 :poison) (repeat 50 :steel) [:ghost :normal :flying :ground :dark])))
583 #+begin_src clojure :results output :exports both
584 (clojure.pprint/pprint
585 (pokemon.types/susceptibility [:grass :ice]))
586 #+end_src
588 #+results:
589 #+begin_example
590 {:water 1/2,
591 :psychic 1,
592 :dragon 1,
593 :fire 4,
594 :ice 1,
595 :grass 1/2,
596 :ghost 1,
597 :poison 2,
598 :flying 2,
599 :normal 1,
600 :rock 2,
601 :electric 1/2,
602 :ground 1/2,
603 :fighting 2,
604 :dark 1,
605 :steel 2,
606 :bug 2}
607 #+end_example
609 This miserable combination is weak to 6 types and double-weak to
610 Fire. No pok\eacute{}mon in the games actually has this type.
612 * Conclusion
614 Searching for a type that is weak to everything takes a very long time
615 and fails to reveal any results. That's the problem with a search
616 over this large problem space --- if there's an easy solution, the
617 search will find it quickly, but it can be very hard to determine
618 whether there is actually a solution.
620 In the [[./lpsolve.org][next installment]], I'll use =lp_solve= to solve this problem in
621 a different way.
625 * COMMENT main program
626 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :exports none
627 <<header>>
628 #+end_src
630 ## this is necessary to define pokemon-table inside the source code.
632 #+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
633 <<data>>
634 #+end_src
636 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :exports none
637 <<types>>
638 <<search>>
639 <<pokemon-search>>
640 <<old-school>>
641 <<weaknesses>>
642 <<weak-types>>
643 #+end_src