diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f553393 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ + +target/ + +pom.xml + +.lein-repl-history + +.lein-failures + +eastwood.txt diff --git a/docs/uberdoc.html b/docs/uberdoc.html new file mode 100644 index 0000000..91fdaea --- /dev/null +++ b/docs/uberdoc.html @@ -0,0 +1,3997 @@ + +
mw-engine0.1.6-SNAPSHOTCellular automaton world builder. +dependencies
| (this space intentionally left almost blank) | |||||||||||||||||||||
Functions to transform a world and run rules. + | (ns ^{:doc + :author "Simon Brooke"} + mw-engine.core + (:require [clojure.core.reducers :as r] + [mw-engine.world :as world] + [mw-engine.utils :refer [get-int-or-zero map-world]]) + (:gen-class)) | |||||||||||||||||||||
mw-engine: the state/transition engine of MicroWorld. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA. + +Copyright (C) 2014 Simon Brooke + +Every rule is a function of two arguments, a cell and a world. If the rule +fires, it returns a new cell, which should have the same values for :x and +:y as the old cell. Anything else can be modified. + +While any function of two arguments can be used as a rule, a special high
+level rule language is provided by the A cell is a map containing at least values for the keys :x, :y, and :state; +a transformation should not alter the values of :x or :y, and should not +return a cell without a keyword as the value of :state. Anything else is +legal. + +A world is a two dimensional matrix (sequence of sequences) of cells, such
+that every cell's :x and :y properties reflect its place in the matrix.
+See Each time the world is transformed (see | ||||||||||||||||||||||
Apply a single | (defn apply-rule + ([world cell rule] + (cond + (ifn? rule) (apply-rule world cell rule nil) + (seq? rule) (let [[afn src] rule] (apply-rule world cell afn src)))) + ([world cell rule source] + (let [result (apply rule (list cell world))] + (cond + (and result source) (merge result {:rule source}) + true result)))) | |||||||||||||||||||||
Derive a cell from this | (defn- apply-rules + [world cell rules] + (cond (empty? rules) cell + true (let [result (apply-rule world cell (first rules))] + (cond result result + true (apply-rules world cell (rest rules)))))) | |||||||||||||||||||||
Derive a cell from this | (defn- transform-cell + [world cell rules] + (try + (merge + (apply-rules world cell rules) + {:generation (+ (get-int-or-zero cell :generation) 1)}) + (catch Exception e + (merge cell {:error + (format "%s at generation %d when in state %s" + (.getMessage e) + (:generation cell) + (:state cell)) + :stacktrace (map #(.toString %) (.getStackTrace e)) + :state :error})))) | |||||||||||||||||||||
Return a world derived from this | (defn transform-world + [world rules] + (map-world world transform-cell (list rules))) | |||||||||||||||||||||
Consider this single argument as a map of | (defn- transform-world-state + [state] + (let [world (transform-world (:world state) (:rules state))] + ;;(world/print-world world) + {:world world :rules (:rules state)})) | |||||||||||||||||||||
Run this world with these rules for this number of generations. + +
| (defn run-world + [world init-rules rules generations] + (reduce (fn [world _iteration] + (transform-world world rules)) + (transform-world world init-rules) + (range generations))) | |||||||||||||||||||||
Simple functions to allow a world to be visualised. + | (ns ^{:doc + :author "Simon Brooke"} + mw-engine.display + (:require [hiccup.core :refer [html]] + mw-engine.utils + mw-engine.world)) | |||||||||||||||||||||
mw-engine: the state/transition engine of MicroWorld. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA. + +Copyright (C) 2014 Simon Brooke + | ||||||||||||||||||||||
+ | (defn format-css-class [state] + "Format this `state`, assumed to be a keyword indicating a state in the + world, into a CSS class" + (subs (str state) 1)) | |||||||||||||||||||||
Render this | (defn format-image-path + [state] + (format "img/tiles/%s.png" (format-css-class state))) | |||||||||||||||||||||
+ | (defn format-mouseover [cell] + (str cell)) | |||||||||||||||||||||
Render this world cell as a Hiccup table cell. + | (defn render-cell + [cell] + (let [state (:state cell)] + [:td {:class (format-css-class state) :title (format-mouseover cell)} + [:a {:href (format "inspect?x=%d&y=%d" (:x cell) (:y cell))} + [:img {:alt (:state cell) :width 32 :height 32 :src (format-image-path state)}]]])) | |||||||||||||||||||||
Render this world | (defn render-world-row + [row] + (apply vector (cons :tr (map render-cell row)))) | |||||||||||||||||||||
Render this | (defn render-world-table + [world] + (apply vector + (cons :table + (map render-world-row world)))) | |||||||||||||||||||||
Experimental, probably of no interest to anyone else; attempt to + compute drainage on a world, assumed to have altitudes already set + from a heightmap. + | (ns ^{:doc + :author "Simon Brooke"} + mw-engine.drainage + (:require [mw-engine.core :refer [run-world]] + [mw-engine.heightmap :as heightmap] + [mw-engine.utils :refer [get-int-or-zero get-least-cell get-neighbours + get-neighbours-with-property-value + map-world]])) | |||||||||||||||||||||
mw-engine: the state/transition engine of MicroWorld. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA. + +Copyright (C) 2014 Simon Brooke + | ||||||||||||||||||||||
+ | (def ^:dynamic *sealevel* 10) | |||||||||||||||||||||
forward declaration of flow, to allow for a wee bit of mutual recursion. + | (declare flow) | |||||||||||||||||||||
Compute rainfall for a cell with this | (defn rainfall + [gradient remaining map-width] + (cond + ;; if there's no rain left in the cloud, it can't fall; + (zero? remaining) + 0 + (pos? gradient) + ;; rain, on prevailing westerly wind, falls preferentially on rising ground; + (int (rand gradient)) + ;; rain falls randomly across the width of the map... + (zero? (int (rand map-width))) 1 + :else + 0)) | |||||||||||||||||||||
Return a row like this | (defn rain-row + ([row] + (rain-row row 1)) + ([row rain-probability] + (rain-row row (count row) 0 (int (* (count row) rain-probability)))) + ([row map-width previous-altitude drops-in-cloud] + (cond + (empty? row) nil + (pos? drops-in-cloud) + (let [cell (first row) + alt (or (:altitude cell) 0) + rising (- alt previous-altitude) + fall (rainfall rising drops-in-cloud map-width)] + (cons + (assoc cell :rainfall fall) + (rain-row (rest row) map-width alt (- drops-in-cloud fall)))) + :else + (map + #(assoc % :rainfall 0) + row)))) | |||||||||||||||||||||
Simulate rainfall on this | (defn rain-world + [world] + (map + rain-row + world)) | |||||||||||||||||||||
Return a list of the cells in this | (defn flow-contributors + [cell world] + (filter #(map? %) + (map + (fn [n] + (cond + (= cell (get-least-cell (get-neighbours world n) :altitude)) n + (and (= (:altitude cell) (:altitude n)) + (> (or (:flow n) 0) (or (:flow cell) 0))) n)) + (get-neighbours-with-property-value + world (:x cell) (:y cell) 1 :altitude + (or (:altitude cell) 0) >=)))) | |||||||||||||||||||||
Detects point hollows - that is, individual cells all of whose neighbours
+ are higher. Return true if this | (defn is-hollow + [world cell] + ;; quicker to count the elements of the list and compare equality of numbers + ;; than recursive equality check on members, I think. But worth benchmarking. + (let [neighbours (get-neighbours world cell) + altitude (get-int-or-zero cell :altitude)] + (= (count neighbours) + (count (get-neighbours-with-property-value + world (:x cell) (:y cell) 1 :altitude altitude >))))) | |||||||||||||||||||||
Raise the altitude of a copy of this | (defn flood-hollow + ([world cell neighbours] + (let [lowest (get-least-cell neighbours :altitude)] + (merge cell {:state :water :altitude (:altitude lowest)}))) + ([world cell] + (flood-hollow world cell (get-neighbours world cell)))) | |||||||||||||||||||||
Flood all local hollows in this | (defn flood-hollows + [world] + (map-world world + #(if (is-hollow %1 %2) (flood-hollow %1 %2) %2))) | |||||||||||||||||||||
+ | (def max-altitude 255) | |||||||||||||||||||||
Experimental non recursive flow algorithm, needs to be run on a world as
+ many times as there are distinct altitude values. This algorithm works only
+ if applied sequentially from the highest altitude to the lowest, see
+ | (defn flow-nr + [cell world] + (if (= (- max-altitude (get-int-or-zero cell :generation)) + (get-int-or-zero cell :altitude)) + (merge cell + {:flow (reduce + + (map + #(+ (get-int-or-zero % :rainfall) + (get-int-or-zero % :flow)) + (flow-contributors cell world)))}))) | |||||||||||||||||||||
Compute the total flow upstream of this Flow comes from a higher cell to a lower only if the lower is the lowest neighbour of the higher. + | (def flow + (memoize + (fn [cell world] + (cond + (not (nil? (:flow cell))) cell + (<= (or (:altitude cell) 0) *sealevel*) cell + true + (merge cell + {:flow (+ (:rainfall cell) + (apply + + (map (fn [neighbour] (:flow (flow neighbour world))) + (flow-contributors cell world))))}))))) | |||||||||||||||||||||
Experimental non-recursive flow-world algorithm + | (defn flow-world-nr + [world] + (run-world world nil (list flow-nr) max-altitude)) | |||||||||||||||||||||
Return a world like this | (defn flow-world + [world] + (map-world (rain-world world) flow)) | |||||||||||||||||||||
Return a sequence of cells starting with this | (defn explore-lake + [world cell]) | |||||||||||||||||||||
If this | (defn is-lake? + [world cell] + (if + ;; if it's already tagged as a lake, it's a lake + (:lake cell) cell + (let + [outflow (min (map :altitude (get-neighbours world cell)))] + (if-not + (> (:altitude cell) outflow) + (assoc cell :lake true))))) | |||||||||||||||||||||
+ | (defn find-lakes + [world]) | |||||||||||||||||||||
+ | (defn run-drainage + [hmap] + "Create a world from the heightmap `hmap`, rain on it, and then compute river + flows." + (flow-world (rain-world (flood-hollows (heightmap/apply-heightmap hmap))))) | |||||||||||||||||||||
Functions to apply a heightmap to a world. + | (ns ^{:doc + :author "Simon Brooke"} + mw-engine.heightmap + (:import [java.awt.image BufferedImage]) + (:require [fivetonine.collage.util :as collage :only [load-image]] + [mikera.image.core :as imagez :only [filter-image get-pixels]] + [mikera.image.filters :as filters] + [mw-engine.utils :refer [abs get-int get-neighbours map-world]] + [mw-engine.world :refer [make-world]])) | |||||||||||||||||||||
mw-engine: the state/transition engine of MicroWorld. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA. + +Copyright (C) 2014 Simon Brooke + +Heightmaps are considered only as greyscale images, so colour is redundent +(will be ignored). Darker shades are higher. + | ||||||||||||||||||||||
Set the value of this
| (defn tag-property + ([world cell property heightmap] + (tag-property cell property heightmap)) + ([cell property heightmap] + (merge cell + {property + (+ (get-int cell property) + (- 256 + (abs + (mod + (.getRGB heightmap + (get-int cell :x) + (get-int cell :y)) 256))))}))) | |||||||||||||||||||||
Set the | (defn tag-gradient + [world cell] + (let [heights (remove nil? (map :altitude (get-neighbours world cell))) + highest (cond (empty? heights) 0 ;; shouldn't happen + true (apply max heights)) + lowest (cond (empty? heights) 0 ;; shouldn't + true (apply min heights)) + gradient (- highest lowest)] + (merge cell {:gradient gradient}))) | |||||||||||||||||||||
Set the | (defn tag-gradients + [world] + (map-world world tag-gradient)) | |||||||||||||||||||||
Set the altitude of this cell from the corresponding pixel of this heightmap. + If the heightmap you supply is smaller than the world, this will break. + +
| (defn tag-altitude + ([world cell heightmap] + (tag-property cell :altitude heightmap)) + ([cell heightmap] + (tag-property cell :altitude heightmap))) | |||||||||||||||||||||
Apply the image file loaded from this path to this world, and return a world whose
+ altitudes are modified (added to) by the altitudes in the heightmap. It is assumed that
+ the heightmap is at least as large in x and y dimensions as the world. Note that, in
+ addition to setting the
| (defn apply-heightmap + ([world imagepath] + (let [heightmap (imagez/filter-image + (filters/grayscale) + (collage/load-image imagepath))] + (map-world + (map-world world tag-altitude (list heightmap)) + tag-gradient))) + ([imagepath] + (let [heightmap (imagez/filter-image + (filters/grayscale) + (collage/load-image imagepath)) + world (make-world (.getWidth heightmap) (.getHeight heightmap))] + (map-world + (map-world world tag-altitude (list heightmap)) + tag-gradient)))) | |||||||||||||||||||||
Generalised from apply-heightmap, set an arbitrary property on each cell
+ of this
| (defn apply-valuemap + [world imagepath property] + (let [heightmap (imagez/filter-image + (filters/grayscale) + (collage/load-image imagepath))] + (map-world world tag-property (list property heightmap)))) | |||||||||||||||||||||
A set of MicroWorld rules describing a simplified natural ecosystem. + | (ns ^{:doc + :author "Simon Brooke"} + mw-engine.natural-rules + (:require [mw-engine.utils :refer :all] + [mw-engine.world :refer :all])) | |||||||||||||||||||||
mw-engine: the state/transition engine of MicroWorld. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA. + +Copyright (C) 2014 Simon Brooke + +Since the completion of the rule language this is more or less obsolete - +there are still a few things that you can do with rules written in Clojure +that you can't do in the rule language, but not many and I doubt they're +important. + | ||||||||||||||||||||||
treeline at arbitrary altitude. + | (def treeline 150) | |||||||||||||||||||||
waterline also at arbitrary altitude. + | (def waterline 10) | |||||||||||||||||||||
and finally snowline is also arbitrary. + | (def snowline 200) | |||||||||||||||||||||
Rare chance of lightning strikes + | (def lightning-probability 500) | |||||||||||||||||||||
rules describing vegetation + | (def vegetation-rules + (list + ;; Randomly, birds plant tree seeds into grassland. + (fn [cell world] (cond (and (= (:state cell) :grassland)(< (rand 10) 1))(merge cell {:state :heath}))) + ;; heath below the treeline grows gradually into forest, providing browsing pressure is not to high + (fn [cell world] + (cond (and + (= (:state cell) :heath) + ;; browsing limit really ought to vary with soil fertility, but... + (< (+ (get-int cell :deer)(get-int cell :sheep)) 6) + (< (get-int cell :altitude) treeline)) + (merge cell {:state :scrub}))) + (fn [cell world] (cond (= (:state cell) :scrub) (merge cell {:state :forest}))) + ;; Forest on fertile land grows to climax + (fn [cell world] + (cond + (and + (= (:state cell) :forest) + (> (get-int cell :fertility) 10)) + (merge cell {:state :climax}))) + ;; Climax forest occasionally catches fire (e.g. lightning strikes) + (fn [cell world] (cond (and (= (:state cell) :climax)(< (rand lightning-probability) 1)) (merge cell {:state :fire}))) + ;; Climax forest neighbouring fires is likely to catch fire + (fn [cell world] + (cond + (and (= (:state cell) :climax) + (< (rand 3) 1) + (not (empty? (get-neighbours-with-state world (:x cell) (:y cell) 1 :fire)))) + (merge cell {:state :fire}))) + ;; After fire we get waste + (fn [cell world] (cond (= (:state cell) :fire) (merge cell {:state :waste}))) + ;; And after waste we get pioneer species; if there's a woodland seed + ;; source, it's going to be heath, otherwise grassland. + (fn [cell world] + (cond + (and (= (:state cell) :waste) + (not + (empty? + (flatten + (list + (get-neighbours-with-state world (:x cell) (:y cell) 1 :scrub) + (get-neighbours-with-state world (:x cell) (:y cell) 1 :forest) + (get-neighbours-with-state world (:x cell) (:y cell) 1 :climax)))))) + (merge cell {:state :heath}))) + (fn [cell world] + (cond (= (:state cell) :waste) + (merge cell {:state :grassland}))) + ;; Forest increases soil fertility + (fn [cell world] + (cond (member? (:state cell) '(:forest :climax)) + (merge cell {:fertility (+ (get-int cell :fertility) 1)}))))) | |||||||||||||||||||||
rules describing herbivore behaviour + | (def herbivore-rules + (list + ;; if there are too many deer for the fertility of the area to sustain, + ;; some die or move on. + (fn [cell world] + (cond (> (get-int cell :deer) (get-int cell :fertility)) + (merge cell {:deer (get-int cell :fertility)}))) + ;; deer arrive occasionally at the edge of the map. + (fn [cell world] + (cond (and (< (count (get-neighbours world cell)) 8) + (< (rand 50) 1) + (> (get-int cell :fertility) 0) + (= (get-int cell :deer) 0)) + (merge cell {:deer 2}))) + ;; deer gradually spread through the world by breeding or migrating. + (fn [cell world] + (let [n (apply + (map #(get-int % :deer) (get-neighbours world cell)))] + (cond (and + (> (get-int cell :fertility) 0) + (= (get-int cell :deer) 0) + (>= n 2)) + (merge cell {:deer (int (/ n 2))})))) + ;; deer breed. + (fn [cell world] + (cond + (>= (get-int cell :deer) 2) + (merge cell {:deer (int (* (:deer cell) 2))}))))) | |||||||||||||||||||||
rules describing predator behaviour + | (def predator-rules + (list + ;; wolves eat deer + (fn [cell world] + (cond + (>= (get-int cell :wolves) 1) + (merge cell {:deer (max 0 (- (get-int cell :deer) (get-int cell :wolves)))}))) +;; ;; not more than eight wolves in a pack, for now (hack because wolves are not dying) +;; (fn [cell world] +;; (cond (> (get-int cell :wolves) 8) (merge cell {:wolves 8}))) + ;; if there are not enough deer to sustain the get-int of wolves, + ;; some wolves die or move on. (doesn't seem to be working?) + (fn [cell world] + (cond (> (get-int cell :wolves) (get-int cell :deer)) + (merge cell {:wolves 0}))) + ;; wolves arrive occasionally at the edge of the map. + (fn [cell world] + (cond (and (< (count (get-neighbours world cell)) 8) + (< (rand 50) 1) + (not (= (:state cell) :water)) + (= (get-int cell :wolves) 0)) + (merge cell {:wolves 2}))) + ;; wolves gradually spread through the world by breeding or migrating. + (fn [cell world] + (let [n (apply + (map #(get-int % :wolves) (get-neighbours world cell)))] + (cond (and + (not (= (:state cell) :water)) + (= (get-int cell :wolves) 0) + (>= n 2)) + (merge cell {:wolves 2})))) + ;; wolves breed. + (fn [cell world] + (cond + (>= (get-int cell :wolves) 2) + (merge cell {:wolves (int (* (:wolves cell) 2))}))))) | |||||||||||||||||||||
rules which initialise the world + | (def init-rules + (list + ;; below the waterline, we have water. + (fn [cell world] + (cond (and (= (:state cell) :new) (< (get-int cell :altitude) waterline)) (merge cell {:state :water}))) + ;; above the snowline, we have snow. + (fn [cell world] + (cond (and (= (:state cell) :new) (> (get-int cell :altitude) snowline)) (merge cell {:state :snow}))) + ;; in between, we have a wasteland. + (fn [cell world] (cond (= (:state cell) :new) (merge cell {:state :grassland}))))) | |||||||||||||||||||||
+ | (def natural-rules (flatten + (list + vegetation-rules + herbivore-rules + predator-rules))) | |||||||||||||||||||||
Utility functions needed by MicroWorld and, specifically, in the + interpretation of MicroWorld rule. + | (ns ^{:doc + :author "Simon Brooke"} + mw-engine.utils + (:require + [clojure.math.combinatorics :as combo])) | |||||||||||||||||||||
mw-engine: the state/transition engine of MicroWorld. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA. + +Copyright (C) 2014 Simon Brooke + | ||||||||||||||||||||||
Surprisingly, Clojure doesn't seem to have an abs function, or else I've + missed it. So here's one of my own. Maps natural numbers onto themselves, + and negative integers onto natural numbers. Also maps negative real numbers + onto positive real numbers. + +
| (defn abs + [n] + (if (neg? n) (- 0 n) n)) | |||||||||||||||||||||
True if elt is a member of col. + | (defn member? + [elt col] (some #(= elt %) col)) | |||||||||||||||||||||
Return the value of this | (defn get-int-or-zero + [map property] + (let [value (map property)] + (if (integer? value) value 0))) | |||||||||||||||||||||
Return a cell like this | (defn init-generation + [world cell] + (merge cell {:generation (get-int-or-zero cell :generation)})) | |||||||||||||||||||||
True if x, y are in bounds for this world (i.e., there is a cell at x, y) + else false. + +
| (defn in-bounds + [world x y] + (and (>= x 0)(>= y 0)(< y (count world))(< x (count (first world))))) | |||||||||||||||||||||
Wholly non-parallel map world implementation; see documentation for | (defn map-world-n-n + ([world function] + (map-world-n-n world function nil)) + ([world function additional-args] + (into [] + (map (fn [row] + (into [] (map + #(apply function + (cons world (cons % additional-args))) + row))) + world)))) | |||||||||||||||||||||
Wholly parallel map-world implementation; see documentation for | (defn map-world-p-p + ([world function] + (map-world-p-p world function nil)) + ([world function additional-args] + (into [] + (pmap (fn [row] + (into [] (pmap + #(apply function + (cons world (cons % additional-args))) + row))) + world)))) | |||||||||||||||||||||
Apply this | (defn map-world + ([world function] + (map-world world function nil)) + ([world function additional-args] + (into [] + (pmap (fn [row] + (into [] (map + #(apply function + (cons world (cons % additional-args))) + row))) + world)))) | |||||||||||||||||||||
Return the cell a x, y in this world, if any. + +
| (defn get-cell + [world x y] + (cond (in-bounds world x y) + (nth (nth world y) x))) | |||||||||||||||||||||
Get the value of a property expected to be an integer from a map; if not present (or not an integer) return 0. + +
| (defn get-int + [map key] + (cond (map? map) + (let [v (map key)] + (cond (and v (integer? v)) v + true 0)) + true (throw (Exception. "No map passed?")))) | |||||||||||||||||||||
Return the population of this species in this cell. Currently a synonym for
+
| (defn population + [cell species] + (get-int cell species)) | |||||||||||||||||||||
Memoised get neighbours is more efficient when running deeply recursive + algorithms on the same world. But it's less efficient when running the + engine in its normal iterative style, because then we will rarely call + get naighbours on the same cell of the same world twice. + | (def memo-get-neighbours + (memoize + (fn [world x y depth] + (remove nil? + (map #(get-cell world (first %) (first (rest %))) + (remove #(= % (list x y)) + (combo/cartesian-product + (range (- x depth) (+ x depth 1)) + (range (- y depth) (+ y depth 1))))))))) | |||||||||||||||||||||
Get the neighbours to distance depth of a cell in this world. + +
+ | (defn get-neighbours + ([world x y depth] + (remove nil? + (map #(get-cell world (first %) (first (rest %))) + (remove #(= % (list x y)) + (combo/cartesian-product + (range (- x depth) (+ x depth 1)) + (range (- y depth) (+ y depth 1))))))) + ([world cell depth] + (memo-get-neighbours world (:x cell) (:y cell) depth)) + ([world cell] + (get-neighbours world cell 1))) | |||||||||||||||||||||
Get the neighbours to distance depth of the cell at x, y in this world which + have this value for this property. + +
+
+It gets messy. + | (defn get-neighbours-with-property-value + ([world x y depth property value op] + (filter + #(eval + (list op + (or (get % property) (get-int % property)) + value)) + (get-neighbours world x y depth))) + ([world x y depth property value] + (get-neighbours-with-property-value world x y depth property value =)) + ([world cell depth property value] + (get-neighbours-with-property-value world (:x cell) (:y cell) depth + property value)) + ([world cell property value] + (get-neighbours-with-property-value world cell 1 + property value))) | |||||||||||||||||||||
Get the neighbours to distance depth of the cell at x, y in this world which + have this state. + +
+ | (defn get-neighbours-with-state + ([world x y depth state] + (filter #(= (:state %) state) (get-neighbours world x y depth))) + ([world cell depth state] + (get-neighbours-with-state world (:x cell) (:y cell) depth state)) + ([world cell state] + (get-neighbours-with-state world cell 1 state))) | |||||||||||||||||||||
Return the cell from among these | (defn get-least-cell + ([cells property default] + (cond + (empty? cells) nil + true (let [downstream (get-least-cell (rest cells) property default)] + (cond (< + (or (property (first cells)) default) + (or (property downstream) default)) (first cells) + true downstream)))) + ([cells property] + (get-least-cell cells property (Integer/MAX_VALUE)))) | |||||||||||||||||||||
If this | (defn- set-cell-property + [cell x y property value] + (cond + (and (= x (:x cell)) (= y (:y cell))) + (merge cell {property value :rule "Set by user"}) + true + cell)) | |||||||||||||||||||||
Return a world like this | (defn set-property + ([world cell property value] + (set-property world (:x cell) (:y cell) property value)) + ([world x y property value] + (apply + vector ;; we want a vector of vectors, not a list of lists, for efficiency + (map + (fn [row] + (apply + vector + (map #(set-cell-property % x y property value) + row))) + world)))) | |||||||||||||||||||||
Return a world like this | (defn merge-cell + [world cell] + (if (in-bounds world (:x cell) (:y cell)) + (map-world world + #(if + (and + (= (:x cell)(:x %2)) + (= (:y cell)(:y %2))) + (merge %2 cell) + %2)) + world)) | |||||||||||||||||||||
Functions to create and to print two dimensional cellular automata. + | (ns ^{:doc + :author "Simon Brooke"} + mw-engine.world + (:require [clojure.string :as string :only [join]] + [mw-engine.utils :refer [population]])) | |||||||||||||||||||||
mw-engine: the state/transition engine of MicroWorld. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA. + +Copyright (C) 2014 Simon Brooke + +Functions to create and to print two dimensional cellular automata. +Nothing in this namespace should determine what states are possible within +the automaton, except for the initial state, :new. + +A cell is a map containing at least values for the keys :x, :y, and :state. + +A world is a two dimensional matrix (sequence of sequences) of cells, such +that every cell's :x and :y properties reflect its place in the matrix. + | ||||||||||||||||||||||
Create a minimal default cell at x, y + +
| (defn- make-cell + [x y] + {:x x :y y :state :new}) | |||||||||||||||||||||
Make the (remaining) cells in a row at this height in a world of this width. + +
| (defn- make-world-row + [index width height] + (cond (= index width) nil + true (cons (make-cell index height) + (make-world-row (inc index) width height)))) | |||||||||||||||||||||
Make the (remaining) rows in a world of this width and height, from this + index. + +
| (defn- make-world-rows + [index width height] + (cond (= index height) nil + true (cons (apply vector (make-world-row 0 width index)) + (make-world-rows (inc index) width height)))) | |||||||||||||||||||||
Make a world width cells from east to west, and height cells from north to + south. + +
| (defn make-world + [width height] + (apply vector (make-world-rows 0 width height))) | |||||||||||||||||||||
Truncate the print name of the state of this cell to at most limit characters. + | (defn truncate-state + [cell limit] + (let [s (:state cell)] + (cond (> (count (str s)) limit) (subs s 0 limit) + true s))) | |||||||||||||||||||||
Return a formatted string summarising the current state of this cell. + | (defn format-cell + [cell] + (format "%10s(%2d/%2d)" + (truncate-state cell 10) + (population cell :deer) + (population cell :wolves))) | |||||||||||||||||||||
Format one row in the state of a world for printing. + | (defn- format-world-row + [row] + (string/join (map format-cell row))) | |||||||||||||||||||||
Print the current state of this world, and return nil. + +
| (defn print-world + [world] + (println) + (dorun + (map + #(println + (format-world-row %)) + world)) + nil) | |||||||||||||||||||||