(ns pokemon.types (:use clojure.set) ;; (:use clojure.contrib.combinatorics) (:use clojure.math.combinatorics) (:use clojure.math.numeric-tower) ;; (:use clojure.contrib.def) (:use rlm.rlm-commands) (:require rlm.map-utils)) (let [pokemon-table-gen-one (quote (("normal" 1 1 1 1 1 1 1 1 1 1 1 1 0.5 0 1) ("fire" 1 0.5 0.5 1 2 2 1 1 1 1 1 2 0.5 1 0.5) ("water" 1 2 0.5 1 0.5 1 1 1 2 1 1 1 2 1 0.5) ("electric" 1 1 2 0.5 0.5 1 1 1 0 2 1 1 1 1 0.5) ("grass" 1 0.5 2 1 0.5 1 1 0.5 2 0.5 1 0.5 2 1 0.5) ("ice" 1 1 0.5 1 2 0.5 1 1 2 2 1 1 1 1 2) ("fighting" 2 1 1 1 1 2 1 0.5 1 0.5 0.5 0.5 2 0 1) ("poison" 1 1 1 1 2 1 1 0.5 0.5 1 1 2 0.5 0.5 1) ("ground" 1 2 1 2 0.5 1 1 2 1 0 1 0.5 2 1 1) ("flying" 1 1 1 0.5 2 1 2 1 1 1 1 2 0.5 1 1) ("psychic" 1 1 1 1 1 1 2 2 1 1 0.5 1 1 1 1) ("bug" 1 0.5 1 1 2 1 0.5 2 1 0.5 2 1 1 0 1) ("rock" 1 2 1 1 1 2 0.5 1 0.5 2 1 2 1 1 1) ("ghost" 0 1 1 1 1 1 1 1 1 1 0 1 1 2 1) ("dragon" 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2))) pokemon-table-gen-two (quote (("normal" 1 1 1 1 1 1 1 1 1 1 1 1 0.5 0 1 1 0.5) ("fire" 1 0.5 0.5 1 2 2 1 1 1 1 1 2 0.5 1 0.5 1 2) ("water" 1 2 0.5 1 0.5 1 1 1 2 1 1 1 2 1 0.5 1 1) ("electric" 1 1 2 0.5 0.5 1 1 1 0 2 1 1 1 1 0.5 1 1) ("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) ("ice" 1 0.5 0.5 1 2 0.5 1 1 2 2 1 1 1 1 2 1 0.5) ("fighting" 2 1 1 1 1 2 1 0.5 1 0.5 0.5 0.5 2 0 1 2 2) ("poison" 1 1 1 1 2 1 1 0.5 0.5 1 1 1 0.5 0.5 1 1 0) ("ground" 1 2 1 2 0.5 1 1 2 1 0 1 0.5 2 1 1 1 2) ("flying" 1 1 1 0.5 2 1 2 1 1 1 1 2 0.5 1 1 1 0.5) ("psychic" 1 1 1 1 1 1 2 2 1 1 0.5 1 1 1 1 0 0.5) ("bug" 1 0.5 1 1 2 1 0.5 0.5 1 0.5 2 1 1 0.5 1 2 0.5) ("rock" 1 2 1 1 1 2 0.5 1 0.5 2 1 2 1 1 1 1 0.5) ("ghost" 0 1 1 1 1 1 1 1 1 1 2 1 1 2 1 0.5 0.5) ("dragon" 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 0.5) ("dark" 1 1 1 1 1 1 0.5 1 1 1 2 1 1 2 1 0.5 0.5) ("steel" 1 0.5 0.5 0.5 1 2 1 1 1 1 1 1 2 1 1 1 0.5)))] (in-ns 'pokemon.types) ;; record type strengths as a vector of vectors ;; the variables pokemon-table-gen-one and pokemon-table-gen-two ;; are replaced with the tables above when this file is tangled. (def ^:dynamic pokemon-gen-one pokemon-table-gen-one) (def ^:dynamic pokemon-gen-two pokemon-table-gen-two) (defn type-names [] (vec (doall (map (comp keyword first) pokemon-gen-two)))) ;; ;; DXH GEN 8 ;; (defn type-names [:normal :fighting :flying :poison :ground :rock :bug ;; :ghost :steel :fire :water :grass :electric :psychic :ice :dragon :dark :fairy]) (defn attack-strengths [] (zipmap (type-names) (map (comp vec rest) pokemon-gen-two))) (defn defense-strengths [] (zipmap (type-names) (map (apply juxt (map (attack-strengths) (type-names))) (range (count (type-names))))))) (in-ns 'pokemon.types) (defn multitypes "All combinations of up to n types" [n] (vec (map vec (reduce concat (map (partial combinations (type-names)) (range 1 (inc n))))))) (defn susceptibility "Hash-map of the susceptibilities of the given type combination to each type of attack" [pkmn-types] (rlm.map-utils/map-vals clojure.core/rationalize (apply hash-map (interleave (type-names) (apply (partial map *) (map (defense-strengths) pkmn-types)))))) (defn susceptance "The cumulative susceptibility of the given type combination" [types] (reduce + (map #(expt % 2) (vals (susceptibility types))))) (in-ns 'pokemon.types) (defn comparatize "Define a comparator which uses the numerical outputs of fn as its criterion. Objects are sorted in increasing numerical order. Objects with the same fn-value are further compared by clojure.core/compare." [fun] (fn [a b] (let [val-a (fun a) val-b (fun b)] (cond ;; if the function cannot differentiate the two values ;; then compare the two values using clojure.core/compare (= val-a val-b) (compare a b) true ;; LOWER values of the function are preferred (compare (- val-a val-b) 0))))) (defn best-first-step [successors [visited unvisited]] (cond (empty? unvisited) nil true (let [best-node (first unvisited) visited* (conj visited best-node) unvisited* (difference (union unvisited (set (successors best-node))) visited*)] (println best-node) [visited* unvisited*]))) (alter-var-root #'best-first-step memoize) ;; memoize partial from core so that for example ;; (= (partial + 1) (partial + 1)) ;; this way, best first search can take advantage of the memoization ;; of best-first step (undef partial) (def partial (memoize clojure.core/partial)) (defn best-first-search "Searches through a network of alternatives, pursuing initially-promising positions first. Comparator defines which positions are more promising, successors produces a list of improved positions from the given position (if any exist), and initial-nodes is a list of starting positions. Returns a lazy sequence of search results [visited-nodes unvisited-nodes], which terminates when there are no remaining unvisited positions." [comparator successors initial-nodes] (let [initial-nodes (apply (partial sorted-set-by comparator) initial-nodes) initial-visited-nodes (sorted-set-by comparator) step (partial best-first-step successors)] (take-while (comp not nil?) (iterate step [initial-visited-nodes initial-nodes])))) (in-ns 'pokemon.types) (def type-compare "compare two type combinations W.R.T. their susceptibilities" (comparatize susceptance)) (defn type-successors "Return the set of types that can be made by appending a single type to the given combination." [type] (if (nil? type) '() (set (map (comp vec sort (partial into type)) (multitypes 1))))) (defn immortal? "A type combo is immortal if it is resistant or invulnerable to every pokemon type. This is because that set of types can just be repeated to achieve as low a susceptance as desired" [type] (every? (partial > 1) (vals (susceptibility type)))) (defn type-successors* "Stop expanding a type if it's immortal, or if it is longer than or equal to limit-size. Also, only return type additions that are strictly better than the initial type." [limit-size type] (if (or (<= limit-size (count type)) (immortal? type)) '() (set (filter #(< 0 (type-compare type %)) (type-successors type))))) (defn pokemon-type-search "Search among type-combos no greater than length n, limited by limit steps of best-first-search." ([n] (pokemon-type-search n Integer/MAX_VALUE)) ([n limit] (first (last (take limit (best-first-search type-compare (partial type-successors* n) (multitypes 1))))))) (def immortals "find all the immortal pokemon types." (comp (partial filter immortal?) pokemon-type-search)) (in-ns 'pokemon.types) (defmacro old-school [& forms] `(binding [pokemon-gen-two pokemon-gen-one] ~@forms)) (in-ns 'pokemon.types) (defn critical-weaknesses [type] (count (filter #(> % 1) (vals (susceptibility type))))) (in-ns 'pokemon.types) (defn type-compare-weak "compare first by total number of critical-weaknesses, then by overall susceptance, favoring weaker types." [type-1 type-2] (let [measure (memoize (juxt critical-weaknesses susceptance))] (if (= (measure type-2) (measure type-1)) (compare type-2 type-1) (compare (measure type-2) (measure type-1))))) (defn resistant? "might as well get rid of types that are resistant to any type" [type] (not (every? #(< 0 %) (vals (susceptibility type))))) (defn type-successors-weak "Generate ways to weaken the given type combination. Discard type combinations that either strengthen the given type combination or that make it stronger" [limit type] (set (if (<= limit (count type)) '() (filter #(< 0 (type-compare-weak type %)) (remove resistant? (type-successors type)))))) (defn pokemon-type-search-weak "Search among type-combos no greater than length n, limited by limit steps of best-first-search. Find the weakest type combination possible in terms of susceptance." ([n] (pokemon-type-search-weak n Integer/MAX_VALUE)) ([n limit] (first (last (take limit (best-first-search type-compare-weak (partial type-successors-weak n) (multitypes 1)))))))