Mercurial > pokemon-types
comparison org/lpsolve.org @ 0:e03d363ed9a9
initial committ for pokemon types report
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sun, 16 Oct 2011 06:57:42 -0700 |
parents | |
children | 55bba4805393 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:e03d363ed9a9 |
---|---|
1 #+title: Discovering Effective Pokemon Types Using Linear Optimization | |
2 #+author: Robert McIntyre & Dylan Holmes | |
3 #+EMAIL: rlm@mit.edu | |
4 #+MATHJAX: align:"left" mathml:t path:"../MathJax/MathJax.js" | |
5 #+STYLE: <link rel="stylesheet" type="text/css" href="../css/aurellem.css" /> | |
6 #+OPTIONS: H:3 num:t toc:t \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t | |
7 #+SETUPFILE: ../templates/level-0.org | |
8 #+INCLUDE: ../templates/level-0.org | |
9 | |
10 | |
11 | |
12 * Introduction | |
13 In the [[./types.org][previous post]], we used the best-first search algorithm to | |
14 locate the most effective Pok\eacute{}mon type | |
15 combinations. Afterwards, we realized that we could transform this | |
16 search problem into a /linear optimization problem/. This conversion | |
17 offered several advantages: first, search algorithms are comparatively | |
18 slow, whereas linear optimization algorithms are extremely fast; | |
19 second, it is difficult to determine whether a search problem has any | |
20 solution, whereas it is straightforward to determine whether a linear | |
21 optimization problem has any solution; finally, because systems of | |
22 linear equations are so common, many programming languages have linear | |
23 equation solvers written for them. | |
24 | |
25 In this article, we will | |
26 - Solve a simple linear optimization problem in C :: We demonstrate | |
27 how to use the linear programming C library, =lp_solve=, to | |
28 solve a simple linear | |
29 optimization problem. | |
30 - Incorporate a C library into Clojure :: We will show how we gave | |
31 Clojure access to the linear programming C library, =lp_solve=. | |
32 - Find effective Pokemon types using linear programming :: Building | |
33 on our earlier code, (...) | |
34 - Present our results :: (!) | |
35 | |
36 #which can be represented and solved as a system of linear equations. | |
37 | |
38 * COMMENT | |
39 This post continues the [[./types.org][previous one]] about pok\eacute{}mon types. | |
40 Pok\eacute{}mon is a game in which adorable creatures battle each | |
41 other using fantastic attacks. It was made into a several gameboy | |
42 games that all share the same battle system. Every pok\eacute{}mon in the | |
43 gameboy game has one or two /types/, such as Ground, Fire, Water, | |
44 etc. Every pok\eacute{}mon attack has exactly one type. Certain defending | |
45 types are weak or strong to other attacking types. For example, Water | |
46 attacks are strong against Fire pok\eacute{}mon, while Electric attacks are | |
47 weak against Ground Pok\eacute{}mon. In the games, attacks can be either | |
48 twice as effective as normal (Water vs. Fire), neutrally effective | |
49 (Normal vs. Normal), half as effective (Fire vs. Water), or not | |
50 effective at all (Electric vs. Ground). Thus the range of defense | |
51 values for a particular type is the set 0, 1/2, 1, 2. These are | |
52 referred to in the game as being immune, resistant, neutral, and | |
53 weak, respectively. I call them the /susceptance/ of one type to another. | |
54 | |
55 If a pokemon has two types, then the strengths and weakness of each | |
56 type are /multiplied/ together. Thus Electric (2x weak to Ground) | |
57 combined with Flying (immune to Ground (0x)) is immune to | |
58 Ground. Fire (2x weak to Water) combined with Water (1/2x resistant | |
59 to Water) is neutral to Water. If both types are resistant to another | |
60 type, then the combination is doubly-resistant (1/4x) to that type. If | |
61 both types are weak to a certain type then the combination is | |
62 double-weak (4x) to that type. | |
63 | |
64 ** Immortal Types | |
65 | |
66 In the game, pok\eacute{}mon can have either one type, or two types. If this | |
67 restriction is lifted, is there any combination of types that is | |
68 resistant to all types? I call such a combination an /Immortal Type/, | |
69 since if that type's pattern was repeated over and over again towards | |
70 infinity, the resulting type would be immune to all attack types. | |
71 | |
72 * Linear Programming | |
73 | |
74 ** Terminology | |
75 Linear programming is the process of finding an optimal solution to a | |
76 linear equation of several variables which are constrained by some linear | |
77 inequalities. | |
78 | |
79 In linear programming terminology, the function to be extremized is | |
80 the /objective function/. | |
81 | |
82 ** COMMENT | |
83 First, we'll give a small example of a linear optimization problem, | |
84 and show how it can be solved with Clojure and =lp_solve=. Then, | |
85 we'll show how finding immortal pok\eacute{}mon types can be converted | |
86 into a linear problem suitable for solving in the same way. | |
87 | |
88 ** The Farmer's Problem | |
89 | |
90 Let's solve the Farmer's Problem, a typical linear programming problem | |
91 borrowed from http://lpsolve.sourceforge.net/5.5/formulate.htm. | |
92 | |
93 | |
94 #+BEGIN_QUOTE | |
95 *The Farmer's Problem:* Suppose a farmer has 75 acres on which to | |
96 plant two crops: wheat and barley. To produce these crops, it costs | |
97 the farmer (for seed, fertilizer, etc.) $120 per acre for the wheat | |
98 and $210 per acre for the barley. The farmer has $15000 available for | |
99 expenses. But after the harvest, the farmer must store the crops while | |
100 awaiting favorable market conditions. The farmer has storage space | |
101 for 4000 bushels. Each acre yields an average of 110 bushels of wheat | |
102 or 30 bushels of barley. If the net profit per bushel of wheat (after | |
103 all expenses have been subtracted) is $1.30 and for barley is $2.00, | |
104 how should the farmer plant the 75 acres to maximize profit? | |
105 #+END_QUOTE | |
106 | |
107 The Farmer's Problem is to maximize profit subject to constraints on | |
108 available farmland, funds for expenses, and storage space. | |
109 | |
110 | | Wheat | Barley | Maximum total | | |
111 |----------+----------------------+---------------------+--------------| | |
112 | / | < | > | <> | | |
113 | Farmland | \(w\) acres | \(b\) acres | 75 acres | | |
114 | Expense | $120 per acre | $210 per acre | $15000 | | |
115 | Storage | 110 bushels per acre | 30 bushels per acre | 4000 bushels | | |
116 |----------+----------------------+---------------------+--------------| | |
117 | Profit | $1.30 per bushel | $2.00 per bushel | | | |
118 | |
119 *** COMMENT | |
120 can be represented as a linear optimization | |
121 problem. In this form, it is a problem with two variables\mdash{}the number of | |
122 acres of wheat, \(w\), and the number of acres of barley, \(b\). The | |
123 aim is to maximize profit, which | |
124 | |
125 subject to three constraints: the farmer can't spend more money | |
126 than he has, the farmer can't use more acres than he owns, and the harvest has | |
127 to fit in his storage space. | |
128 | |
129 We can express these constraints succinctly using matrix | |
130 notation. Denoting the number of acres of barley and wheat by \(b\) and \(w\), | |
131 we want to maximize the expression \(143 w + 60 b\) subject to | |
132 | |
133 \( | |
134 \begin{cases} | |
135 120 w + 210 b & \leq & 1500\\ | |
136 110 w + 30 b & \leq & 4000\\ | |
137 1 w + 1 w & \leq & 75 | |
138 \end{cases} | |
139 \) | |
140 | |
141 #\(\begin{bmatrix}120&210\\110&30\\1 & | |
142 #1\end{bmatrix}\;\begin{bmatrix}w\\b\end{bmatrix} | |
143 #\leq \begin{bmatrix}\$15000\\4000\text{ bushels}\\75\text{ acres}\end{bmatrix}\) | |
144 | |
145 | |
146 ** Solution using LP Solve | |
147 #(LP solve is available at http://www.example.com.) | |
148 In a new file, =farmer.lp=, we list the variables and constraints | |
149 of our problem using LP Solve syntax. | |
150 | |
151 #+begin_src lpsolve :tangle farmer.lp | |
152 /* Maximize Total Profit */ | |
153 max: +143 wheat +60 barley; | |
154 | |
155 | |
156 /* -------- Constraints --------*/ | |
157 | |
158 /* the farmer can't spend more money than he has */ | |
159 +120 wheat +210 barley <= 15000; | |
160 | |
161 /* the harvest has to fit in his storage space */ | |
162 +110 wheat +30 barley <= 4000; | |
163 | |
164 /* he can't use more acres than he owns */ | |
165 +wheat +barley <= 75; | |
166 #+end_src | |
167 | |
168 | |
169 #This is a set of linear equations ideal for solving using a program like | |
170 #=lp_solve=. In Linear Algebra terms we are maximizing the linear function | |
171 | |
172 #\(\text{profit} = 143\text{ wheat} + 60\text{ barley}\), subject to the constraints | |
173 | |
174 #Ax <= b, | |
175 | |
176 #where A is [120 210 110 30 1 1], x is [wheat barley] and b is [15000 | |
177 #4000 75]. | |
178 | |
179 Running the =lp_solve= program on =farmer.lp= yields the following output. | |
180 | |
181 #+begin_src sh :exports both :results scalar | |
182 ~/roBin/lpsolve/lp_solve ~/aurellem/src/pokemon/farmer.lp | |
183 #+end_src | |
184 | |
185 #+results: | |
186 : | |
187 : Value of objective function: 6315.62500000 | |
188 : | |
189 : Actual values of the variables: | |
190 : wheat 21.875 | |
191 : barley 53.125 | |
192 | |
193 This shows that the farmer can maximize his profit by planting 21.875 | |
194 of the available acres with wheat and the remaining 53.125 acres with | |
195 barley; by doing so, he will make $6315.62(5) in profit. | |
196 | |
197 | |
198 #The farmer can make a profit of $6315.62 by planting 21.875 acres of | |
199 #his land with wheat and the other 53.125 acres of his land with barley. | |
200 | |
201 * Incorporating =lp_solve= into Clojure | |
202 | |
203 There is a Java API available which enables Java programs to use Lp | |
204 Solve. Although Clojure can use this Java API directly, the | |
205 interaction between Java, C, and Clojure is clumsy: | |
206 | |
207 | |
208 The Java API for LP Solve makes it possible to use Lp Solve algorithms | |
209 within Java. Although Clojure can use this Java API directly, | |
210 | |
211 | |
212 ** The Farmer's Problem in Clojure | |
213 We are going to solve the same problem involving wheat and barley, | |
214 that we did above, but this time using clojure and the lpsolve API. | |
215 | |
216 #Because we ultimately intend to use Lp Solve to find optimal Pokemon type combinations. | |
217 # we want to solve linear optimization problems within Clojure, the language | |
218 | |
219 ** Setup | |
220 =lp_solve= is a crufty =C= program which already comes with a JNI | |
221 interface written by Juergen Ebert. It's API is not | |
222 particularly friendly from a functional/immutable perspective, but | |
223 with a little work, we can build something that works great with | |
224 clojure. | |
225 | |
226 #+srcname: intro | |
227 #+begin_src clojure :results silent | |
228 (ns pokemon.lpsolve | |
229 (:use rlm.ns-rlm)) | |
230 (rlm.ns-rlm/ns-clone rlm.light-base) | |
231 (use 'clojure.set) | |
232 (import 'lpsolve.LpSolve) | |
233 (use '[clojure [pprint :only [pprint]]]) | |
234 #+end_src | |
235 | |
236 The LpSolve Java interface is available from the same site as | |
237 =lp_solve= itself, http://lpsolve.sourceforge.net/ | |
238 Using it is the same as many other =C= | |
239 programs. There are excellent instructions to get set | |
240 up. The short version is that you must call Java with | |
241 =-Djava.library.path=/path/to/lpsolve/libraries= and also add the | |
242 libraries to your export =LD_LIBRARY_PATH= if you are using Linux. For | |
243 example, in my =.bashrc= file, I have the line | |
244 =LD_LIBRARY_PATH=$HOME/roBin/lpsolve:$LD_LIBRARY_PATH=. | |
245 If everything is set-up correctly, | |
246 | |
247 #+srcname: body | |
248 #+begin_src clojure :results verbatim :exports both | |
249 (import 'lpsolve.LpSolve) | |
250 #+end_src | |
251 | |
252 #+results: body | |
253 : lpsolve.LpSolve | |
254 | |
255 should run with no problems. | |
256 | |
257 ** Making a DSL to talk with LpSolve | |
258 *** Problems | |
259 Since we are using a =C= wrapper, we have to deal with manual memory | |
260 management for the =C= structures which are wrapped by the =LpSolve= | |
261 object. Memory leaks in =LpSolve= instances can crash the JVM, so it's | |
262 very important to get it right. Also, the Java wrapper follows the | |
263 =C= tradition closely and defines many =static final int= constants | |
264 for the different states of the =LpSolve= instance instead of using Java | |
265 enums. The calling convention for adding rows and columns to | |
266 the constraint matrix is rather complicated and must be done column by | |
267 column or row by row, which can be error prone. Finally, I'd like to | |
268 gather all the important output information from the =LpSolve= instance | |
269 into a final, immutable structure. | |
270 | |
271 In summary, the issues I'd like to address are: | |
272 | |
273 - reliable memory management | |
274 - functional interface to =LpSolve= | |
275 - intelligible, immutable output | |
276 | |
277 To deal with these issues I'll create four functions for interfacing | |
278 with =LpSolve= | |
279 | |
280 #+srcname: declares | |
281 #+begin_src clojure :results silent | |
282 (in-ns 'pokemon.lpsolve) | |
283 | |
284 ;; deal with automatic memory management for LpSolve instance. | |
285 (declare linear-program) | |
286 | |
287 ;; functional interface to LpSolve | |
288 (declare lp-solve) | |
289 | |
290 ;; immutable output from lp-solve | |
291 (declare solve get-results) | |
292 #+end_src | |
293 | |
294 *** Memory Management | |
295 | |
296 Every instance of =LpSolve= must be manually garbage collected via a | |
297 call to =deleteLP=. I use a non-hygienic macro similar to =with-open= | |
298 to ensure that =deleteLP= is always called. | |
299 | |
300 #+srcname: memory-management | |
301 #+begin_src clojure :results silent | |
302 (in-ns 'pokemon.lpsolve) | |
303 (defmacro linear-program | |
304 "solve a linear programming problem using LpSolve syntax. | |
305 within the macro, the variable =lps= is bound to the LpSolve instance." | |
306 [& statements] | |
307 (list 'let '[lps (LpSolve/makeLp 0 0)] | |
308 (concat '(try) | |
309 statements | |
310 ;; always free the =C= data structures. | |
311 '((finally (.deleteLp lps)))))) | |
312 #+end_src | |
313 | |
314 | |
315 The macro captures the variable =lps= within its body, providing for a | |
316 convenient way to access the object using any of the methods of the | |
317 =LpSolve= API without having to worry about when to call | |
318 =deleteLP=. | |
319 | |
320 *** Sensible Results | |
321 The =linear-program= macro deletes the actual =lps= object once it is | |
322 done working, so it's important to collect the important results and | |
323 add return them in an immutable structure at the end. | |
324 | |
325 #+srcname: get-results | |
326 #+begin_src clojure :results silent | |
327 (in-ns 'pokemon.lpsolve) | |
328 | |
329 (defrecord LpSolution | |
330 [objective-value | |
331 optimal-values | |
332 variable-names | |
333 solution | |
334 status | |
335 model]) | |
336 | |
337 (defn model | |
338 "Returns a textual representation of the problem suitable for | |
339 direct input to the =lp_solve= program (lps format)" | |
340 [#^LpSolve lps] | |
341 (let [target (java.io.File/createTempFile "lps" ".lp")] | |
342 (.writeLp lps (.getPath target)) | |
343 (slurp target))) | |
344 | |
345 (defn results | |
346 "given an LpSolve object, solves the object and returns a map of the | |
347 essential values which compose the solution." | |
348 [#^LpSolve lps] | |
349 (locking lps | |
350 (let [status (solve lps) | |
351 number-of-variables (.getNcolumns lps) | |
352 optimal-values (double-array number-of-variables) | |
353 optimal-values (do (.getVariables lps optimal-values) | |
354 (seq optimal-values)) | |
355 variable-names | |
356 (doall ;; the doall is necessary since the lps object might | |
357 ;; soon be deleted | |
358 (map | |
359 #(.getColName lps (inc %)) | |
360 (range number-of-variables))) | |
361 model (model lps)] | |
362 (LpSolution. | |
363 (.getObjective lps) | |
364 optimal-values | |
365 variable-names | |
366 (zipmap variable-names optimal-values) | |
367 status | |
368 model)))) | |
369 | |
370 #+end_src | |
371 | |
372 Here I've created an object called =LpSolution= which stores the | |
373 important results from a session with =lp_solve=. Of note is the | |
374 =model= function which returns the problem in a form that can be | |
375 solved by other linear programming packages. | |
376 | |
377 *** Solution Status of an LpSolve Object | |
378 | |
379 #+srcname: solve | |
380 #+begin_src clojure :results silent | |
381 (in-ns 'pokemon.lpsolve) | |
382 | |
383 (defn static-integer? | |
384 "does the field represent a static integer constant?" | |
385 [#^java.lang.reflect.Field field] | |
386 (and (java.lang.reflect.Modifier/isStatic (.getModifiers field)) | |
387 (integer? (.get field nil)))) | |
388 | |
389 (defn integer-constants [class] | |
390 (filter static-integer? (.getFields class))) | |
391 | |
392 (defn-memo constant-map | |
393 "Takes a class and creates a map of the static constant integer | |
394 fields with their names. This helps with C wrappers where they have | |
395 just defined a bunch of integer constants instead of enums" | |
396 [class] | |
397 (let [integer-fields (integer-constants class)] | |
398 (into (sorted-map) | |
399 (zipmap (map #(.get % nil) integer-fields) | |
400 (map #(.getName %) integer-fields))))) | |
401 | |
402 (defn solve | |
403 "Solve an instance of LpSolve and return a string representing the | |
404 status of the computation. Will only solve a particular LpSolve | |
405 instance once." | |
406 [#^LpSolve lps] | |
407 ((constant-map LpSolve) | |
408 (.solve lps))) | |
409 | |
410 #+end_src | |
411 | |
412 The =.solve= method of an =LpSolve= object only returns an integer code | |
413 to specify the status of the computation. The =solve= method here | |
414 uses reflection to look up the actual name of the status code and | |
415 returns a more helpful status message that is also resistant to | |
416 changes in the meanings of the code numbers. | |
417 | |
418 *** The Farmer Example in Clojure, Pass 1 | |
419 | |
420 Now we can implement a nicer version of the examples from the | |
421 [[http://lpsolve.sourceforge.net/][=lp\_solve= website]]. The following is a more or less | |
422 line-by-line translation of the Java code from that example. | |
423 | |
424 #+srcname: farmer-example | |
425 #+begin_src clojure :results silent | |
426 (in-ns 'pokemon.lpsolve) | |
427 (defn farmer-example [] | |
428 (linear-program | |
429 (results | |
430 (doto lps | |
431 ;; name the columns | |
432 (.setColName 1 "wheat") | |
433 (.setColName 2 "barley") | |
434 (.setAddRowmode true) | |
435 ;; row 1 : 120x + 210y <= 15000 | |
436 (.addConstraintex 2 | |
437 (double-array [120 210]) | |
438 (int-array [1 2]) | |
439 LpSolve/LE | |
440 15e3) | |
441 ;; row 2 : 110x + 30y <= 4000 | |
442 (.addConstraintex 2 | |
443 (double-array [110 30]) | |
444 (int-array [1 2]) | |
445 LpSolve/LE | |
446 4e3) | |
447 ;; ;; row 3 : x + y <= 75 | |
448 (.addConstraintex 2 | |
449 (double-array [1 1]) | |
450 (int-array [1 2]) | |
451 LpSolve/LE | |
452 75) | |
453 (.setAddRowmode false) | |
454 | |
455 ;; add constraints | |
456 (.setObjFnex 2 | |
457 (double-array [143 60]) | |
458 (int-array [1 2])) | |
459 | |
460 ;; set this as a maximization problem | |
461 (.setMaxim))))) | |
462 | |
463 #+end_src | |
464 | |
465 #+begin_src clojure :results output :exports both | |
466 (clojure.pprint/pprint | |
467 (:solution (pokemon.lpsolve/farmer-example))) | |
468 #+end_src | |
469 | |
470 #+results: | |
471 : {"barley" 53.12499999999999, "wheat" 21.875} | |
472 | |
473 And it works as expected! | |
474 | |
475 *** The Farmer Example in Clojure, Pass 2 | |
476 We don't have to worry about memory management anymore, and the farmer | |
477 example is about half as long as the example from the =LpSolve= | |
478 website, but we can still do better. Solving linear problems is all | |
479 about the constraint matrix $A$ , the objective function $c$, and the | |
480 right-hand-side $b$, plus whatever other options one cares to set for | |
481 the particular instance of =lp_solve=. Why not make a version of | |
482 =linear-program= that takes care of initialization? | |
483 | |
484 | |
485 | |
486 #+srcname: lp-solve | |
487 #+begin_src clojure :results silent | |
488 (in-ns 'pokemon.lpsolve) | |
489 (defn initialize-lpsolve-row-oriented | |
490 "fill in an lpsolve instance using a constraint matrix =A=, the | |
491 objective function =c=, and the right-hand-side =b=" | |
492 [#^LpSolve lps A b c] | |
493 ;; set the name of the last column to _something_ | |
494 ;; this appears to be necessary to ensure proper initialization. | |
495 (.setColName lps (count c) (str "C" (count c))) | |
496 | |
497 ;; This is the recommended way to "fill-in" an lps instance from the | |
498 ;; documentation. First, set row mode, then set the objective | |
499 ;; function, then set each row of the problem, and then turn off row | |
500 ;; mode. | |
501 (.setAddRowmode lps true) | |
502 (.setObjFnex lps (count c) | |
503 (double-array c) | |
504 (int-array (range 1 (inc (count c))))) | |
505 (dorun | |
506 (for [n (range (count A))] | |
507 (let [row (nth A n) | |
508 row-length (int (count row))] | |
509 (.addConstraintex lps | |
510 row-length | |
511 (double-array row) | |
512 (int-array (range 1 (inc row-length))) | |
513 LpSolve/LE | |
514 (double (nth b n)))))) | |
515 (.setAddRowmode lps false) | |
516 lps) | |
517 | |
518 | |
519 (defmacro lp-solve | |
520 "by default:, | |
521 minimize (* c x), subject to (<= (* A x) b), | |
522 using continuous variables. You may set any number of | |
523 other options as in the LpSolve API." | |
524 [A b c & lp-solve-forms] | |
525 ;; assume that A is a vector of vectors | |
526 (concat | |
527 (list 'linear-program | |
528 (list 'initialize-lpsolve-row-oriented 'lps A b c)) | |
529 `~lp-solve-forms)) | |
530 #+end_src | |
531 | |
532 Now, we can use a much more functional approach to solving the | |
533 farmer's problem: | |
534 | |
535 #+srcname: better-farmer | |
536 #+begin_src clojure :results silent | |
537 (in-ns 'pokemon.lpsolve) | |
538 (defn better-farmer-example [] | |
539 (lp-solve [[120 210] | |
540 [110 30] | |
541 [1 1]] | |
542 [15000 | |
543 4000 | |
544 75] | |
545 [143 60] | |
546 (.setColName lps 1 "wheat") | |
547 (.setColName lps 2 "barley") | |
548 (.setMaxim lps) | |
549 (results lps))) | |
550 #+end_src | |
551 | |
552 #+begin_src clojure :exports both :results verbatim | |
553 (vec (:solution (pokemon.lpsolve/better-farmer-example))) | |
554 #+end_src | |
555 | |
556 #+results: | |
557 : [["barley" 53.12499999999999] ["wheat" 21.875]] | |
558 | |
559 Notice that both the inputs to =better-farmer-example= and the results | |
560 are immutable. | |
561 | |
562 * Using LpSolve to find Immortal Types | |
563 ** Converting the Pokemon problem into a linear form | |
564 How can the original question about pok\eacute{}mon types be converted | |
565 into a linear problem? | |
566 | |
567 Pokemon types can be considered to be vectors of numbers representing | |
568 their susceptances to various attacking types, so Water might look | |
569 something like this. | |
570 | |
571 #+begin_src clojure :results scalar :exports both | |
572 (:water (pokemon.types/defense-strengths)) | |
573 #+end_src | |
574 | |
575 #+results: | |
576 : [1 0.5 0.5 2 2 0.5 1 1 1 1 1 1 1 1 1 1 0.5] | |
577 | |
578 Where the numbers represent the susceptibility of Water to the | |
579 attacking types in the following order: | |
580 | |
581 #+begin_src clojure :results output :exports both | |
582 (clojure.pprint/pprint | |
583 (pokemon.types/type-names)) | |
584 #+end_src | |
585 | |
586 #+results: | |
587 #+begin_example | |
588 [:normal | |
589 :fire | |
590 :water | |
591 :electric | |
592 :grass | |
593 :ice | |
594 :fighting | |
595 :poison | |
596 :ground | |
597 :flying | |
598 :psychic | |
599 :bug | |
600 :rock | |
601 :ghost | |
602 :dragon | |
603 :dark | |
604 :steel] | |
605 #+end_example | |
606 | |
607 | |
608 So, for example, Water is /resistant/ (x0.5) against Fire, which is | |
609 the second element in the list. | |
610 | |
611 To combine types, these sorts of vectors are multiplied together | |
612 pair-wise to yield the resulting combination. | |
613 | |
614 Unfortunately, we need some way to add two type vectors together | |
615 instead of multiplying them if we want to solve the problem with | |
616 =lp_solve=. Taking the log of the vector does just the trick. | |
617 | |
618 If we make a matrix with each column being the log (base 2) of the | |
619 susceptance of each type, then finding an immortal type corresponds to | |
620 setting each constraint (the $b$ vector) to -1 (since log_2(1/2) = -1) | |
621 and setting the constraint vector $c$ to all ones, which means that we | |
622 want to find the immortal type which uses the least amount of types. | |
623 | |
624 #+srcname: pokemon-lp | |
625 #+begin_src clojure :results silent | |
626 (in-ns 'pokemon.lpsolve) | |
627 | |
628 (require 'pokemon.types) | |
629 (require 'incanter.core) | |
630 | |
631 (defn log-clamp-matrix [matrix] | |
632 ;; we have to clamp the Infinities to a more reasonable negative | |
633 ;; value because lp_solve does not play well with infinities in its | |
634 ;; constraint matrix. | |
635 (map (fn [row] (map #(if (= Double/NEGATIVE_INFINITY %) -1e3 %) row)) | |
636 (incanter.core/log2 | |
637 (incanter.core/trans | |
638 matrix)))) | |
639 | |
640 ;; constraint matrices | |
641 (defn log-defense-matrix [] | |
642 (log-clamp-matrix | |
643 (doall (map (pokemon.types/defense-strengths) | |
644 (pokemon.types/type-names))))) | |
645 | |
646 (defn log-attack-matrix [] | |
647 (incanter.core/trans (log-defense-matrix))) | |
648 | |
649 ;; target vectors | |
650 (defn all-resistant [] | |
651 (doall (map (constantly -1) (pokemon.types/type-names)))) | |
652 | |
653 (defn all-weak [] | |
654 (doall (map (constantly 1) (pokemon.types/type-names)))) | |
655 | |
656 (defn all-neutral [] | |
657 (doall (map (constantly 0) (pokemon.types/type-names)))) | |
658 | |
659 | |
660 ;; objective functions | |
661 (defn number-of-types [] | |
662 (doall (map (constantly 1) (pokemon.types/type-names)))) | |
663 | |
664 (defn set-constraints | |
665 "sets all the constraints for an lpsolve instance to the given | |
666 constraint. =constraint= here is one of the LpSolve constants such | |
667 as LpSolve/EQ." | |
668 [#^LpSolve lps constraint] | |
669 (dorun (map (fn [index] (.setConstrType lps index constraint)) | |
670 ;; ONE based indexing!!! | |
671 (range 1 (inc (.getNrows lps)))))) | |
672 | |
673 | |
674 (defn set-discrete | |
675 "sets every variable in an lps problem to be a discrete rather than | |
676 continuous variable" | |
677 [#^LpSolve lps] | |
678 (dorun (map (fn [index] (.setInt lps index true)) | |
679 ;; ONE based indexing!!! | |
680 (range 1 (inc (.getNcolumns lps)))))) | |
681 | |
682 (defn set-variable-names | |
683 "sets the variable names of the problem given a vector of names" | |
684 [#^LpSolve lps names] | |
685 (dorun | |
686 (map (fn [[index name]] | |
687 (.setColName lps (inc index) (str name))) | |
688 ;; ONE based indexing!!! | |
689 (indexed names)))) | |
690 | |
691 (defn poke-solve | |
692 ([poke-matrix target objective-function constraint min-num-types] | |
693 ;; must have at least one type | |
694 (let [poke-matrix | |
695 (concat poke-matrix | |
696 [(map (constantly 1) | |
697 (range (count (first poke-matrix))))]) | |
698 target (concat target [min-num-types])] | |
699 (lp-solve poke-matrix target objective-function | |
700 (set-constraints lps constraint) | |
701 ;; must have more than min-num-types | |
702 (.setConstrType lps (count target) LpSolve/GE) | |
703 (set-discrete lps) | |
704 (set-variable-names lps (pokemon.types/type-names)) | |
705 (results lps)))) | |
706 ([poke-matrix target objective-function constraint] | |
707 ;; at least one type | |
708 (poke-solve poke-matrix target objective-function constraint 1))) | |
709 | |
710 (defn solution | |
711 "If the results of an lpsolve operation are feasible, returns the | |
712 results. Otherwise, returns the error." | |
713 [results] | |
714 (if (not (= (:status results) "OPTIMAL")) | |
715 (:status results) | |
716 (:solution results))) | |
717 | |
718 #+end_src | |
719 | |
720 With this, we are finally able to get some results. | |
721 | |
722 ** Results | |
723 #+srcname: results | |
724 #+begin_src clojure :results silent | |
725 (in-ns 'pokemon.lpsolve) | |
726 | |
727 (defn best-defense-type | |
728 "finds a type combination which is resistant to all attacks." | |
729 [] | |
730 (poke-solve | |
731 (log-defense-matrix) (all-resistant) (number-of-types) LpSolve/LE)) | |
732 | |
733 (defn worst-attack-type | |
734 "finds the attack type which is not-very-effective against all pure | |
735 defending types (each single defending type is resistant to this | |
736 attack combination" | |
737 [] | |
738 (poke-solve | |
739 (log-attack-matrix) (all-resistant) (number-of-types) LpSolve/LE)) | |
740 | |
741 (defn worst-defense-type | |
742 "finds a defending type that is weak to all single attacking types." | |
743 [] | |
744 (poke-solve | |
745 (log-defense-matrix) (all-weak) (number-of-types) LpSolve/GE)) | |
746 | |
747 (defn best-attack-type | |
748 "finds an attack type which is super effective against all single | |
749 defending types" | |
750 [] | |
751 (poke-solve | |
752 (log-attack-matrix) (all-weak) (number-of-types) LpSolve/GE)) | |
753 | |
754 (defn solid-defense-type | |
755 "finds a defense type which is either neutral or resistant to all | |
756 single attacking types" | |
757 [] | |
758 (poke-solve | |
759 (log-defense-matrix) (all-neutral) (number-of-types) LpSolve/LE)) | |
760 | |
761 (defn solid-attack-type | |
762 "finds an attack type which is either neutral or super-effective to | |
763 all single attacking types." | |
764 [] | |
765 (poke-solve | |
766 (log-attack-matrix) (all-neutral) (number-of-types) LpSolve/GE)) | |
767 | |
768 (defn weak-defense-type | |
769 "finds a defense type which is either neutral or weak to all single | |
770 attacking types" | |
771 [] | |
772 (poke-solve | |
773 (log-defense-matrix) (all-neutral) (number-of-types) LpSolve/GE)) | |
774 | |
775 (defn neutral-defense-type | |
776 "finds a defense type which is perfectly neutral to all attacking | |
777 types." | |
778 [] | |
779 (poke-solve | |
780 (log-defense-matrix) (all-neutral) (number-of-types) LpSolve/EQ)) | |
781 | |
782 #+end_src | |
783 | |
784 *** Strongest Attack/Defense Combinations | |
785 | |
786 #+begin_src clojure :results output :exports both | |
787 (clojure.pprint/pprint | |
788 (pokemon.lpsolve/solution (pokemon.lpsolve/best-defense-type))) | |
789 #+end_src | |
790 | |
791 #+results: | |
792 #+begin_example | |
793 {":normal" 0.0, | |
794 ":ground" 1.0, | |
795 ":poison" 2.0, | |
796 ":flying" 1.0, | |
797 ":fighting" 0.0, | |
798 ":dragon" 0.0, | |
799 ":fire" 0.0, | |
800 ":dark" 1.0, | |
801 ":ice" 0.0, | |
802 ":steel" 1.0, | |
803 ":ghost" 0.0, | |
804 ":electric" 0.0, | |
805 ":bug" 0.0, | |
806 ":psychic" 0.0, | |
807 ":grass" 0.0, | |
808 ":water" 2.0, | |
809 ":rock" 0.0} | |
810 #+end_example | |
811 | |
812 # #+results-old: | |
813 # : [[":normal" 0.0] [":ground" 1.0] [":poison" 0.0] [":flying" 1.0] [":fighting" 0.0] [":dragon" 1.0] [":fire" 0.0] [":dark" 0.0] [":ice" 0.0] [":steel" 2.0] [":ghost" 1.0] [":electric" 0.0] [":bug" 0.0] [":psychic" 0.0] [":grass" 0.0] [":water" 2.0] [":rock" 0.0]] | |
814 | |
815 | |
816 This is the immortal type combination we've been looking for. By | |
817 combining Steel, Water, Poison, and three types which each have complete | |
818 immunities to various other types, we've created a type that is resistant to | |
819 all attacking types. | |
820 | |
821 #+begin_src clojure :results output :exports both | |
822 (clojure.pprint/pprint | |
823 (pokemon.types/susceptibility | |
824 [:poison :poison :water :water :steel :ground :flying :dark])) | |
825 #+end_src | |
826 | |
827 #+results: | |
828 #+begin_example | |
829 {:water 1/2, | |
830 :psychic 0, | |
831 :dragon 1/2, | |
832 :fire 1/2, | |
833 :ice 1/2, | |
834 :grass 1/2, | |
835 :ghost 1/4, | |
836 :poison 0, | |
837 :flying 1/2, | |
838 :normal 1/2, | |
839 :rock 1/2, | |
840 :electric 0, | |
841 :ground 0, | |
842 :fighting 1/2, | |
843 :dark 1/4, | |
844 :steel 1/8, | |
845 :bug 1/8} | |
846 #+end_example | |
847 | |
848 # #+results-old: | |
849 # : {:water 1/4, :psychic 1/4, :dragon 1/2, :fire 1/2, :ice 1/2, :grass 1/2, :ghost 1/2, :poison 0, :flying 1/4, :normal 0, :rock 1/4, :electric 0, :ground 0, :fighting 0, :dark 1/2, :steel 1/16, :bug 1/16} | |
850 | |
851 | |
852 Cool! | |
853 | |
854 #+begin_src clojure :results output :exports both | |
855 (clojure.pprint/pprint | |
856 (pokemon.lpsolve/solution (pokemon.lpsolve/solid-defense-type))) | |
857 #+end_src | |
858 | |
859 #+results: | |
860 #+begin_example | |
861 {":normal" 0.0, | |
862 ":ground" 0.0, | |
863 ":poison" 0.0, | |
864 ":flying" 0.0, | |
865 ":fighting" 0.0, | |
866 ":dragon" 0.0, | |
867 ":fire" 0.0, | |
868 ":dark" 1.0, | |
869 ":ice" 0.0, | |
870 ":steel" 0.0, | |
871 ":ghost" 1.0, | |
872 ":electric" 0.0, | |
873 ":bug" 0.0, | |
874 ":psychic" 0.0, | |
875 ":grass" 0.0, | |
876 ":water" 0.0, | |
877 ":rock" 0.0} | |
878 #+end_example | |
879 | |
880 Dark and Ghost are the best dual-type combo, and are resistant or | |
881 neutral to all types. | |
882 | |
883 #+begin_src clojure :results output :exports both | |
884 (clojure.pprint/pprint | |
885 (pokemon.types/old-school | |
886 (pokemon.lpsolve/solution (pokemon.lpsolve/solid-defense-type)))) | |
887 #+end_src | |
888 | |
889 #+results: | |
890 #+begin_example | |
891 {":normal" 0.0, | |
892 ":ground" 0.0, | |
893 ":poison" 0.0, | |
894 ":flying" 0.0, | |
895 ":fighting" 0.0, | |
896 ":dragon" 0.0, | |
897 ":fire" 0.0, | |
898 ":ice" 0.0, | |
899 ":ghost" 1.0, | |
900 ":electric" 0.0, | |
901 ":bug" 0.0, | |
902 ":psychic" 1.0, | |
903 ":grass" 0.0, | |
904 ":water" 0.0, | |
905 ":rock" 0.0} | |
906 #+end_example | |
907 | |
908 Ghost and Psychic are a powerful dual type combo in the original games, | |
909 due to a glitch which made Psychic immune to Ghost type attacks, even | |
910 though the game claims that Ghost is strong to Psychic. | |
911 | |
912 #+begin_src clojure :results verbatim :exports both | |
913 (pokemon.lpsolve/solution (pokemon.lpsolve/best-attack-type)) | |
914 #+end_src | |
915 | |
916 #+results: | |
917 : INFEASIBLE | |
918 | |
919 #+begin_src clojure :results verbatim :exports both | |
920 (pokemon.lpsolve/solution (pokemon.lpsolve/solid-attack-type)) | |
921 #+end_src | |
922 | |
923 #+results: | |
924 : INFEASIBLE | |
925 | |
926 | |
927 #+begin_src clojure :results verbatim :exports both | |
928 (pokemon.types/old-school | |
929 (pokemon.lpsolve/solution (pokemon.lpsolve/best-attack-type))) | |
930 #+end_src | |
931 | |
932 #+results: | |
933 : INFEASIBLE | |
934 | |
935 | |
936 #+begin_src clojure :results output :exports both | |
937 (clojure.pprint/pprint | |
938 (pokemon.types/old-school | |
939 (pokemon.lpsolve/solution (pokemon.lpsolve/solid-attack-type)))) | |
940 #+end_src | |
941 | |
942 #+results: | |
943 #+begin_example | |
944 {":normal" 0.0, | |
945 ":ground" 0.0, | |
946 ":poison" 0.0, | |
947 ":flying" 0.0, | |
948 ":fighting" 0.0, | |
949 ":dragon" 1.0, | |
950 ":fire" 0.0, | |
951 ":ice" 0.0, | |
952 ":ghost" 0.0, | |
953 ":electric" 0.0, | |
954 ":bug" 0.0, | |
955 ":psychic" 0.0, | |
956 ":grass" 0.0, | |
957 ":water" 0.0, | |
958 ":rock" 0.0} | |
959 #+end_example | |
960 | |
961 The best attacking type combination is strangely Dragon from the | |
962 original games. It is neutral against all the original types except | |
963 for Dragon, which it is strong against. There is no way to make an | |
964 attacking type that is strong against every type, or even one that is | |
965 strong or neutral against every type, in the new games. | |
966 | |
967 | |
968 *** Weakest Attack/Defense Combinations | |
969 | |
970 #+begin_src clojure :results output :exports both | |
971 (clojure.pprint/pprint | |
972 (pokemon.types/old-school | |
973 (pokemon.lpsolve/solution (pokemon.lpsolve/worst-attack-type)))) | |
974 #+end_src | |
975 | |
976 #+results: | |
977 #+begin_example | |
978 {":normal" 5.0, | |
979 ":ground" 0.0, | |
980 ":poison" 0.0, | |
981 ":flying" 0.0, | |
982 ":fighting" 0.0, | |
983 ":dragon" 0.0, | |
984 ":fire" 1.0, | |
985 ":ice" 2.0, | |
986 ":ghost" 1.0, | |
987 ":electric" 1.0, | |
988 ":bug" 1.0, | |
989 ":psychic" 0.0, | |
990 ":grass" 3.0, | |
991 ":water" 2.0, | |
992 ":rock" 0.0} | |
993 #+end_example | |
994 | |
995 # #+results-old: | |
996 # : [[":normal" 5.0] [":ground" 1.0] [":poison" 0.0] [":flying" 0.0] [":fighting" 2.0] [":dragon" 0.0] [":fire" 0.0] [":ice" 4.0] [":ghost" 1.0] [":electric" 4.0] [":bug" 0.0] [":psychic" 0.0] [":grass" 0.0] [":water" 1.0] [":rock" 1.0]] | |
997 | |
998 #+begin_src clojure :results output :exports both | |
999 (clojure.pprint/pprint | |
1000 (pokemon.lpsolve/solution (pokemon.lpsolve/worst-attack-type))) | |
1001 #+end_src | |
1002 | |
1003 #+results: | |
1004 #+begin_example | |
1005 {":normal" 4.0, | |
1006 ":ground" 1.0, | |
1007 ":poison" 1.0, | |
1008 ":flying" 0.0, | |
1009 ":fighting" 1.0, | |
1010 ":dragon" 0.0, | |
1011 ":fire" 0.0, | |
1012 ":dark" 0.0, | |
1013 ":ice" 4.0, | |
1014 ":steel" 0.0, | |
1015 ":ghost" 1.0, | |
1016 ":electric" 3.0, | |
1017 ":bug" 0.0, | |
1018 ":psychic" 1.0, | |
1019 ":grass" 1.0, | |
1020 ":water" 1.0, | |
1021 ":rock" 2.0} | |
1022 #+end_example | |
1023 | |
1024 # #+results-old: | |
1025 # : [[":normal" 4.0] [":ground" 1.0] [":poison" 1.0] [":flying" 0.0] [":fighting" 2.0] [":dragon" 0.0] [":fire" 0.0] [":dark" 0.0] [":ice" 5.0] [":steel" 0.0] [":ghost" 1.0] [":electric" 5.0] [":bug" 0.0] [":psychic" 1.0] [":grass" 0.0] [":water" 1.0] [":rock" 2.0]] | |
1026 | |
1027 | |
1028 This is an extremely interesting type combination, in that it uses | |
1029 quite a few types. | |
1030 | |
1031 #+begin_src clojure :results verbatim :exports both | |
1032 (reduce + (vals (:solution (pokemon.lpsolve/worst-attack-type)))) | |
1033 #+end_src | |
1034 | |
1035 #+results: | |
1036 : 20.0 | |
1037 | |
1038 20 types is the /minimum/ number of types before the attacking | |
1039 combination is not-very-effective or worse against all defending | |
1040 types. This would probably have been impossible to discover using | |
1041 best-first search, since it involves such an intricate type | |
1042 combination. | |
1043 | |
1044 It's so interesting that it takes 20 types to make an attack type that | |
1045 is weak to all types that the combination merits further investigation. | |
1046 | |
1047 Unfortunately, all of the tools that we've written so far are focused | |
1048 on defense type combinations. However, it is possible to make every | |
1049 tool attack-oriented via a simple macro. | |
1050 | |
1051 #+srcname: attack-oriented | |
1052 #+begin_src clojure :results silent | |
1053 (in-ns 'pokemon.lpsolve) | |
1054 | |
1055 (defmacro attack-mode [& forms] | |
1056 `(let [attack-strengths# pokemon.types/attack-strengths | |
1057 defense-strengths# pokemon.types/defense-strengths] | |
1058 (binding [pokemon.types/attack-strengths | |
1059 defense-strengths# | |
1060 pokemon.types/defense-strengths | |
1061 attack-strengths#] | |
1062 ~@forms))) | |
1063 #+end_src | |
1064 | |
1065 Now all the tools from =pokemon.types= will work for attack | |
1066 combinations. | |
1067 | |
1068 #+begin_src clojure :results output :exports both | |
1069 (clojure.pprint/pprint | |
1070 (pokemon.types/susceptibility [:water])) | |
1071 #+end_src | |
1072 | |
1073 #+results: | |
1074 #+begin_example | |
1075 {:water 1/2, | |
1076 :psychic 1, | |
1077 :dragon 1, | |
1078 :fire 1/2, | |
1079 :ice 1/2, | |
1080 :grass 2, | |
1081 :ghost 1, | |
1082 :poison 1, | |
1083 :flying 1, | |
1084 :normal 1, | |
1085 :rock 1, | |
1086 :electric 2, | |
1087 :ground 1, | |
1088 :fighting 1, | |
1089 :dark 1, | |
1090 :steel 1/2, | |
1091 :bug 1} | |
1092 #+end_example | |
1093 | |
1094 | |
1095 #+begin_src clojure :results output :exports both | |
1096 (clojure.pprint/pprint | |
1097 (pokemon.lpsolve/attack-mode | |
1098 (pokemon.types/susceptibility [:water]))) | |
1099 #+end_src | |
1100 | |
1101 #+results: | |
1102 #+begin_example | |
1103 {:water 1/2, | |
1104 :psychic 1, | |
1105 :dragon 1/2, | |
1106 :fire 2, | |
1107 :ice 1, | |
1108 :grass 1/2, | |
1109 :ghost 1, | |
1110 :poison 1, | |
1111 :flying 1, | |
1112 :normal 1, | |
1113 :rock 2, | |
1114 :electric 1, | |
1115 :ground 2, | |
1116 :fighting 1, | |
1117 :dark 1, | |
1118 :steel 1, | |
1119 :bug 1} | |
1120 #+end_example | |
1121 | |
1122 Now =pokemon.types/susceptibility= reports the /attack-type/ | |
1123 combination's effectiveness against other types. | |
1124 | |
1125 The 20 type combo achieves its goal in a very clever way. | |
1126 | |
1127 First, it weakens its effectiveness to other types at the expense of | |
1128 making it very strong against flying. | |
1129 | |
1130 #+begin_src clojure :results output :exports both | |
1131 (clojure.pprint/pprint | |
1132 (pokemon.lpsolve/attack-mode | |
1133 (pokemon.types/susceptibility | |
1134 [:normal :normal :normal :normal | |
1135 :ice :ice :ice :ice | |
1136 :electric :electric :electric | |
1137 :rock :rock]))) | |
1138 #+end_src | |
1139 | |
1140 #+results: | |
1141 #+begin_example | |
1142 {:water 1/2, | |
1143 :psychic 1, | |
1144 :dragon 2, | |
1145 :fire 1/4, | |
1146 :ice 1/4, | |
1147 :grass 2, | |
1148 :ghost 0, | |
1149 :poison 1, | |
1150 :flying 512, | |
1151 :normal 1, | |
1152 :rock 1/16, | |
1153 :electric 1/8, | |
1154 :ground 0, | |
1155 :fighting 1/4, | |
1156 :dark 1, | |
1157 :steel 1/1024, | |
1158 :bug 4} | |
1159 #+end_example | |
1160 | |
1161 Then, it removes it's strengths against Flying, Normal, and Fighting | |
1162 by adding Ghost and Ground. | |
1163 | |
1164 #+begin_src clojure :results output :exports both | |
1165 (clojure.pprint/pprint | |
1166 (pokemon.lpsolve/attack-mode | |
1167 (pokemon.types/susceptibility | |
1168 [:normal :normal :normal :normal | |
1169 :ice :ice :ice :ice | |
1170 :electric :electric :electric | |
1171 :rock :rock | |
1172 ;; Spot resistances | |
1173 :ghost :ground]))) | |
1174 #+end_src | |
1175 | |
1176 #+results: | |
1177 #+begin_example | |
1178 {:water 1/2, | |
1179 :psychic 2, | |
1180 :dragon 2, | |
1181 :fire 1/2, | |
1182 :ice 1/4, | |
1183 :grass 1, | |
1184 :ghost 0, | |
1185 :poison 2, | |
1186 :flying 0, | |
1187 :normal 0, | |
1188 :rock 1/8, | |
1189 :electric 1/4, | |
1190 :ground 0, | |
1191 :fighting 1/4, | |
1192 :dark 1/2, | |
1193 :steel 1/1024, | |
1194 :bug 2} | |
1195 #+end_example | |
1196 | |
1197 Adding the pair Psychic and Fighting takes care of its strength | |
1198 against Psychic and makes it ineffective against Dark, which is immune | |
1199 to Psychic. | |
1200 | |
1201 Adding the pair Grass and Poison makes takes care of its strength | |
1202 against poison and makes it ineffective against Steel, which is immune | |
1203 to poison. | |
1204 | |
1205 #+begin_src clojure :results output :exports both | |
1206 (clojure.pprint/pprint | |
1207 (pokemon.lpsolve/attack-mode | |
1208 (pokemon.types/susceptibility | |
1209 [;; setup | |
1210 :normal :normal :normal :normal | |
1211 :ice :ice :ice :ice | |
1212 :electric :electric :electric | |
1213 :rock :rock | |
1214 ;; Spot resistances | |
1215 :ghost :ground | |
1216 ;; Pair resistances | |
1217 :psychic :fighting | |
1218 :grass :poison]))) | |
1219 #+end_src | |
1220 | |
1221 #+results: | |
1222 #+begin_example | |
1223 {:water 1, | |
1224 :psychic 1/2, | |
1225 :dragon 1, | |
1226 :fire 1/4, | |
1227 :ice 1/2, | |
1228 :grass 1, | |
1229 :ghost 0, | |
1230 :poison 1/2, | |
1231 :flying 0, | |
1232 :normal 0, | |
1233 :rock 1/4, | |
1234 :electric 1/4, | |
1235 :ground 0, | |
1236 :fighting 1/2, | |
1237 :dark 0, | |
1238 :steel 0, | |
1239 :bug 1/2} | |
1240 #+end_example | |
1241 | |
1242 Can you see the final step? | |
1243 | |
1244 It's adding the Water type, which is weak against Water and Dragon and | |
1245 strong against Rock and Fire. | |
1246 | |
1247 #+begin_src clojure :results output :exports both | |
1248 (clojure.pprint/pprint | |
1249 (pokemon.lpsolve/attack-mode | |
1250 (pokemon.types/susceptibility | |
1251 [;; setup | |
1252 :normal :normal :normal :normal | |
1253 :ice :ice :ice :ice | |
1254 :electric :electric :electric | |
1255 :rock :rock | |
1256 ;; Spot resistances | |
1257 :ghost :ground | |
1258 ;; Pair resistances | |
1259 :psychic :fighting | |
1260 :grass :poison | |
1261 ;; completion | |
1262 :water]))) | |
1263 #+end_src | |
1264 | |
1265 #+results: | |
1266 #+begin_example | |
1267 {:water 1/2, | |
1268 :psychic 1/2, | |
1269 :dragon 1/2, | |
1270 :fire 1/2, | |
1271 :ice 1/2, | |
1272 :grass 1/2, | |
1273 :ghost 0, | |
1274 :poison 1/2, | |
1275 :flying 0, | |
1276 :normal 0, | |
1277 :rock 1/2, | |
1278 :electric 1/4, | |
1279 :ground 0, | |
1280 :fighting 1/2, | |
1281 :dark 0, | |
1282 :steel 0, | |
1283 :bug 1/2} | |
1284 #+end_example | |
1285 | |
1286 Which makes a particularly beautiful combination which is ineffective | |
1287 against all defending types. | |
1288 | |
1289 | |
1290 # #+begin_src clojure :results scalar :exports both | |
1291 # (with-out-str (clojure.contrib.pprint/pprint (seq (attack-mode (pokemon.types/susceptibility [:normal :normal :normal :normal :ice :ice :ice :ice :electric :electric :electric :rock :rock :ground :ghost :psychic :fighting :grass :poison]))))) | |
1292 # #+end_src | |
1293 | |
1294 # #+results: | |
1295 # | [:water 1] | [:psychic 1/2] | [:dragon 1] | [:fire 1/4] | [:ice 1/2] | [:grass 1] | [:ghost 0] | [:poison 1/2] | [:flying 0] | [:normal 0] | [:rock 1/4] | [:electric 1/4] | [:ground 0] | [:fighting 1/2] | [:dark 0] | [:steel 0] | [:bug 1/2] | | |
1296 | |
1297 | |
1298 Is there anything else that's interesting? | |
1299 | |
1300 #+begin_src clojure :exports both | |
1301 (pokemon.lpsolve/solution (pokemon.lpsolve/worst-defense-type)) | |
1302 #+end_src | |
1303 | |
1304 #+results: | |
1305 : INFEASIBLE | |
1306 | |
1307 #+begin_src clojure :exports both | |
1308 (pokemon.types/old-school | |
1309 (pokemon.lpsolve/solution (pokemon.lpsolve/worst-defense-type))) | |
1310 #+end_src | |
1311 | |
1312 #+results: | |
1313 : INFEASIBLE | |
1314 | |
1315 #+begin_src clojure :exports both | |
1316 (pokemon.lpsolve/solution (pokemon.lpsolve/weak-defense-type)) | |
1317 #+end_src | |
1318 | |
1319 #+results: | |
1320 : INFEASIBLE | |
1321 | |
1322 #+begin_src clojure :exports both | |
1323 (pokemon.types/old-school | |
1324 (pokemon.lpsolve/solution (pokemon.lpsolve/weak-defense-type))) | |
1325 #+end_src | |
1326 | |
1327 #+results: | |
1328 : INFEASIBLE | |
1329 | |
1330 #+begin_src clojure :exports both | |
1331 (pokemon.lpsolve/solution (pokemon.lpsolve/neutral-defense-type)) | |
1332 #+end_src | |
1333 | |
1334 #+results: | |
1335 : INFEASIBLE | |
1336 | |
1337 #+begin_src clojure :exports both | |
1338 (pokemon.types/old-school | |
1339 (pokemon.lpsolve/solution (pokemon.lpsolve/neutral-defense-type))) | |
1340 #+end_src | |
1341 | |
1342 #+results: | |
1343 : INFEASIBLE | |
1344 | |
1345 There is no way to produce a defense-type that is weak to all types. | |
1346 This is probably because there are many types that are completely | |
1347 immune to some types, such as Flying, which is immune to Ground. A | |
1348 perfectly weak type could not use any of these types. | |
1349 | |
1350 * Summary | |
1351 | |
1352 Overall, the pok\eacute{}mon type system is slanted more towards defense | |
1353 rather than offense. While it is possible to create superior | |
1354 defensive types and exceptionally weak attack types, it is not possible to | |
1355 create exceptionally weak defensive types or very powerful attack | |
1356 types. | |
1357 | |
1358 Using the =lp_solve= library was more complicated than the best-first | |
1359 search, but yielded results quickly and efficiently. Expressing the | |
1360 problem in a linear form does have its drawbacks, however --- it's | |
1361 hard to ask questions such as "what is the best 3-type defensive combo | |
1362 in terms of susceptibility?", since susceptibility is not a linear | |
1363 function of a combo's types. It is also hard to get all the solutions | |
1364 to a particular problem, such as all the pokemon type combinations of | |
1365 length 8 which are immortal defense types. | |
1366 | |
1367 | |
1368 * COMMENT main-program | |
1369 #+begin_src clojure :tangle ../src/pokemon/lpsolve.clj :noweb yes :exports none | |
1370 <<intro>> | |
1371 <<body>> | |
1372 <<declares>> | |
1373 <<memory-management>> | |
1374 <<get-results>> | |
1375 <<solve>> | |
1376 <<farmer-example>> | |
1377 <<lp-solve>> | |
1378 <<better-farmer>> | |
1379 <<pokemon-lp>> | |
1380 <<results>> | |
1381 <<attack-oriented>> | |
1382 #+end_src | |
1383 | |
1384 | |
1385 * COMMENT Stuff to do. | |
1386 ** TODO fix namespaces to not use rlm.light-base | |
1387 |