...
 
Commits (3)
......@@ -2,6 +2,11 @@
(:use amoebas.defs amoebas.util)
)
;;
;; this namespace contains a (hopefully growing) collection of stuff that
;; can be sueful when writing amoeba functions
;;
;;
;; directions
;; directions correspond to the eight neighboring cells
......@@ -28,35 +33,65 @@
(def dW 7)
(def Neighbors ;; NB: this is a region
(def Neighbors ;; the positions of all neighbors, i.e. the cells we can directly move to, hit, or divide into
;; NB: this is a region
;; also note: the direction can be used as an index into this vector.
[ [-1 -1] [0 -1] [1 -1] [1 0] [1 1] [0 1] [-1 1] [-1 0] ]
)
(def Here [0 0])
(def Here [0 0]) ;; we're here!
(def Environment ;; NB: this is a region
(def Environment ;; the positions of all visible cells
;; NB: this is a region
(for [x (range (inc (* 2 ViewDistance))) y (range (inc (* 2 ViewDistance)))] [(- x ViewDistance) (- y ViewDistance)])
)
(def Neighbor-To-Dir
(def Neighbor-To-Dir ;; maps neighboring position to the corresponding direction we need to move to/hit in/divide into
;; it's the inverse map of Neighbors above
{ [-1 -1] 0 [0 -1] 1 [1 -1] 2 [1 0] 3 [1 1] 4 [0 1] 5 [-1 1] 6 [-1 0] 6 }
)
(defn- add-dir [dir d] (mod (+ dir d) 8) )
(defn CW [dir] (add-dir dir 1))
(defn CW
"given a direction, this returns the next direction in clockwise rotation"
[dir]
(add-dir dir 1)
)
(defn CCW [dir] (add-dir dir -1))
(defn CCW
"given a direction, this returns the next direction in counter-clockwise rotation"
[dir]
(add-dir dir -1)
)
(defn CW90 [dir] (add-dir dir 2))
(defn CW90
"given a direction, this returns the direction in clockwise rotation that is perpendicular to it"
[dir]
(add-dir dir 2)
)
(defn CCW90 [dir] (add-dir dir -2))
(defn CCW90
"given a direction, this returns the direction in counter-clockwise rotation that is perpendicular to it"
[dir]
(add-dir dir -2)
)
(defn AWAY [dir] (add-dir dir 4))
(defn AWAY
"given a direction, this returns the opposite direction"
[dir]
(add-dir dir 4)
)
(defn add-coordinates
"adds two coordinate vectors"
[p1 p2]
(mapv + p1 p2)
......@@ -67,7 +102,10 @@
;; sections are regions that are contiguous parts of the environment
;;
(def Env-Sections
(def Env-Sections
;; this vector contains the sections corresponding to the eight directions
;; moving in this direction means that the max norm distance to the positions
;; in these sections does not increase
[
(for [a (range (inc ViewDistance)) b (range (inc ViewDistance))] [(- a) (- b)]) ;; NW
(for [a (range (inc ViewDistance)) b (range (inc (* 2 a)))] [(- b a) (- a)]) ;; N
......@@ -81,6 +119,7 @@
)
(defn cells-in-section
"returns all cells of a specified section; the section is specified by its direction"
[dir env]
(map env (Env-Sections dir))
......@@ -90,13 +129,21 @@
;; energy utility functions
;;
(defn energy-level [r] (int (* r MaxAmoebaEnergy)))
(defn energy-level
[r]
(int (* r MaxAmoebaEnergy))
)
(defn above-division-energy-level [r] (int (+ MinDivideEnergy (* r (- MaxAmoebaEnergy MinDivideEnergy)))) )
(defn above-division-energy-level
[r]
(int (+ MinDivideEnergy (* r (- MaxAmoebaEnergy MinDivideEnergy))))
)
;;
;; functions operating individual cells
;; (a cell is represented by a vector pair of [x y] coordinates
;;
;;
;;
(defn cell-empty?
......@@ -106,12 +153,14 @@
)
(defn empty-neighbors
"computes the directions in which there are empty cells"
[env]
(filter #(cell-empty? env (Neighbors %)) (range 8))
)
(defn contains-hostile
(defn contains-hostile?
"given a position, determines whether it contains a hostile"
[species pos env]
(let
......@@ -124,7 +173,8 @@
)
)
(defn contains-friendly
(defn contains-friendly?
"given a position, determines whether it contains a friendly"
[species pos env]
(let
......@@ -149,7 +199,7 @@
[species region env]
(filter #(contains-hostile species % env) region)
(filter #(contains-hostile? species % env) region)
)
(defn friendlies
......@@ -157,7 +207,7 @@
[species region env]
(filter #(contains-friendly species % env) region)
(filter #(contains-friendly? species % env) region)
)
(defn at-least-fuel-subregion
......@@ -185,6 +235,7 @@
)
(defn empty-subregion
"determines the subregion of empty cells in a region"
[region env]
(filter #(cell-empty? env %) region)
......@@ -197,24 +248,28 @@
;;
(defn sections-by-hostiles
"sorts the sections by the number of hostiles in them, ascending"
[dirs env species]
(sort-by #(count (hostiles (Env-Sections %))) dirs)
)
(defn sections-by-friendlies
"sorts the sections by the number of friendlies in them, ascending"
[dirs env species]
(sort-by #(count (friendlies (Env-Sections %))) dirs)
)
(defn sections-by-fuel
"sorts the sections by the amount of fuel in them, ascending"
[dirs env]
(sort-by #(total-fuel (Env-Sections %) env) dirs)
)
(defn sections-by-fuel-density
"sorts the sections by the average amount of fuel in them, ascending"
[dirs env]
(sort-by #(average-fuel (Env-Sections %) env) dirs)
......
......@@ -19,6 +19,9 @@
;;
(defn create-world
"In the beginning, we just create the earth. It is neither formless (in fact, it is quadratic),
nor is it empty (we put the specified amount of fuel in each cell).
The representation of the world is just a vector. We do the indexing ourselves."
[fuel]
(vec (for [i (range WorldSize) j (range WorldSize)] fuel))
......@@ -31,45 +34,36 @@
)
(defn add-pos
"add a displacement to a coordinate pair with wrapping in both dimensions"
[[x y] [dx dy]]
[(wrap (+ x dx)) (wrap (+ y dy))]
)
(defn world-index [[x y]] (+ (* WorldSize y) x))
(defn world-index
"compute the index corresponding to a cell position"
[[x y]]
(+ (* WorldSize y) x)
)
(defn cell-fuel
"return the amount of fuel in the cell specified by the position"
[world pos]
(world (world-index pos))
)
(defn cell-fuel!
[world pos fuel]
(assoc! world (world-index pos) fuel)
)
(defn add-fuel!
(defn- add-fuel!
[world pos delta]
(let [idx (world-index pos)]
(assoc! world idx (bound 0 (+ (world idx) delta) MaxCellEnergy))
)
)
(defn world-add-fuel
[world delta]
(doseq
[x (range WorldSize) y (range WorldSize)]
(add-fuel! world [x y] delta)
)
)
(defn sunshine
"Let there be light."
[world pop]
(vec
......@@ -87,12 +81,14 @@
;;
(defn free?
"determine whether the specified cell is free"
[pop pos]
(not (pop pos)) ;; contains? not working on transient array maps
)
(defn occupied?
"determine whether the specified cell is occupied"
[pop pos]
(pop pos) ;; contains? not working on transient array maps
......@@ -109,12 +105,14 @@
occupant (pop pos)
]
{:fuel fuel :occupant occupant} ;; FIXME (?): leaking data
{:fuel fuel :occupant occupant}
)
)
(defn environment
"create the environment function around the specified position, given
the population and the world"
[pos world pop]
(memoize (fn
......@@ -136,12 +134,13 @@
nil
)
)
( [] pos ) ;; FIXME (this is a hack to be able to obtain the absolute position, for debugging)
;; ( [] pos ) ;; FIXME (this is a hack to be able to obtain the absolute position, for debugging)
))
)
(defn update-amoeba
"produce an updated version of amoeba a, in various variations"
(
[a energy health data]
......@@ -175,6 +174,7 @@
(defn step-rest
"perform a step action"
[world pop pos a data]
(let
......@@ -195,69 +195,73 @@
)
(defn step-move
"perform a move action"
[world pop pos a dir data]
(let
[ new-pos (add-pos pos (Neighbor-Cells dir)) ]
(if (and (<= MoveEnergy (:energy a)) (free? pop new-pos))
(if (and (<= MoveEnergy (:energy a)) (free? pop new-pos)) ;; gotta have the energy and the cell must be empty
(do
(dissoc! pop pos)
(assoc! pop new-pos (update-amoeba a (- (:energy a) MoveEnergy) data))
)
(step-rest world pop pos a data)
(step-rest world pop pos a data) ;; otherwise: default action, rest
)
)
)
(defn step-hit
"hit your neighbor"
[world pop pos a dir data]
(let
[
target-pos (add-pos pos (Neighbor-Cells dir))
target (pop target-pos)
new-energy (- (:energy a) AttackEnergy)
target-pos (add-pos pos (Neighbor-Cells dir)) ;; where we hit
target (pop target-pos) ;; whom we hit
new-energy (- (:energy a) AttackEnergy)
]
(if (and (<= AttackEnergy (:energy a)) target)
(if (and (<= AttackEnergy (:energy a)) target) ;; we gotta have enough energy, and there has to be someone to hit
(do
(if (<= (:health target) HitLoss)
(if (<= (:health target) HitLoss) ;; if fatally injured
(do
(dissoc! pop target-pos) ;; target dead
(add-fuel! world target-pos (:energy target))
(dissoc! pop target-pos) ;; target dead
(add-fuel! world target-pos (:energy target)) ;; the corpse becomes fuel
)
(assoc! pop target-pos (update-amoeba target (- (:health target) HitLoss))) ;; target injured
(assoc! pop target-pos (update-amoeba target (- (:health target) HitLoss))) ;; target injured
)
(assoc! pop pos (update-amoeba a (- (:energy a) AttackEnergy) data)) ;; expended attack energy
(assoc! pop pos (update-amoeba a (- (:energy a) AttackEnergy) data)) ;; expended attack energy
)
(step-rest world pop pos a data)
(step-rest world pop pos a data) ;; otherwise: default action, rest
)
)
)
(defn step-divide
"Oooh, it's an... amoeba. :-("
[world pop pos a dir f data child-data]
(let
[
new-pos (add-pos pos (Neighbor-Cells dir))
new-energy (int (/ (- (:energy a) DivideEnergyLoss) 2))
new-energy (int (/ (- (:energy a) DivideEnergyLoss) 2)) ;; we lose energy, and split the rest like, uh, clones
]
(if (and (<= MinDivideEnergy (:energy a)) (free? pop new-pos))
(if (and (<= MinDivideEnergy (:energy a)) (free? pop new-pos)) ;; we gotta have enough energy, and the target has to be empty
(do
(assoc! pop new-pos (struct Amoeba f (:species a) new-energy (:health a) child-data)) ;; the spawn
(assoc! pop pos (update-amoeba a new-energy data))
(assoc! pop pos (update-amoeba a new-energy data)) ;; that's us
)
(step-rest world pop pos a data)
(step-rest world pop pos a data) ;; otherwise: default action, rest
)
)
)
(defn step
"make a step for an amoeba"
[world pop pos a]
(try
......@@ -278,9 +282,9 @@
:divide (step-divide world pop pos a dir f data child-data)
)
)
(catch Exception e
(catch Exception e ;; if something went wrong...
(println "ERROR: " e) ;; DEBUG
(step-rest world pop pos a (:data a))
(step-rest world pop pos a (:data a)) ;; ... default action, rest
)
)
)
......@@ -288,6 +292,7 @@
(defn step-all
"step through all amoebas currently in the population"
[world population]
(let
......
......@@ -7,10 +7,21 @@
)
)
(import '(java.awt.image BufferedImage))
(import '(javax.imageio ImageIO))
;;
;; this namespace contains a collection of stuff that doesn't
;; fit anywhere else
;;
(defn bound
"bounds the middle value by the other two; if it sits
between them, returns the value itself, otherwise it
returns the lower bound (the first parameter) if less than it,
or the upper bound (the last parameter) if greater than it."
[a x b]
(cond
......@@ -21,12 +32,18 @@
)
(defn sum
"sums all values in s"
[s]
(apply + s)
)
(defn population-stats
"given a population p, which is a map from locations to amoebas,
compute the subpopulation sizes for each species,
the total energy for each subpopulation,
and the set of live species in the population"
[p]
(let
......@@ -42,7 +59,12 @@
)
)
(defn random-location [] [(rand-int WorldSize) (rand-int WorldSize)] )
(defn random-location
"produce a random location"
[]
[(rand-int WorldSize) (rand-int WorldSize)]
)
......@@ -51,6 +73,7 @@
;;
(defn get-png-imagewriter
"get an image write for the PNG format"
[]
(let
......@@ -64,7 +87,11 @@
)
(defn create-directory [dir]
(defn create-directory
"create a directory at the specified path,
unless one exists already"
[dir]
(let
[ f (io/file dir) ]
......@@ -75,6 +102,9 @@
)
(defn save-frame
"save the bitmap representing the specified frame as an image
in the specfied directory, with a name generated from the round
number gen; in PNG format"
[frame dir gen]
(let
......@@ -94,6 +124,9 @@
;;
(defn write-csv-stats
"write two files containing the population sizes for each species
and the total energy for each round, as a CSV file"
[dir species statsv]
(with-open
......
#Leiningen
#Tue Mar 26 18:39:56 CET 2019
#Tue Mar 26 20:41:19 CET 2019
groupId=amoebas
artifactId=amoebas
version=0.1.0-SNAPSHOT
revision=d3858702b660d093395ac69bc0dcbd41c8aa4562\n
revision=f27b332eec9afeffd5bcb83c71b38f2cfbe0adaa\n
36161
\ No newline at end of file