Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Solver of Squares: Playing Games with Clojure

Solver of Squares: Playing Games with Clojure

This is a short presentation (~15 minutes) given to a local functional programming user's group, HuntFunc.

Chad Taylor

January 06, 2015
Tweet

More Decks by Chad Taylor

Other Decks in Programming

Transcript

  1. @tessellator A* Search • A tree-search algorithm • Uses a

    heuristic to guide search • Can guarantee shortest-path if the heuristic meets certain requirements
  2. @tessellator ;; (in-ns ‘solver-of-squares.search) (defn a* ([initial-state succ goal? cost?

    estimate? max-cost] (letfn [(wrap-costs [state] (let [c (cost? state), e (estimate? state)] (vary-meta state assoc :cost c :estimate e :total (+ c e)))) (apply-conj! [A xs] (if (empty? xs) A (recur (h/insert! (comp :total meta) A (first xs)) (rest xs))))] (let [cost (comp :cost meta) total (comp :total meta)] ((fn [queue seen] (if-let [state (nth queue 0)] (cond (goal? state) state (> (cost state) max-cost) nil (contains? seen state) (recur (h/pop! total queue) seen) :else (recur (->> (succ state) (map wrap-costs) (apply-conj! (h/pop! total queue))) (conj seen state))))) (transient [(wrap-costs initial-state)]) #{})))))
  3. @tessellator ;; (in-ns ‘solver-of-squares.search) (defn a* ([initial-state succ goal? cost?

    estimate? max-cost] (letfn [(wrap-costs [state] (let [c (cost? state), e (estimate? state)] (vary-meta state assoc :cost c :estimate e :total (+ c e)))) (apply-conj! [A xs] (if (empty? xs) A (recur (h/insert! (comp :total meta) A (first xs)) (rest xs))))] (let [cost (comp :cost meta) total (comp :total meta)] ((fn [queue seen] (if-let [state (nth queue 0)] (cond (goal? state) state (> (cost state) max-cost) nil (contains? seen state) (recur (h/pop! total queue) seen) :else (recur (->> (succ state) (map wrap-costs) (apply-conj! (h/pop! total queue))) (conj seen state))))) (transient [(wrap-costs initial-state)]) #{})))))
  4. @tessellator (fn [queue seen] (if-let [state (nth queue 0)] (cond

    (goal? state) state (> (cost state) max-cost) nil (contains? seen state) (recur (h/pop! total queue) seen) :else (recur (->> (succ state) (map wrap-costs) (apply-conj! (h/pop! total queue))) (conj seen state)))))
  5. @tessellator ;; (in-ns ‘solver-of-squares.search) (defn manhattan-distance [current target] (->> (map

    (comp abs -) target current) (reduce +))) ;; (in-ns ‘solver-of-squares.core) (defn estimate-remaining-cost [goal state] (reduce + (map #(manhattan-distance (goal %) (-> state % :position)) (keys goal))))
  6. @tessellator A good start, but not admissible because it sometimes

    over-estimates …so we are not guaranteed an optimal (shortest-path) solution
  7. @tessellator “If a tree falls in the woods, does it

    make a sound? If a pure function mutates some local data in order to produce an immutable return value, is that ok?” - Rich Hickey clojure.org/transients
  8. @tessellator Transients allow to manipulate a data structure in-place Okay

    under two conditions: 1. You control the “variable” absolutely while transient 2. It is made persistent before made generally available
  9. @tessellator ;; (in-ns ‘solver-of-squares.heap) (defn- sift-up! [f A i] (let

    [idx (dec i) pidx (dec (int (/ i 2)))] (if (zero? idx) A (if (< (f (nth A idx)) (f (nth A pidx))) (recur f (swap! A idx pidx) (inc pidx)) A)))) (defn insert! [f A v] (let [c (count A)] (sift-up! f (conj! A v) (inc c))))
  10. @tessellator ;; (in-ns ‘solver-of-squares.levels) (def level11 {:goal {:red [1 2]

    :blue [2 2] :navy [3 2]} :blocks {:red {:position [0 4] :direction :right} :navy {:position [2 0] :direction :up} :blue {:position [4 4] :direction :left}} :arrows {[2 4] :down}}) Defining a level: