Skip to content

Instantly share code, notes, and snippets.

@angry-gopher
Forked from michiakig/ants.clj
Last active August 29, 2015 14:19
Show Gist options
  • Save angry-gopher/29e697c0fd9440df3a93 to your computer and use it in GitHub Desktop.
Save angry-gopher/29e697c0fd9440df3a93 to your computer and use it in GitHub Desktop.

Revisions

  1. @michiakig michiakig created this gist Jul 19, 2011.
    318 changes: 318 additions & 0 deletions ants.clj
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,318 @@
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Ant sim ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Copyright (c) Rich Hickey. All rights reserved.
    ; The use and distribution terms for this software are covered by the
    ; Common Public License 1.0 (http://opensource.org/licenses/cpl.php)
    ; which can be found in the file CPL.TXT at the root of this distribution.
    ; By using this software in any fashion, you are agreeing to be bound by
    ; the terms of this license.
    ; You must not remove this notice, or any other, from this software.

    ;dimensions of square world
    (def dim 80)
    ;number of ants = nants-sqrt^2
    (def nants-sqrt 7)
    ;number of places with food
    (def food-places 35)
    ;range of amount of food at a place
    (def food-range 100)
    ;scale factor for pheromone drawing
    (def pher-scale 20.0)
    ;scale factor for food drawing
    (def food-scale 30.0)
    ;evaporation rate
    (def evap-rate 0.99)

    (def animation-sleep-ms 100)
    (def ant-sleep-ms 40)
    (def evap-sleep-ms 1000)

    (def running true)

    (defstruct cell :food :pher) ;may also have :ant and :home

    ;world is a 2d vector of refs to cells
    (def world
    (apply vector
    (map (fn [_]
    (apply vector (map (fn [_] (ref (struct cell 0 0)))
    (range dim))))
    (range dim))))

    (defn place [[x y]]
    (-> world (nth x) (nth y)))

    (defstruct ant :dir) ;may also have :food

    (defn create-ant
    "create an ant at the location, returning an ant agent on the location"
    [loc dir]
    (sync nil
    (let [p (place loc)
    a (struct ant dir)]
    (alter p assoc :ant a)
    (agent loc))))

    (def home-off (/ dim 4))
    (def home-range (range home-off (+ nants-sqrt home-off)))

    (defn setup
    "places initial food and ants, returns seq of ant agents"
    []
    (sync nil
    (dotimes [i food-places]
    (let [p (place [(rand-int dim) (rand-int dim)])]
    (alter p assoc :food (rand-int food-range))))
    (doall
    (for [x home-range y home-range]
    (do
    (alter (place [x y])
    assoc :home true)
    (create-ant [x y] (rand-int 8)))))))

    (defn bound
    "returns n wrapped into range 0-b"
    [b n]
    (let [n (rem n b)]
    (if (neg? n)
    (+ n b)
    n)))

    (defn wrand
    "given a vector of slice sizes, returns the index of a slice given a
    random spin of a roulette wheel with compartments proportional to
    slices."
    [slices]
    (let [total (reduce + slices)
    r (rand total)]
    (loop [i 0 sum 0]
    (if (< r (+ (slices i) sum))
    i
    (recur (inc i) (+ (slices i) sum))))))

    ;dirs are 0-7, starting at north and going clockwise
    ;these are the deltas in order to move one step in given dir
    (def dir-delta {0 [0 -1]
    1 [1 -1]
    2 [1 0]
    3 [1 1]
    4 [0 1]
    5 [-1 1]
    6 [-1 0]
    7 [-1 -1]})

    (defn delta-loc
    "returns the location one step in the given dir. Note the world is a torus"
    [[x y] dir]
    (let [[dx dy] (dir-delta (bound 8 dir))]
    [(bound dim (+ x dx)) (bound dim (+ y dy))]))

    ;(defmacro dosync [& body]
    ; `(sync nil ~@body))

    ;ant agent functions
    ;an ant agent tracks the location of an ant, and controls the behavior of
    ;the ant at that location

    (defn turn
    "turns the ant at the location by the given amount"
    [loc amt]
    (dosync
    (let [p (place loc)
    ant (:ant @p)]
    (alter p assoc :ant (assoc ant :dir (bound 8 (+ (:dir ant) amt))))))
    loc)

    (defn move
    "moves the ant in the direction it is heading. Must be called in a
    transaction that has verified the way is clear"
    [loc]
    (let [oldp (place loc)
    ant (:ant @oldp)
    newloc (delta-loc loc (:dir ant))
    p (place newloc)]
    ;move the ant
    (alter p assoc :ant ant)
    (alter oldp dissoc :ant)
    ;leave pheromone trail
    (when-not (:home @oldp)
    (alter oldp assoc :pher (inc (:pher @oldp))))
    newloc))

    (defn take-food [loc]
    "Takes one food from current location. Must be called in a
    transaction that has verified there is food available"
    (let [p (place loc)
    ant (:ant @p)]
    (alter p assoc
    :food (dec (:food @p))
    :ant (assoc ant :food true))
    loc))

    (defn drop-food [loc]
    "Drops food at current location. Must be called in a
    transaction that has verified the ant has food"
    (let [p (place loc)
    ant (:ant @p)]
    (alter p assoc
    :food (inc (:food @p))
    :ant (dissoc ant :food))
    loc))

    (defn rank-by
    "returns a map of xs to their 1-based rank when sorted by keyfn"
    [keyfn xs]
    (let [sorted (sort-by (comp float keyfn) xs)]
    (reduce (fn [ret i] (assoc ret (nth sorted i) (inc i)))
    {} (range (count sorted)))))

    (defn behave
    "the main function for the ant agent"
    [loc]
    (let [p (place loc)
    ant (:ant @p)
    ahead (place (delta-loc loc (:dir ant)))
    ahead-left (place (delta-loc loc (dec (:dir ant))))
    ahead-right (place (delta-loc loc (inc (:dir ant))))
    places [ahead ahead-left ahead-right]]
    (. Thread (sleep ant-sleep-ms))
    (dosync
    (when running
    (send-off *agent* #'behave))
    (if (:food ant)
    ;going home
    (cond
    (:home @p)
    (-> loc drop-food (turn 4))
    (and (:home @ahead) (not (:ant @ahead)))
    (move loc)
    :else
    (let [ranks (merge-with +
    (rank-by (comp #(if (:home %) 1 0) deref) places)
    (rank-by (comp :pher deref) places))]
    (([move #(turn % -1) #(turn % 1)]
    (wrand [(if (:ant @ahead) 0 (ranks ahead))
    (ranks ahead-left) (ranks ahead-right)]))
    loc)))
    ;foraging
    (cond
    (and (pos? (:food @p)) (not (:home @p)))
    (-> loc take-food (turn 4))
    (and (pos? (:food @ahead)) (not (:home @ahead)) (not (:ant @ahead)))
    (move loc)
    :else
    (let [ranks (merge-with +
    (rank-by (comp :food deref) places)
    (rank-by (comp :pher deref) places))]
    (([move #(turn % -1) #(turn % 1)]
    (wrand [(if (:ant @ahead) 0 (ranks ahead))
    (ranks ahead-left) (ranks ahead-right)]))
    loc)))))))

    (defn evaporate
    "causes all the pheromones to evaporate a bit"
    []
    (dorun
    (for [x (range dim) y (range dim)]
    (dosync
    (let [p (place [x y])]
    (alter p assoc :pher (* evap-rate (:pher @p))))))))

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; UI ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    (import
    '(java.awt Color Graphics Dimension)
    '(java.awt.image BufferedImage)
    '(javax.swing JPanel JFrame))

    ;pixels per world cell
    (def scale 5)

    (defn fill-cell [#^Graphics g x y c]
    (doto g
    (.setColor c)
    (.fillRect (* x scale) (* y scale) scale scale)))

    (defn render-ant [ant #^Graphics g x y]
    (let [black (. (new Color 0 0 0 255) (getRGB))
    gray (. (new Color 100 100 100 255) (getRGB))
    red (. (new Color 255 0 0 255) (getRGB))
    [hx hy tx ty] ({0 [2 0 2 4]
    1 [4 0 0 4]
    2 [4 2 0 2]
    3 [4 4 0 0]
    4 [2 4 2 0]
    5 [0 4 4 0]
    6 [0 2 4 2]
    7 [0 0 4 4]}
    (:dir ant))]
    (doto g
    (.setColor (if (:food ant)
    (new Color 255 0 0 255)
    (new Color 0 0 0 255)))
    (.drawLine (+ hx (* x scale)) (+ hy (* y scale))
    (+ tx (* x scale)) (+ ty (* y scale))))))

    (defn render-place [g p x y]
    (when (pos? (:pher p))
    (fill-cell g x y (new Color 0 255 0
    (int (min 255 (* 255 (/ (:pher p) pher-scale)))))))
    (when (pos? (:food p))
    (fill-cell g x y (new Color 255 0 0
    (int (min 255 (* 255 (/ (:food p) food-scale)))))))
    (when (:ant p)
    (render-ant (:ant p) g x y)))

    (defn render [g]
    (let [v (dosync (apply vector (for [x (range dim) y (range dim)]
    @(place [x y]))))
    img (new BufferedImage (* scale dim) (* scale dim)
    (. BufferedImage TYPE_INT_ARGB))
    bg (. img (getGraphics))]
    (doto bg
    (.setColor (. Color white))
    (.fillRect 0 0 (. img (getWidth)) (. img (getHeight))))
    (dorun
    (for [x (range dim) y (range dim)]
    (render-place bg (v (+ (* x dim) y)) x y)))
    (doto bg
    (.setColor (. Color blue))
    (.drawRect (* scale home-off) (* scale home-off)
    (* scale nants-sqrt) (* scale nants-sqrt)))
    (. g (drawImage img 0 0 nil))
    (. bg (dispose))))

    (def panel (doto (proxy [JPanel] []
    (paint [g] (render g)))
    (.setPreferredSize (new Dimension
    (* scale dim)
    (* scale dim)))))

    (def frame (doto (new JFrame) (.add panel) .pack .show))

    (def animator (agent nil))

    (defn animation [x]
    (when running
    (send-off *agent* #'animation))
    (. panel (repaint))
    (. Thread (sleep animation-sleep-ms))
    nil)

    (def evaporator (agent nil))

    (defn evaporation [x]
    (when running
    (send-off *agent* #'evaporation))
    (evaporate)
    (. Thread (sleep evap-sleep-ms))
    nil)

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; use ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;; (comment
    ;demo
    ;; (load-file "/Users/rich/dev/clojure/ants.clj")
    (def ants (setup))
    (send-off animator animation)
    (dorun (map #(send-off % behave) ants))
    (send-off evaporator evaporation)

    ;; )