├── .dockerignore ├── .gitignore ├── Dockerfile ├── Makefile ├── README.org ├── deps.edn ├── src └── demo │ ├── ant.clj │ ├── core.clj │ ├── domain.clj │ ├── ui │ ├── ant.clj │ ├── core.clj │ └── world.clj │ ├── util.clj │ └── world.clj ├── test └── demo │ └── ant_test.clj └── tests.edn /.dockerignore: -------------------------------------------------------------------------------- 1 | .cpcache/ 2 | .git 3 | target/ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/pom.xml 2 | **/pom.xml.asc 3 | **/*jar 4 | **//lib/ 5 | **/classes/ 6 | **/target/ 7 | **//checkouts/ 8 | **/.lein-deps-sum 9 | **/.lein-repl-history 10 | **/.lein-plugins/ 11 | **/.lein-failures 12 | **/.nrepl-port 13 | .DS_Store 14 | .cpcache/ 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM clojure:tools-deps-slim-buster as builder 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY deps.edn . 6 | # Force download dependencies. 7 | RUN clojure -P -A:uberjar -Stree 8 | 9 | COPY Makefile . 10 | RUN make check 11 | 12 | COPY src/ /usr/src/app/src 13 | RUN make build 14 | 15 | FROM openjdk:11-slim-buster 16 | 17 | # Runtime dependencies to run Swing apps. 18 | RUN apt-get update && apt-get install -y \ 19 | libfreetype6-dev \ 20 | libxext-dev \ 21 | libxrender-dev \ 22 | libxtst-dev 23 | 24 | COPY --from=builder /usr/src/app/target/app.jar /usr/src/app/app.jar 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REPO := ilmotta/ants-simulation 2 | TAG := 1.0.0 3 | CONTAINER_NAME := ants-simulation 4 | DOCKER_JVM_OPTS := -XX:+UseContainerSupport -XX:MaxRAMPercentage=85 -XX:+UnlockExperimentalVMOptions 5 | JVM_OPTS := -XX:MaxRAMPercentage=85 -XX:+UnlockExperimentalVMOptions 6 | 7 | .PHONY: all test 8 | 9 | all: run 10 | 11 | check: 12 | clojure -M --eval :ok 13 | 14 | run: 15 | clojure -M:run 16 | 17 | run/release: 18 | java $(JVM_OPTS) -jar target/app.jar 19 | 20 | build: 21 | clojure -X:uberjar 22 | 23 | test: 24 | clojure -M:test 25 | 26 | clean: 27 | rm -rf target/ .cpcache/ 28 | 29 | docker/build: 30 | @docker build --tag $(REPO):$(TAG) --rm . 31 | 32 | # Run to add IP to access control list. 33 | # $ xhost +local:docker 34 | docker/run/release: 35 | @docker run -it --rm \ 36 | --name $(CONTAINER_NAME) \ 37 | --network=host \ 38 | -e DISPLAY=$(DISPLAY) \ 39 | -v /tmp/.X11-unix:/tmp/.X11-unix \ 40 | $(REPO):$(TAG) java $(JVM_OPTS) -jar /usr/src/app/app.jar 41 | 42 | docker/shell: 43 | @docker run -it --rm \ 44 | --name $(CONTAINER_NAME) \ 45 | $(REPO):$(TAG) /bin/sh 46 | 47 | docker/clean: 48 | @docker rm -f $(CONTAINER_NAME); \ 49 | docker rmi -f $(REPO):$(TAG) 50 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * Ants Simulation in Clojure 2 | 3 | ** Motivation 4 | 5 | Learn about Clojure's concurrency strategies and refactor the original code from 6 | Rich's [[https://www.youtube.com/watch?v=dGVqrGmwOAw][talk about concurrency in Clojure]]. 7 | 8 | ** Running the simulation 9 | 10 | This project uses the [[https://clojure.org/reference/deps_and_cli][clojure CLI]] and =make= tools to build and execute the 11 | application. It has been tested with OpenJDK 11. Simply call =make= to run it 12 | and =make test= to run all the unit tests. 13 | 14 | #+caption: Fig. 1: Application demo 15 | [[https://cloud.githubusercontent.com/assets/46027/22576690/23b64650-e9a4-11e6-9bfd-529a9ff7f848.gif]] 16 | 17 | You can also run it using Docker (tested with v20.10.x), but I've only tested it 18 | in a GNU/Linux distribution, hence macOS and Windows are not supported. 19 | 20 | #+begin_example sh 21 | make docker/build 22 | make docker/run/release 23 | #+end_example 24 | 25 | You can remove the container and image with: 26 | 27 | #+begin_example sh 28 | make docker/clean 29 | #+end_example 30 | 31 | ** Resources 32 | 33 | - [[https://www.youtube.com/watch?v=dGVqrGmwOAw][Rich's talk]] 34 | - [[https://github.com/dimhold/clojure-concurrency-rich-hickey/blob/master/ClojureConcurrencyTalk.pdf?raw=true][Talk's slides]] 35 | - [[https://github.com/juliangamble/clojure-ants-simulation][Original source (not really from Rich)]] 36 | 37 | ** Original Notice 38 | 39 | Ants is based on the Clojure Ants Simulation by Rich Hickey. 40 | 41 | Copyright (c) Rich Hickey. All rights reserved. The use and distribution terms 42 | for this software are covered by the Common Public License 1.0 ([[http://opensource.org/licenses/cpl1.0.php][cpl]]) which can 43 | be found in the file cpl.txt at the root of this distribution. By using this 44 | software in any fashion, you are agreeing to be bound by the terms of this 45 | license. You must not remove this notice, or any other, from this software. 46 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | 3 | :deps {org.clojure/clojure {:mvn/version "1.10.3"}} 4 | 5 | :aliases 6 | {:run 7 | {:main-opts ["-m" "demo.core"]} 8 | 9 | :test 10 | {:extra-paths ["test"] 11 | :extra-deps {lambdaisland/kaocha {:mvn/version "1.0.861"}} 12 | :main-opts ["-m" "kaocha.runner"]} 13 | 14 | :uberjar 15 | {:replace-deps {com.github.seancorfield/depstar {:mvn/version "2.0.216"}} 16 | :exec-fn hf.depstar/uberjar 17 | :exec-args {:main-class demo.core 18 | :jvm-opts [ 19 | ;; Typically this results in smaller class sizes and faster startup times. 20 | "-Dclojure.compiler.direct-linking=true" 21 | 22 | ;; Reduce class size and make classloading faster. 23 | "-Dclojure.compiler.elide-meta=[:doc :file :line :added]" 24 | 25 | "-Ddepstar.debug=true"] 26 | :jar "target/app.jar" 27 | :aot true 28 | :verbose true 29 | :exclude ["dev/"]}}}} 30 | -------------------------------------------------------------------------------- /src/demo/ant.clj: -------------------------------------------------------------------------------- 1 | (ns demo.ant 2 | (:require [demo.util :refer [bound rank-by roulette]] 3 | [demo.world :as world])) 4 | 5 | (defn drop-food [place] 6 | (-> place (update :food inc) (update :ant dissoc :food))) 7 | 8 | (defn trail [place] 9 | (update place :pher #(if (:home place) % (inc %)))) 10 | 11 | (defn move [from-place to-place] 12 | [(trail (dissoc from-place :ant)) 13 | (assoc to-place :ant (:ant from-place))]) 14 | 15 | (defn take-food [place] 16 | (-> place (update :food dec) (assoc-in [:ant :food] true))) 17 | 18 | (defn turn [place amount] 19 | (update-in place [:ant :dir] (comp (partial bound 8) +) amount)) 20 | 21 | (def rank-by-pher (partial rank-by :pher)) 22 | (def rank-by-home (partial rank-by #(if (:home %) 1 0))) 23 | (def rank-by-food (partial rank-by :food)) 24 | (def foraging (juxt rank-by-food rank-by-pher)) 25 | (def homing (juxt rank-by-home rank-by-pher)) 26 | (def turn-around #(turn % 4)) 27 | 28 | (defn rand-behavior [config world behavior place] 29 | (let [[ahead ahead-left ahead-right :as nearby] (world/nearby-places config world (:location place) (get-in place [:ant :dir])) 30 | ranks (apply merge-with + (behavior nearby)) 31 | actions [#(move % ahead) #(turn % -1) #(turn % 1)] 32 | index (roulette [(if (:ant ahead) 0 (ranks ahead)) 33 | (ranks ahead-left) 34 | (ranks ahead-right)])] 35 | ((actions index) place))) 36 | 37 | (defn behave [config world place] 38 | (let [[ahead & _] (world/nearby-places config world (:location place) (get-in place [:ant :dir]))] 39 | (if (get-in place [:ant :food]) 40 | (cond 41 | (:home place) (-> place drop-food turn-around) 42 | (and (:home ahead) (not (:ant ahead))) (move place ahead) 43 | :else (rand-behavior config world homing place)) 44 | (cond 45 | (and (pos? (:food place)) (not (:home place))) (-> place take-food turn-around) 46 | (and (pos? (:food ahead)) (not (:home ahead))) (move place ahead) 47 | :else (rand-behavior config world foraging place))))) 48 | -------------------------------------------------------------------------------- /src/demo/core.clj: -------------------------------------------------------------------------------- 1 | (ns demo.core 2 | (:require [demo.ant :as ant] 3 | [demo.domain :as domain] 4 | [demo.ui.core :as ui] 5 | [demo.ui.world :as ui-world]) 6 | (:import (javax.swing JPanel)) 7 | (:gen-class)) 8 | 9 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ant sim ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 10 | ;; Copyright (c) Rich Hickey. All rights reserved. 11 | ;; 12 | ;; The use and distribution terms for this software are covered by the Common 13 | ;; Public License 1.0 (http://opensource.org/licenses/cpl.php) which can be 14 | ;; found in the file CPL.TXT at the root of this distribution. By using this 15 | ;; software in any fashion, you are agreeing to be bound by the terms of this 16 | ;; license. You must not remove this notice, or any other, from this software. 17 | 18 | (defonce animator (agent nil)) 19 | (defonce evaporator (agent nil)) 20 | 21 | (defonce app-state 22 | (atom {:panel nil 23 | :applet nil 24 | :frame nil 25 | :world nil 26 | :config {:animation-sleep-ms 100 27 | :ant-sleep-ms 40 28 | :dim 120 ; World dimensions 29 | :evaporation-rate 0.99 30 | :evaporation-sleep-ms 1000 31 | :food-places 100 ; Number of places with food 32 | :food-range 100 ; Max amount of food 33 | :food-scale 30.0 ; Scale factor for food drawing 34 | :nants-sqrt 10 ; Number of ants = nants-sqrt^2 35 | :pher-scale 20.0 ; Scale factor for pheromone drawing 36 | :scale 5 ; Pixels per world cell 37 | }})) 38 | 39 | (defn on-render [state img] 40 | (doto img 41 | (ui-world/fill-world-bg) 42 | (ui-world/render-all-places (:config state) (:world state)) 43 | (ui-world/render-home {:scale (get-in state [:config :scale]) 44 | :home-off (/ (get-in state [:config :dim]) 4) 45 | :nants-sqrt (get-in state [:config :nants-sqrt])}))) 46 | 47 | (defn init-panel [state] 48 | (swap! state 49 | (fn [state] 50 | (let [x-scale (* (get-in state [:config :scale]) (get-in state [:config :dim])) 51 | y-scale x-scale] 52 | (assoc state :panel (ui/make-panel x-scale y-scale #(on-render state %))))))) 53 | 54 | (defn init-applet [state] 55 | (swap! state 56 | (fn [state] 57 | (assoc state :applet (ui/make-applet (:panel state)))))) 58 | 59 | (defn init-frame [state] 60 | (swap! state 61 | (fn [state] 62 | (assoc state :frame (ui/make-frame (:applet state) "Ants"))))) 63 | 64 | ;; World is a 2D vector of refs to cells. 65 | (defn init-world [state] 66 | (swap! state 67 | (fn [state] 68 | (assoc state :world 69 | (mapv (fn [x] (mapv (fn [y] (ref (domain/build-cell {:location [x y]}))) 70 | (range (get-in state [:config :dim])))) 71 | (range (get-in state [:config :dim])))))) 72 | state) 73 | 74 | (defn init-home [state] 75 | (let [home-off (/ (get-in @state [:config :dim]) 4) 76 | home-range (range home-off (+ (get-in @state [:config :nants-sqrt]) home-off))] 77 | (doseq [x home-range y home-range 78 | :let [place (get-in (:world @state) [x y])]] 79 | (alter place assoc :home true 80 | :ant (domain/build-ant {:dir (rand-int 8) 81 | :agent (agent (:location @place))})))) 82 | state) 83 | 84 | (defn init-ants [state] 85 | (doseq [row (:world @state), col row] 86 | (alter col assoc :ant 87 | (domain/build-ant {:dir (rand-int 8) 88 | :agent (agent (:location col))}))) 89 | state) 90 | 91 | (defn init-food [state] 92 | (doseq [_ (range (get-in @state [:config :food-places])) 93 | :let [random-loc [(rand-int (get-in @state [:config :dim])) 94 | (rand-int (get-in @state [:config :dim]))]]] 95 | (-> @state 96 | (:world) 97 | (get-in random-loc) 98 | (alter assoc :food (rand-int (get-in @state [:config :food-range]))))) 99 | state) 100 | 101 | (defn reset-food [state] 102 | (doseq [row (:world @state), col row] 103 | (alter col assoc :food 0)) 104 | (init-food state)) 105 | 106 | (defn reset-pheromones [state] 107 | (doseq [row (:world @state), col row] 108 | (alter col assoc :pher 0))) 109 | 110 | (defn animation-loop [_ state] 111 | (send-off *agent* animation-loop state) 112 | (.repaint ^JPanel (:panel @state)) 113 | (Thread/sleep (get-in @state [:config :animation-sleep-ms])) 114 | nil) 115 | 116 | (defn evaporation-loop [_ state] 117 | (send-off *agent* evaporation-loop state) 118 | (dosync 119 | (doseq [row (:world @state), col row] 120 | (alter col update :pher * (get-in @state [:config :evaporation-rate])))) 121 | (Thread/sleep (get-in @state [:config :evaporation-sleep-ms])) 122 | nil) 123 | 124 | (defn ant-loop [location state] 125 | (Thread/sleep (get-in @state [:config :ant-sleep-ms])) 126 | (dosync 127 | (send-off *agent* ant-loop state) 128 | (let [world (:world @state) 129 | place @(get-in world location) 130 | new-places (flatten [(ant/behave (:config @state) world place)])] 131 | (doseq [new-place new-places] 132 | (ref-set (get-in world (:location new-place)) new-place)) 133 | (-> new-places last :location)))) 134 | 135 | (defn start-ants [state] 136 | (doseq [row (:world @state), col row 137 | :let [place @col] 138 | :when (:ant place)] 139 | (send-off (get-in place [:ant :agent]) ant-loop state))) 140 | 141 | (defn start [state] 142 | (send-off animator animation-loop state) 143 | (send-off evaporator evaporation-loop state) 144 | (start-ants state)) 145 | 146 | (defn -main [] 147 | (dosync (-> app-state 148 | (init-world) 149 | (init-home) 150 | (init-food))) 151 | (doto app-state 152 | (init-panel) 153 | (init-applet) 154 | (init-frame) 155 | (start))) 156 | 157 | (comment 158 | (-main)) 159 | 160 | (comment 161 | (dosync 162 | (reset-pheromones app-state) 163 | (reset-food app-state))) 164 | -------------------------------------------------------------------------------- /src/demo/domain.clj: -------------------------------------------------------------------------------- 1 | (ns demo.domain) 2 | 3 | (defrecord Ant [dir agent food]) 4 | (defrecord Cell [ant food home location pher]) 5 | 6 | (defn build-cell [args] 7 | (map->Cell (merge {:food 0 :pher 0 :home false :location [0 0]} args))) 8 | 9 | (defn build-ant [args] 10 | (map->Ant args)) 11 | -------------------------------------------------------------------------------- /src/demo/ui/ant.clj: -------------------------------------------------------------------------------- 1 | (ns demo.ui.ant 2 | (:require [demo.ui.core :as ui] 3 | [demo.util :as util])) 4 | 5 | (def directions 6 | {0 [2 0 2 4] 7 | 1 [4 0 0 4] 8 | 2 [4 2 0 2] 9 | 3 [4 4 0 0] 10 | 4 [2 4 2 0] 11 | 5 [0 4 4 0] 12 | 6 [0 2 4 2] 13 | 7 [0 0 4 4]}) 14 | 15 | (defn ant-color [ant] 16 | (if (:food ant) :red :black)) 17 | 18 | (defn next-loc [dir loc config] 19 | (-> dir directions (util/delta (util/scale loc (:scale config))))) 20 | 21 | (defn render-ant [ant img config x y] 22 | (ui/make-line img {:color (ant-color ant) 23 | :border (next-loc (:dir ant) [x y] config)})) 24 | -------------------------------------------------------------------------------- /src/demo/ui/core.clj: -------------------------------------------------------------------------------- 1 | (ns demo.ui.core 2 | (:require [demo.util :as util]) 3 | (:import (javax.swing JApplet JFrame JPanel) 4 | (java.awt Graphics Color Dimension) 5 | (java.awt.image BufferedImage))) 6 | 7 | (def all-colors 8 | {:blue (Color/blue) 9 | :red (Color/red) 10 | :black (Color/black) 11 | :white (Color/white)}) 12 | 13 | (defn colors [color] 14 | (if (instance? Color color) 15 | color 16 | (all-colors color))) 17 | 18 | (defn color ^Color [[^long r ^long g ^long b] value max-value] 19 | (new Color r g b (util/scaled-color {:value value :max-value max-value}))) 20 | 21 | (defn make-img ^BufferedImage [[^long x ^long y]] 22 | (BufferedImage. x y (BufferedImage/TYPE_INT_ARGB))) 23 | 24 | (defn make-frame ^JFrame [^JApplet applet ^String name] 25 | (doto (JFrame. name) 26 | (.add (.getContentPane applet)) 27 | (.pack) 28 | (.setLocationByPlatform true) 29 | (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) 30 | (.setVisible true))) 31 | 32 | (defn render [^Graphics graphics width height on-render] 33 | (let [img (make-img [width height])] 34 | (on-render img) 35 | (.drawImage graphics img 0 0 nil) 36 | (.dispose (.getGraphics img)))) 37 | 38 | (defn make-panel [width height on-render] 39 | (doto (proxy [JPanel] [] 40 | (paint [graphics] (render graphics width height on-render))) 41 | (.setPreferredSize (new Dimension width height)))) 42 | 43 | (defn make-rect [^BufferedImage img {:keys [color border fill]}] 44 | (let [graphics (.getGraphics img)] 45 | (when color 46 | (.setColor graphics (colors color))) 47 | (when border 48 | (.drawRect graphics (nth border 0) (nth border 1) (nth border 2) (nth border 3))) 49 | (when fill 50 | (.fillRect graphics (nth fill 0) (nth fill 1) (nth fill 2) (nth fill 3))))) 51 | 52 | (defn make-line [^BufferedImage img {:keys [color border]}] 53 | (let [graphics (.getGraphics img)] 54 | (when color 55 | (.setColor graphics (colors color))) 56 | (when border 57 | (.drawLine graphics (nth border 0) (nth border 1) (nth border 2) (nth border 3))))) 58 | 59 | (defn make-applet ^JApplet [panel] 60 | (doto (new JApplet) 61 | (.setContentPane panel) 62 | (.setVisible true))) 63 | -------------------------------------------------------------------------------- /src/demo/ui/world.clj: -------------------------------------------------------------------------------- 1 | (ns demo.ui.world 2 | (:require [demo.ui.ant :as ui-ant] 3 | [demo.ui.core :as ui]) 4 | (:import (java.awt.image BufferedImage))) 5 | 6 | (defn food-color [config food] 7 | (ui/color [255 0 0] food (:food-scale config))) 8 | 9 | (defn pheromone-color [config pheromone] 10 | (ui/color [0 255 0] pheromone (:pher-scale config))) 11 | 12 | (defn render-place-as-pheromone [img config pher x y] 13 | (ui/make-rect img {:color (pheromone-color config pher) 14 | :fill [(* x (:scale config)) 15 | (* y (:scale config)) 16 | (:scale config) (:scale config)]})) 17 | 18 | (defn render-place-as-food [img config food x y] 19 | (ui/make-rect img {:color (food-color config food) 20 | :fill [(* x (:scale config)) 21 | (* y (:scale config)) 22 | (:scale config) (:scale config)]})) 23 | 24 | (defn render-home [img {:keys [scale home-off nants-sqrt]}] 25 | (ui/make-rect img 26 | {:color :blue 27 | :border [(* scale home-off) (* scale home-off) 28 | (* scale nants-sqrt) (* scale nants-sqrt)]})) 29 | 30 | (defn fill-world-bg [^BufferedImage img] 31 | (ui/make-rect img {:color :white 32 | :fill [0 0 (.getWidth img) (.getHeight img)]})) 33 | 34 | (defn render-all-places [img config world] 35 | (doseq [x (range (:dim config)), y (range (:dim config))] 36 | (let [{:keys [pher food ant]} (-> world (get-in [x y]) deref)] 37 | (when (pos? pher) (render-place-as-pheromone img config pher x y)) 38 | (when (pos? food) (render-place-as-food img config food x y)) 39 | (when ant (ui-ant/render-ant ant img config x y))))) 40 | -------------------------------------------------------------------------------- /src/demo/util.clj: -------------------------------------------------------------------------------- 1 | (ns demo.util) 2 | 3 | (defn rank-by 4 | "Returns a map of xs to their 1-based rank when sorted by keyfn." 5 | [keyfn xs] 6 | (let [sorted (sort-by (comp float keyfn) xs)] 7 | (reduce (fn [ret i] (assoc ret (nth sorted i) (inc i))) 8 | {} (range (count sorted))))) 9 | 10 | (defn bound 11 | "Returns n wrapped into range 0-b" 12 | [b n] 13 | (let [n (rem n b)] 14 | (if (neg? n) 15 | (+ n b) 16 | n))) 17 | 18 | (defn roulette 19 | "Given a vector of slice sizes, returns the index of a slice given a random 20 | spin of a roulette wheel with compartments proportional to slices." 21 | [slices] 22 | (let [total (reduce + slices) 23 | r (rand total)] 24 | (reduce 25 | (fn [{:keys [i slice-sum]} slice] 26 | (let [slice-sum (+ slice-sum slice)] 27 | (if (< r slice-sum) 28 | (reduced i) 29 | {:i (inc i) 30 | :slice-sum slice-sum}))) 31 | {:i 0 :slice-sum 0} 32 | slices))) 33 | 34 | (defn scaled-color ^long [{:keys [value max-value]}] 35 | (int (min 255 (* 255 (/ value max-value))))) 36 | 37 | (defn delta [[ax ay bx by] [x y]] 38 | [(+ ax x) (+ ay y) (+ bx x) (+ by y)]) 39 | 40 | (defn scale [[h t] amount] 41 | [(* amount h) (* amount t)]) 42 | -------------------------------------------------------------------------------- /src/demo/world.clj: -------------------------------------------------------------------------------- 1 | (ns demo.world 2 | (:require [demo.util :as util])) 3 | 4 | (def direction-delta 5 | {0 [0 -1] 6 | 1 [1 -1] 7 | 2 [1 0] 8 | 3 [1 1] 9 | 4 [0 1] 10 | 5 [-1 1] 11 | 6 [-1 0] 12 | 7 [-1 -1]}) 13 | 14 | (defn delta-location [config location direction] 15 | (->> direction 16 | (util/bound 8) 17 | (direction-delta) 18 | (map + location) 19 | (map (partial util/bound (:dim config))))) 20 | 21 | (defn nearby-places [config world location direction] 22 | (->> direction 23 | ((juxt identity dec inc)) 24 | (map (partial delta-location config location)) 25 | (map (partial get-in world)) 26 | (map deref))) 27 | -------------------------------------------------------------------------------- /test/demo/ant_test.clj: -------------------------------------------------------------------------------- 1 | (ns demo.ant-test 2 | (:require [clojure.test :refer [deftest testing is]] 3 | [demo.ant :as ant])) 4 | 5 | (deftest drop-food 6 | (let [place {:food 9 :ant {:food true :dir 10}}] 7 | (testing "drops food from ant" 8 | (is (= {:dir 10} (:ant (ant/drop-food place))))) 9 | (testing "increments places' food" 10 | (is (= {:food 10} (select-keys (ant/drop-food place) [:food])))))) 11 | 12 | (deftest trail 13 | (testing "does not leave pheromone trail when at home" 14 | (is (= 8 (:pher (ant/trail {:home true :pher 8}))))) 15 | (testing "leaves pheromone trail when not home" 16 | (is (= 9 (:pher (ant/trail {:home false :pher 8})))))) 17 | 18 | (deftest move 19 | (testing "moves ant from initial place to destination" 20 | (is (= [nil {:dir 1}] 21 | (map :ant (ant/move {:pher 1 :ant {:dir 1}} 22 | {:pher 2 :ant {:dir 2}}))))) 23 | (testing "does not leave trail behind when at home" 24 | (is (= 8 (:pher (first (ant/move {:ant {:dir 1} :home true :pher 8} {:ant {:dir 2} :home true :pher 8}))))) 25 | (is (= 8 (:pher (second (ant/move {:ant {:dir 1} :home true :pher 8} {:ant {:dir 2} :home true :pher 8})))))) 26 | (testing "leaves trail behind when not home" 27 | (is (= 9 (:pher (first (ant/move {:ant {:dir 1} :home false :pher 8} {:ant {:dir 2} :home false :pher 8}))))) 28 | (is (= 8 (:pher (second (ant/move {:ant {:dir 1} :home false :pher 8} {:ant {:dir 2} :home false :pher 8}))))))) 29 | 30 | (deftest take-food 31 | (let [place {:food 1 :ant {:food false}}] 32 | (testing "decrements places' food" 33 | (is (= 0 (:food (ant/take-food place))))) 34 | (testing "marks ant with food" 35 | (is (= {:food true} (:ant (ant/take-food place))))))) 36 | -------------------------------------------------------------------------------- /tests.edn: -------------------------------------------------------------------------------- 1 | #kaocha/v1 2 | {:tests [{;; Every suite must have an :id 3 | :id :unit 4 | 5 | ;; Directories containing files under test. This is used to 6 | ;; watch for changes, and when doing code coverage analysis 7 | ;; through Cloverage. These directories are *not* automatically 8 | ;; added to the classpath. 9 | :source-paths ["src"] 10 | 11 | ;; Directories containing tests. These will automatically be 12 | ;; added to the classpath when running this suite. 13 | :test-paths ["test"] 14 | 15 | ;; Regex strings to determine whether a namespace contains 16 | ;; tests. (use strings, not actual regexes, due to a limitation of Aero) 17 | :ns-patterns ["-test$"]}] 18 | 19 | :plugins [:kaocha.plugin/print-invocations 20 | :kaocha.plugin/profiling] 21 | 22 | ;; Colorize output (use ANSI escape sequences). 23 | :color? true 24 | 25 | ;; Watch the file system for changes and re-run. You can change this here to be 26 | ;; on by default, then disable it when necessary with `--no-watch`. 27 | :watch? false 28 | 29 | ;; Specifiy the reporter function that generates output. Must be a namespaced 30 | ;; symbol, or a vector of symbols. The symbols must refer to vars, which Kaocha 31 | ;; will make sure are loaded. When providing a vector of symbols, or pointing 32 | ;; at a var containing a vector, then kaocha will call all referenced functions 33 | ;; for reporting. 34 | :reporter kaocha.report/documentation 35 | 36 | ;; Enable/disable output capturing. 37 | :capture-output? true 38 | 39 | ;; Plugin specific configuration. Show the 10 slowest tests of each type, rather 40 | ;; than only 3. 41 | :kaocha.plugin.profiling/count 10} 42 | --------------------------------------------------------------------------------