view org/types.org @ 4:a227fe337e83

fixed headers
author Robert McIntyre <rlm@mit.edu>
date Thu, 20 Oct 2011 01:12:46 -0700
parents 55bba4805393
children ff9655688ddb
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/ because we are interested in the column totals,
20 which quantify the overall vulnerability of each Pok\eacute{}mon type
21 (as opposed to the row totals, which quantify the overall
22 effectiveness of each attack type.)
24 In the Pok\eacute{}mon games, only four susceptibility values (two,
25 one, one-half, and zero) occur. These numbers indicate particularly
26 high susceptibility, average susceptibility, particularly low
27 susceptibility, and no susceptibility
28 (immunity). Here is the entire Pok\eacute{}mon type chart.
32 ** TODO add the pokemon chart in a pretty form
34 * COMMENT Pokemon Table Data
36 #+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.
37 #+label: pokemon-matchups
38 #+tblname: pokemon-table-gen-two
39 | | normal | fire | water | electric | grass | ice | fighting | poison | ground | flying | psychic | bug | rock | ghost | dragon | dark | steel |
40 |----------+--------+------+-------+----------+-------+-----+----------+--------+--------+--------+---------+-----+------+-------+--------+------+-------|
41 | normal | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 0 | 1 | 1 | .5 |
42 | fire | 1 | .5 | .5 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 1 | 2 |
43 | water | 1 | 2 | .5 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | 1 |
44 | electric | 1 | 1 | 2 | .5 | .5 | 1 | 1 | 1 | 0 | 2 | 1 | 1 | 1 | 1 | .5 | 1 | 1 |
45 | grass | 1 | .5 | 2 | 1 | .5 | 1 | 1 | .5 | 2 | .5 | 1 | .5 | 2 | 1 | .5 | 1 | .5 |
46 | ice | 1 | .5 | .5 | 1 | 2 | .5 | 1 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 |
47 | fighting | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | .5 | .5 | .5 | 2 | 0 | 1 | 2 | 2 |
48 | poison | 1 | 1 | 1 | 1 | 2 | 1 | 1 | .5 | .5 | 1 | 1 | 1 | .5 | .5 | 1 | 1 | 0 |
49 | ground | 1 | 2 | 1 | 2 | .5 | 1 | 1 | 2 | 1 | 0 | 1 | .5 | 2 | 1 | 1 | 1 | 2 |
50 | flying | 1 | 1 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | 1 | 1 | .5 |
51 | psychic | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 1 | 1 | .5 | 1 | 1 | 1 | 1 | 0 | .5 |
52 | bug | 1 | .5 | 1 | 1 | 2 | 1 | .5 | .5 | 1 | .5 | 2 | 1 | 1 | .5 | 1 | 2 | .5 |
53 | rock | 1 | 2 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | .5 |
54 | ghost | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 1 | 2 | 1 | .5 | .5 |
55 | dragon | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | .5 |
56 | dark | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 2 | 1 | .5 | .5 |
57 | steel | 1 | .5 | .5 | .5 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | .5 |
59 #+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.
60 #+label: pokemon-matchups-gen-1
61 #+tblname: pokemon-table-gen-one
62 | | normal | fire | water | electric | grass | ice | fighting | poison | ground | flying | psychic | bug | rock | ghost | dragon |
63 |----------+--------+------+-------+----------+-------+-----+----------+--------+--------+--------+---------+-----+------+-------+--------|
64 | normal | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | .5 | 0 | 1 |
65 | fire | 1 | .5 | .5 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | .5 |
66 | water | 1 | 2 | .5 | 1 | .5 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | 2 | 1 | .5 |
67 | electric | 1 | 1 | 2 | .5 | .5 | 1 | 1 | 1 | 0 | 2 | 1 | 1 | 1 | 1 | .5 |
68 | grass | 1 | .5 | 2 | 1 | .5 | 1 | 1 | .5 | 2 | .5 | 1 | .5 | 2 | 1 | .5 |
69 | ice | 1 | 1 | .5 | 1 | 2 | .5 | 1 | 1 | 2 | 2 | 1 | 1 | 1 | 1 | 2 |
70 | fighting | 2 | 1 | 1 | 1 | 1 | 2 | 1 | .5 | 1 | .5 | .5 | .5 | 2 | 0 | 1 |
71 | poison | 1 | 1 | 1 | 1 | 2 | 1 | 1 | .5 | .5 | 1 | 1 | 2 | .5 | .5 | 1 |
72 | ground | 1 | 2 | 1 | 2 | .5 | 1 | 1 | 2 | 1 | 0 | 1 | .5 | 2 | 1 | 1 |
73 | flying | 1 | 1 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 2 | .5 | 1 | 1 |
74 | psychic | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 1 | 1 | .5 | 1 | 1 | 1 | 1 |
75 | bug | 1 | .5 | 1 | 1 | 2 | 1 | .5 | 2 | 1 | .5 | 2 | 1 | 1 | 0 | 1 |
76 | rock | 1 | 2 | 1 | 1 | 1 | 2 | .5 | 1 | .5 | 2 | 1 | 2 | 1 | 1 | 1 |
77 | ghost | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 2 | 1 |
78 | dragon | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 |
80 * Representing the Data
82 After creating the Pok\eacute{}mon types namespace, we store the table
83 of susceptibilities in =pokemon-table-gen-one= and
84 =pokemon-table-gen-two=, each of which is a simple vector of
85 vectors. Because a vector of vectors can be cumbersome, we do not
86 access the tables directly; instead, we use the derivative structures
87 =attack-strengths= and =defense-strengths=, which are functions which
88 return hash-maps associating each row (respectively column) of the
89 table with its corresponding Pok\eacute{}mon type.
93 #+srcname: header
94 #+begin_src clojure :results silent
95 (ns pokemon.types
96 (:use rlm.ns-rlm))
97 (rlm.ns-rlm/ns-clone rlm.light-base)
98 (use 'clojure.set)
99 #+end_src
101 #+srcname: data(pokemon-table-gen-one=pokemon-table-gen-one, pokemon-table-gen-two=pokemon-table-gen-two)
102 #+begin_src clojure :results silent
103 (in-ns 'pokemon.types)
104 ;; record type strengths as a vector of vectors
105 (def pokemon-gen-one pokemon-table-gen-one)
106 (def pokemon-gen-two pokemon-table-gen-two)
108 (defn type-names [] (vec (doall (map (comp keyword first) pokemon-gen-two))))
110 (defn attack-strengths []
111 (zipmap
112 (type-names)
113 (map (comp vec rest) pokemon-gen-two)))
115 (defn defense-strengths []
116 (zipmap (type-names)
117 (map
118 (apply juxt (map (attack-strengths) (type-names)))
119 (range (count (type-names))))))
120 #+end_src
122 #+begin_src clojure :results output :exports both
123 (clojure.pprint/pprint pokemon.types/pokemon-gen-two)
124 #+end_src
126 #+results:
127 #+begin_example
128 (("normal" 1 1 1 1 1 1 1 1 1 1 1 1 0.5 0 1 1 0.5)
129 ("fire" 1 0.5 0.5 1 2 2 1 1 1 1 1 2 0.5 1 0.5 1 2)
130 ("water" 1 2 0.5 1 0.5 1 1 1 2 1 1 1 2 1 0.5 1 1)
131 ("electric" 1 1 2 0.5 0.5 1 1 1 0 2 1 1 1 1 0.5 1 1)
132 ("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)
133 ("ice" 1 0.5 0.5 1 2 0.5 1 1 2 2 1 1 1 1 2 1 0.5)
134 ("fighting" 2 1 1 1 1 2 1 0.5 1 0.5 0.5 0.5 2 0 1 2 2)
135 ("poison" 1 1 1 1 2 1 1 0.5 0.5 1 1 1 0.5 0.5 1 1 0)
136 ("ground" 1 2 1 2 0.5 1 1 2 1 0 1 0.5 2 1 1 1 2)
137 ("flying" 1 1 1 0.5 2 1 2 1 1 1 1 2 0.5 1 1 1 0.5)
138 ("psychic" 1 1 1 1 1 1 2 2 1 1 0.5 1 1 1 1 0 0.5)
139 ("bug" 1 0.5 1 1 2 1 0.5 0.5 1 0.5 2 1 1 0.5 1 2 0.5)
140 ("rock" 1 2 1 1 1 2 0.5 1 0.5 2 1 2 1 1 1 1 0.5)
141 ("ghost" 0 1 1 1 1 1 1 1 1 1 2 1 1 2 1 0.5 0.5)
142 ("dragon" 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 0.5)
143 ("dark" 1 1 1 1 1 1 0.5 1 1 1 2 1 1 2 1 0.5 0.5)
144 ("steel" 1 0.5 0.5 0.5 1 2 1 1 1 1 1 1 2 1 1 1 0.5))
145 #+end_example
147 =pokemon-gen-two= is a simple list-of-list data structure.
149 #+begin_src clojure :results output :exports both
150 (clojure.pprint/pprint (pokemon.types/defense-strengths))
151 #+end_src
153 #+results:
154 #+begin_example
155 {:water [1 0.5 0.5 2 2 0.5 1 1 1 1 1 1 1 1 1 1 0.5],
156 :psychic [1 1 1 1 1 1 0.5 1 1 1 0.5 2 1 2 1 2 1],
157 :dragon [1 0.5 0.5 0.5 0.5 2 1 1 1 1 1 1 1 1 2 1 1],
158 :fire [1 0.5 2 1 0.5 0.5 1 1 2 1 1 0.5 2 1 1 1 0.5],
159 :ice [1 2 1 1 1 0.5 2 1 1 1 1 1 2 1 1 1 2],
160 :grass [1 2 0.5 0.5 0.5 2 1 2 0.5 2 1 2 1 1 1 1 1],
161 :ghost [0 1 1 1 1 1 0 0.5 1 1 1 0.5 1 2 1 2 1],
162 :poison [1 1 1 1 0.5 1 0.5 0.5 2 1 2 0.5 1 1 1 1 1],
163 :flying [1 1 1 2 0.5 2 0.5 1 0 1 1 0.5 2 1 1 1 1],
164 :normal [1 1 1 1 1 1 2 1 1 1 1 1 1 0 1 1 1],
165 :rock [0.5 0.5 2 1 2 1 2 0.5 2 0.5 1 1 1 1 1 1 2],
166 :electric [1 1 1 0.5 1 1 1 1 2 0.5 1 1 1 1 1 1 0.5],
167 :ground [1 1 2 0 2 2 1 0.5 1 1 1 1 0.5 1 1 1 1],
168 :fighting [1 1 1 1 1 1 1 1 1 2 2 0.5 0.5 1 1 0.5 1],
169 :dark [1 1 1 1 1 1 2 1 1 1 0 2 1 0.5 1 0.5 1],
170 :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],
171 :bug [1 2 1 1 0.5 1 0.5 1 0.5 2 1 1 2 1 1 1 1]}
172 #+end_example
174 =defense-strengths= is a more convenient form of =pokemon-gen-two=, with key/value pair access.
176 * Interfacing with the Data
177 #+srcname: types
178 #+begin_src clojure :results silent
179 (in-ns 'pokemon.types)
181 (defn multitypes "All combinations of up to n types" [n]
182 (vec
183 (map vec
184 (reduce concat
185 (map (partial combinations (type-names))
186 (range 1 (inc n)))))))
188 (defn susceptibility ;; susceptibility-map
189 "Hash-map of the susceptibilities of the given type combination
190 to each type of attack"
191 [pkmn-types]
192 (rlm.map-utils/map-vals
193 clojure.core/rationalize
194 (apply hash-map
195 (interleave (type-names)
196 (apply (partial map *)
197 (map (defense-strengths) pkmn-types))))))
199 (defn susceptance ;; susceptibility
200 "The cumulative susceptibility of the given type combination"
201 [types]
202 (reduce + (map sqr (vals (susceptibility types)))))
203 #+end_src
205 * Best-First Search
207 I'd like to find type combinations that are interesting, but the total
208 number of combinations gets huge as we begin to consider more
209 types. For example, the total possible number of type combinations
210 given just 8 possible types is: 17^{8} = 6975757441 combinations.
211 Therefore, it's prudent to use search.
213 These functions are a simple implementation of best-first search in
214 clojure. The idea to start off with a collection of nodes and some way
215 of finding the best node, and to always expand the best node at every
216 step.
218 #+srcname: search
219 #+begin_src clojure :results silent
220 (in-ns 'pokemon.types)
222 (defn comparatize
223 "Define a comparator which uses the numerical outputs of fn as its criterion.
224 Objects are sorted in increasing numerical order. Objects with the same fn-value
225 are further compared by clojure.core/compare."
226 [fun]
227 (fn [a b]
228 (let [val-a (fun a)
229 val-b (fun b)]
230 (cond
231 ;; if the function cannot differentiate the two values
232 ;; then compare the two values using clojure.core/compare
233 (= val-a val-b) (compare a b)
234 true
235 ;; LOWER values of the function are preferred
236 (compare (- val-a val-b) 0)))))
238 (defn-memo best-first-step [successors [visited unvisited]]
239 (cond (empty? unvisited) nil
240 true
241 (let [best-node (first unvisited)
242 visited* (conj visited best-node)
243 unvisited*
244 (difference
245 (union unvisited (set (successors best-node)))
246 visited*)]
247 (println best-node)
248 [visited* unvisited*])))
250 ;; memoize partial from core so that for example
251 ;; (= (partial + 1) (partial + 1))
252 ;; this way, best first search can take advantage of the memoization
253 ;; of best-first step
254 (undef partial)
255 (def partial (memoize clojure.core/partial))
257 (defn best-first-search
258 "Searches through a network of alternatives, pursuing
259 initially-promising positions first. Comparator defines which
260 positions are more promising, successors produces a list of improved
261 positions from the given position (if any exist), and initial-nodes is
262 a list of starting positions. Returns a lazy sequence of search results
263 [visited-nodes unvisited-nodes], which terminates when
264 there are no remaining unvisited positions."
265 [comparator successors initial-nodes]
266 (let [initial-nodes
267 (apply (partial sorted-set-by comparator) initial-nodes)
268 initial-visited-nodes (sorted-set-by comparator)
269 step (partial best-first-step successors)]
270 (take-while
271 (comp not nil?)
272 (iterate step [initial-visited-nodes initial-nodes]))))
274 #+end_src
277 Now that we have a basic best-first-search, it's convenient to write a
278 few pokemon-type specific convenience functions.
280 #+srcname: pokemon-search
281 #+begin_src clojure :results silent
282 (in-ns 'pokemon.types)
283 (defvar type-compare (comparatize susceptance)
284 "compare two type combinations wrt their susceptibilities")
286 (defn type-successors
287 "Return the set of types that can be made by appending a single type
288 to the given combination."
289 [type]
290 (if (nil? type) '()
291 (set (map (comp vec sort (partial into type)) (multitypes 1)))))
293 (defn immortal?
294 "A type combo is immortal if it is resistant or invulnerable to
295 every pokemon type. This is because that set of types can just be
296 repeated to achieve as low a susceptance as desired"
297 [type]
298 (every? (partial > 1) (vals (susceptibility type))))
300 (defn type-successors*
301 "Stop expanding a type if it's immortal, or if it is longer than or
302 equal to limit-size. Also, only return type additions that are
303 strictly better than the initial type."
304 [limit-size type]
305 (if (or (<= limit-size (count type)) (immortal? type)) '()
306 (set (filter #(< 0 (type-compare type %)) (type-successors type)))))
308 (defn pokemon-type-search
309 "Search among type-combos no greater than length n, limited by limit
310 steps of best-first-search."
311 ([n] (pokemon-type-search n Integer/MAX_VALUE))
312 ([n limit]
313 (first (last
314 (take
315 limit
316 (best-first-search
317 type-compare
318 (partial type-successors* n)
319 (multitypes 1)))))))
321 (defvar immortals
322 (comp (partial filter immortal?) pokemon-type-search)
323 "find all the immortal pokemon types ")
325 #+end_src
327 Because there are so many type combinations, it's important to narrow
328 down the results as much as possible. That is why =type-successors*=
329 only returns types that are actually better than the type it is given.
331 Best-first search can get caught optimizing a single type forever, so
332 it's also important to limit the search space to be finite by setting
333 an upper bound on the length of a type combo.
335 * Results
336 ** The best dual-type combo
338 #+begin_src clojure :results cache verbatim :exports both
339 (first (pokemon.types/pokemon-type-search 2))
340 #+end_src
342 #+results:
343 : [:dark :ghost]
345 Dark and Ghost, which additionally has the property of having no
346 weaknesses to any other type, is the best type combo in terms of
347 susceptance.
349 The Dark and Steel types were introduced many years after
350 pok\eacute{}mon started. In addition to the additional types, the
351 pok\eacute{}mon games gained a few new rules concerning some of the
352 matchups of the original types. Therefore, it's also interesting to see what
353 type combination was most powerful before those types and new rules were introduced.
355 The easiest way to do this with my setup is to just rebind the
356 =pokemon-gen-two= table to the =pokemon-gen-one= table. Since
357 everything that references this variable is a function and we're not
358 doing anything too crazy with lazy-sequences and late-binding, this
359 simple macro will do the job.
361 #+srcname: old-school
362 #+begin_src clojure :results silent
363 (in-ns 'pokemon.types)
365 (defmacro old-school
366 [& forms]
367 `(binding [pokemon-gen-two pokemon-gen-one] ~@forms))
368 #+end_src
370 Using the =old-school= macro, it's easy to find answers for the
371 original 15 pokemon types as well as the expanded pokemon types
372 introduced later.
374 #+begin_src clojure :results verbatim :exports both :cache yes
375 (pokemon.types/old-school (first (pokemon.types/pokemon-type-search 2)))
376 #+end_src
378 #+results[f43470fdf460ed546e9c57879abc9eda56da129f]:
379 : [:ghost :psychic]
381 Ghost and Psychic also manages to have no weaknesses to any of the original
382 types.
384 #+begin_src clojure :results output :exports both
385 (clojure.pprint/pprint
386 (pokemon.types/old-school
387 (pokemon.types/susceptibility [:ghost :psychic])))
388 #+end_src
390 #+results:
391 #+begin_example
392 {:water 1,
393 :psychic 1/2,
394 :dragon 1,
395 :fire 1,
396 :ice 1,
397 :grass 1,
398 :ghost 0,
399 :poison 1/2,
400 :flying 1,
401 :normal 0,
402 :rock 1,
403 :electric 1,
404 :ground 1,
405 :fighting 0,
406 :bug 0}
407 #+end_example
409 ** An Immortal Type
410 It's possible to quickly find an immortal type by giving the search
411 a long enough maximum type length. 50 rounds of search with a max
412 type limit of 10 is enough to find an immortal type.
414 #+begin_src clojure :results scalar :exports both
415 (first (pokemon.types/pokemon-type-search 10 50))
416 #+end_src
418 #+results:
419 : [:dragon :fire :flying :ghost :grass :ground :steel :steel :water :water]
422 #+begin_src clojure :results output :exports both
423 (clojure.pprint/pprint
424 (pokemon.types/susceptibility
425 [:dragon :fire :flying :ghost :grass :ground :steel :steel :water :water]))
426 #+end_src
428 #+results:
429 #+begin_example
430 {:water 1/4,
431 :psychic 1/4,
432 :dragon 1/2,
433 :fire 1/2,
434 :ice 1/2,
435 :grass 1/8,
436 :ghost 1/2,
437 :poison 0,
438 :flying 1/2,
439 :normal 0,
440 :rock 1/2,
441 :electric 0,
442 :ground 0,
443 :fighting 0,
444 :dark 1/2,
445 :steel 1/32,
446 :bug 1/16}
447 #+end_example
449 ** Explanations for Common Pok\eacute{}mon Strategies
451 Many people start out a battle with either a normal pok\eacute{}mon or an
452 electric pok\eacute{}mon, and here's some justification for that choice.
454 #+srcname: weaknesses
455 #+begin_src clojure :results silent
456 (in-ns 'pokemon.types)
457 (defn critical-weaknesses [type]
458 (count (filter #(> % 1) (vals (susceptibility type)))))
459 #+end_src
461 #+begin_src clojure :exports both :results output
462 (clojure.pprint/pprint
463 (sort-by pokemon.types/critical-weaknesses (pokemon.types/multitypes 1)))
464 #+end_src
466 #+results:
467 #+begin_example
468 ([:normal]
469 [:electric]
470 [:water]
471 [:fighting]
472 [:poison]
473 [:ghost]
474 [:dragon]
475 [:dark]
476 [:fire]
477 [:ground]
478 [:flying]
479 [:psychic]
480 [:bug]
481 [:steel]
482 [:ice]
483 [:grass]
484 [:rock])
485 #+end_example
487 Electric and Normal are among the best types with which to start the
488 game, since they have the fewest weaknesses among all the types.
490 At the beginning of the pok\eacute{}mon games, players are given a choice
491 between the Fire pok\eacute{}mon Charmander, the Water pok\eacute{}mon Squirtle, or
492 the Grass/Poison pok\eacute{}mon Bulbasaur.
494 #+begin_src clojure :exports both :results verbatim
495 (sort-by pokemon.types/susceptance [[:fire] [:water] [:grass :poison]])
496 #+end_src
498 #+results:
499 : ([:water] [:fire] [:grass :poison])
501 As can be seen, the Water pok\eacute{}mon Squirtle is the most solid
502 choice starting out, insofar as susceptance is concerned.
504 ** The Worst Pok\eacute{}mon Types
506 #+srcname: weak-types
507 #+begin_src clojure :results silent
508 (in-ns 'pokemon.types)
510 (defn type-compare-weak
511 "compare first by total number of critical-weaknesses,
512 then by overall susceptance, favoring weaker types."
513 [type-1 type-2]
514 (let [measure (memoize (juxt critical-weaknesses susceptance))]
515 (if (= (measure type-2) (measure type-1))
516 (compare type-2 type-1)
517 (compare (measure type-2) (measure type-1)))))
519 (defn resistant?
520 "might as well get rid of types that are resistant to any type"
521 [type]
522 (not (every? #(< 0 %) (vals (susceptibility type)))))
524 (defn type-successors-weak
525 [limit type]
526 (set (if (<= limit (count type)) '()
527 (filter #(< 0 (type-compare-weak type %))
528 (remove resistant? (type-successors type))))))
530 (defn pokemon-type-search-weak
531 "Search among type-combos no greater than length n, limited by limit
532 steps of best-first-search."
533 ([n] (pokemon-type-search-weak n Integer/MAX_VALUE))
534 ([n limit]
535 (first (last
536 (take
537 limit
538 (best-first-search
539 type-compare-weak
540 (partial type-successors-weak n)
541 (multitypes 1)))))))
542 #+end_src
545 #+begin_src clojure :results scalar :exports both
546 (first (pokemon.types/pokemon-type-search-weak 1))
547 #+end_src
549 #+results:
550 : [:rock]
552 Poor Rock. It's just not that good a type. Maybe this is why Brock
553 (who has rock pok\eacute{}mon) is the first gym leader in the games.
555 #+begin_src clojure :results scalar cache :exports both
556 (first (pokemon.types/pokemon-type-search-weak 2))
557 #+end_src
559 #+results:
560 : [:grass :ice]
562 # ;;bonus convergently immortal type combo
563 # (susceptance (vec (concat (repeat 150 :water) (repeat 50 :poison) (repeat 50 :steel) [:ghost :normal :flying :ground :dark])))
565 #+begin_src clojure :results output :exports both
566 (clojure.pprint/pprint
567 (pokemon.types/susceptibility [:grass :ice]))
568 #+end_src
570 #+results:
571 #+begin_example
572 {:water 1/2,
573 :psychic 1,
574 :dragon 1,
575 :fire 4,
576 :ice 1,
577 :grass 1/2,
578 :ghost 1,
579 :poison 2,
580 :flying 2,
581 :normal 1,
582 :rock 2,
583 :electric 1/2,
584 :ground 1/2,
585 :fighting 2,
586 :dark 1,
587 :steel 2,
588 :bug 2}
589 #+end_example
591 This miserable combination is weak to 6 types and double-weak to
592 Fire. No pok\eacute{}mon in the games actually has this type.
594 * Conclusion
596 Searching for a type that is weak to everything takes a very long time
597 and fails to reveal any results. That's the problem with a search
598 over this large problem space --- if there's an easy solution, the
599 search will find it quickly, but it can be very hard to determine
600 whether there is actually a solution.
602 In the [[./lpsolve.org][next installment]], I'll use =lp_solve= to solve this problem in
603 a different way.
606 * COMMENT main program
607 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :exports none
608 <<header>>
609 #+end_src
611 ## this is necessary to define pokemon-table inside the source code.
613 #+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
614 <<data>>
615 #+end_src
617 #+begin_src clojure :noweb yes :tangle ../src/pokemon/types.clj :exports none
618 <<types>>
619 <<search>>
620 <<pokemon-search>>
621 <<old-school>>
622 <<weaknesses>>
623 <<weak-types>>
624 #+end_src