diff --git a/.gitignore b/.gitignore index f553393..2cf4294 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,11 @@ pom.xml .lein-failures eastwood.txt + +.clj-kondo/ +.lsp/ +.project +.settings/ +.nrepl-port +.classpath + diff --git a/docs/uberdoc.html b/docs/uberdoc.html index 91fdaea..12cf7e0 100644 --- a/docs/uberdoc.html +++ b/docs/uberdoc.html @@ -382,173 +382,10 @@ color: #1A734D !important; } -mw-engine -- Marginalia

mw-engine

0.1.6-SNAPSHOT


Cellular automaton world builder.

-

dependencies

org.clojure/clojure
1.6.0
org.clojure/math.combinatorics
0.0.7
org.clojure/tools.trace
0.7.8
org.clojure/tools.namespace
0.2.4
hiccup
1.0.5
net.mikera/imagez
0.3.1
fivetonine/collage
0.2.0



(this space intentionally left almost blank)
 

Functions to transform a world and run rules.

+mw-engine -- Marginalia

mw-engine

0.1.6-SNAPSHOT


Cellular automaton world builder.

+

dependencies

org.clojure/clojure
1.8.0
org.clojure/clojurescript
1.10.764
org.clojure/math.combinatorics
0.0.7
org.clojure/tools.trace
0.7.8
org.clojure/tools.namespace
0.2.4
com.taoensso/timbre
4.10.0
fivetonine/collage
0.2.0
hiccup
1.0.5
net.mikera/imagez
0.3.1



(this space intentionally left almost blank)
 

A set of MicroWorld rules describing a simplified natural ecosystem.

(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.

+ :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 @@ -3055,136 +2890,143 @@ 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 mw-parser package, which compiles -rules expressed in a subset of English rules into suitable functions.

- -

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 world.clj.

- -

Each time the world is transformed (see transform-world, for each cell, -rules are applied in turn until one matches. Once one rule has matched no -further rules can be applied.

-

Apply a single rule to a cell. What this is about is that I want to be able, - for debugging purposes, to tag a cell with the rule text of the rule which - fired (and especially so when an exception is thrown. So a rule may be either - an ifn, or a list (ifn source-text). This function deals with despatching - on those two possibilities. world is also passed in in order to be able - to access neighbours.

-
(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))]
+

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 result source) (merge result {:rule source})
-        true result))))

Derive a cell from this cell of this world by applying these rules.

-
(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 cell of this world by applying these rules. If an - exception is thrown, cache its message on the cell and set it's state to error

-
(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 world by applying these rules to each cell.

-
(defn transform-world
-  [world rules]
-  (map-world world transform-cell (list rules)))

Consider this single argument as a map of :world and :rules; apply the rules - to transform the world, and return a map of the new, transformed :world and - these :rules. As a side effect, print the world.

-
(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.

- -
    -
  • world a world as discussed above;
  • -
  • init-rules a sequence of rules as defined above, to be run once to initialise the world;
  • -
  • rules a sequence of rules as defined above, to be run iteratively for each generation;
  • -
  • generations an (integer) number of generations.

    - -

    Return the final generation of the world.

  • -
-
(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 state, assumed to be a keyword indicating a state in the - world, into a path which should recover the corresponding image file.

-
(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 row as a Hiccup table row.

-
(defn render-world-row
-  [row]
-  (apply vector (cons :tr (map render-cell row))))

Render this world as a Hiccup table.

-
(defn render-world-table
-  [world]
-  (apply vector
-    (cons :table
-      (map render-world-row world))))
 

Experimental, probably of no interest to anyone else; attempt to + (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)))
 

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 
@@ -3352,7 +3194,116 @@ USA.

[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.

+ (flow-world (rain-world (flood-hollows (heightmap/apply-heightmap hmap)))))
 

Functions to transform a world and run rules.

+
(ns ^{:doc 
+      :author "Simon Brooke"}
+  mw-engine.core
+  (:require [clojure.core.reducers :as r]
+            [clojure.string :refer [join]]
+            [mw-engine.world :as world]
+            [mw-engine.utils :refer [get-int-or-zero map-world]]
+            [taoensso.timbre :as l]))

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 mw-parser package, which compiles +rules expressed in a subset of English rules into suitable functions.

+ +

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 world.clj.

+ +

Each time the world is transformed (see transform-world, for each cell, +rules are applied in turn until one matches. Once one rule has matched no +further rules can be applied.

+

Apply a single rule to a cell. What this is about is that I want to be able, + for debugging purposes, to tag a cell with the rule text of the rule which + fired (and especially so when an exception is thrown. So a rule may be either + an ifn, or a list (ifn source-text). This function deals with despatching + on those two possibilities. world is also passed in in order to be able + to access neighbours.

+
(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 cell of this world by applying these rules.

+
(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 cell of this world by applying these rules. If an + exception is thrown, cache its message on the cell and set it's state to error

+
(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 world by applying these rules to each cell.

+
(defn transform-world
+  [world rules]
+  (map-world world transform-cell (list rules)))

Consider this single argument as a map of :world and :rules; apply the rules + to transform the world, and return a map of the new, transformed :world and + these :rules. As a side effect, print the world.

+
(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.

+ +
    +
  • world a world as discussed above;
  • +
  • init-rules a sequence of rules as defined above, to be run once to initialise the world;
  • +
  • rules a sequence of rules as defined above, to be run iteratively for each generation;
  • +
  • generations an (integer) number of generations.

    + +

    Return the final generation of the world.

  • +
+
(defn run-world
+  [world init-rules rules generations]
+  (reduce (fn [world iteration]
+            (l/info "Running iteration " iteration)
+            (transform-world world rules))
+        (transform-world world init-rules)
+        (range generations)))
 

Functions to apply a heightmap to a world.

(ns ^{:doc 
       :author "Simon Brooke"}
   mw-engine.heightmap
@@ -3472,12 +3423,12 @@ a world the size of the heightmap will be created;
     (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.

+ (map-world world tag-property (list property heightmap))))
 

Functions to create and to print two dimensional cellular automata.

(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.

+ 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 @@ -3496,143 +3447,132 @@ 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 +

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

+ +
    +
  • x the x coordinate at which this cell is created;
  • +
  • y the y coordinate at which this cell is created.
  • +
+
(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.

+ +
    +
  • index x coordinate of the next cell to be created;
  • +
  • width total width of the matrix, in cells;
  • +
  • height y coordinate of the next cell to be created.
  • +
+
(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.

+ +
    +
  • index y coordinate of the next row to be created;
  • +
  • width total width of the matrix, in cells;
  • +
  • height total height of the matrix, in cells.
  • +
+
(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.

+ +
    +
  • width a natural number representing the width of the matrix to be created;
  • +
  • height a natural number representing the height of the matrix to be created.
  • +
+
(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.

+ +
    +
  • world a world as defined above.
  • +
+
(defn print-world
+  [world]
+  (println)
+  (dorun
+    (map
+      #(println
+         (format-world-row %))
+      world))
+  nil)
 

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

+

Base url (i.e., url of directory) from which to load tile images.

+
(def ^:dynamic *image-base*
+  "img/tiles")
+
(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 state, assumed to be a keyword indicating a state in the + world, into a path which should recover the corresponding image file.

+
(defn format-image-path
+  [state]
+  (format "%s/%s.png" *image-base* (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 row as a Hiccup table row.

+
(defn render-world-row
+  [row]
+  (apply vector (cons :tr (map render-cell row))))

Render this world as a Hiccup table.

+
(defn render-world-table
+  [world]
+  (apply vector
+    (cons :table
+      (map render-world-row world))))
 

Utility functions needed by MicroWorld and, specifically, in the interpretation of MicroWorld rule.

(ns ^{:doc 
       :author "Simon Brooke"}
@@ -3680,7 +3620,7 @@ USA.

(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.

+ else false. DEPRECATED: it's a predicate, prefer in-bounds?.

  • world a world as defined above;
  • @@ -3688,46 +3628,58 @@ USA.

  • y a number which may or may not be a valid y coordinate within that world.
(defn in-bounds
+  {:deprecated "1.1.7"}
+  [world x y]
+  (and (>= x 0)(>= y 0)(< y (count world))(< x (count (first world)))))

True if x, y are in bounds for this world (i.e., there is a cell at x, y) + else false.

+ +
    +
  • world a world as defined above;
  • +
  • x a number which may or may not be a valid x coordinate within that world;
  • +
  • y a number which may or may not be a valid y coordinate within that world.
  • +
+
(defn in-bounds?
+  {:added "1.1.7"}
   [world x y]
   (and (>= x 0)(>= y 0)(< y (count world))(< x (count (first world)))))

Wholly non-parallel map world implementation; see documentation for map-world.

(defn map-world-n-n
   ([world function]
-    (map-world-n-n world function nil))
+   (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 map-world.

+ (into [] + (map (fn [row] + (into [] (map + #(apply function + (cons world (cons % additional-args))) + row))) + world))))

Wholly parallel map-world implementation; see documentation for map-world.

(defn map-world-p-p
   ([world function]
-    (map-world-p-p world function nil))
+   (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 function to each cell in this world to produce a new world. - the arguments to the function will be the world, the cell, and any - additional-args supplied. Note that we parallel map over rows but - just map over cells within a row. That's because it isn't worth starting - a new thread for each cell, but there may be efficiency gains in - running rows in parallel.

+ (into [] + (pmap (fn [row] + (into [] (pmap + #(apply function + (cons world (cons % additional-args))) + row))) + world))))

Apply this function to each cell in this world to produce a new world. + the arguments to the function will be the world, the cell, and any + additional-args supplied. Note that we parallel map over rows but + just map over cells within a row. That's because it isn't worth starting + a new thread for each cell, but there may be efficiency gains in + running rows in parallel.

(defn map-world
   ([world function]
-    (map-world world function nil))
+   (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.

+ (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.

  • world a world as defined above;
  • @@ -3736,7 +3688,7 @@ USA.

(defn get-cell
   [world x y]
-  (cond (in-bounds world x y)
+  (when (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.

    @@ -3745,11 +3697,11 @@ USA.

(defn get-int
   [map key]
-  (cond (map? map)
+  (if (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 + (throw (Exception. "No map passed?"))))

Return the population of this species in this cell. Currently a synonym for get-int, but may not always be (depending whether species are later implemented as actors)

@@ -3894,104 +3846,81 @@ coordinates [x,y] in this world. (= (: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.

+ world))
 
\ No newline at end of file + if(nsIndex == -1) { + if(scroll >= ps[0]) { + nsIndex = ps.length - 1; + } else { + nsIndex = 0; + } + } + + return nsp.nss[nsIndex]; + } + + $(window).scroll(function(e) { + showNs(currentSection(nsPositions)); + }); +}); + \ No newline at end of file diff --git a/project.clj b/project.clj index 383341f..edd1265 100644 --- a/project.clj +++ b/project.clj @@ -1,22 +1,23 @@ (defproject mw-engine "0.1.6-SNAPSHOT" + :dependencies [[org.clojure/clojure "1.10.3"] + [org.clojure/clojurescript "1.10.896" :scope "provided"] + [org.clojure/math.combinatorics "0.1.6"] + [org.clojure/tools.trace "0.7.11"] + [org.clojure/tools.namespace "1.1.1"] + [com.taoensso/timbre "5.1.2"] + [fivetonine/collage "0.3.0"] + [hiccup "1.0.5"] + [net.mikera/imagez "0.12.0"]] :description "Cellular automaton world builder." - :url "http://www.journeyman.cc/microworld/" - :manifest { - "build-signature-version" "unset" - "build-signature-user" "unset" - "build-signature-email" "unset" - "build-signature-timestamp" "unset" - "Implementation-Version" "unset" - } :jvm-opts ["-Xmx4g"] :license {:name "GNU General Public License v2" :url "http://www.gnu.org/licenses/gpl-2.0.html"} - :plugins [[lein-marginalia "0.7.1"]] - :dependencies [[org.clojure/clojure "1.8.0"] - [org.clojure/math.combinatorics "0.0.7"] - [org.clojure/tools.trace "0.7.8"] - [org.clojure/tools.namespace "0.2.4"] - [com.taoensso/timbre "4.10.0"] - [fivetonine/collage "0.2.0"] - [hiccup "1.0.5"] - [net.mikera/imagez "0.3.1"]]) + + :min-lein-version "2.0.0" + :plugins [[lein-cljsbuild "1.1.7"] + [lein-kibit "0.1.2"] + [lein-marginalia "0.7.1"]] + :resource-paths ["resources" "target/cljsbuild"] + :source-paths ["src/clj" "src/cljs" "src/cljc"] + :url "http://www.journeyman.cc/microworld/" + ) diff --git a/src/cljc/mw_engine/core.clj b/src/cljc/mw_engine/core.clj new file mode 100644 index 0000000..866087d --- /dev/null +++ b/src/cljc/mw_engine/core.clj @@ -0,0 +1,134 @@ +(ns ^{:doc "Functions to transform a world and run rules." + :author "Simon Brooke"} + mw-engine.core + (:require [clojure.core.reducers :as r] + [clojure.string :refer [join]] + [mw-engine.world :as world] + [mw-engine.utils :refer [get-int-or-zero map-world]] + [taoensso.timbre :as l])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; 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 `mw-parser` package, which compiles +;;;; rules expressed in a subset of English rules into suitable functions. +;;;; +;;;; 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 `world.clj`. +;;;; +;;;; Each time the world is transformed (see `transform-world`, for each cell, +;;;; rules are applied in turn until one matches. Once one rule has matched no +;;;; further rules can be applied. +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn apply-rule + "Apply a single `rule` to a `cell`. What this is about is that I want to be able, + for debugging purposes, to tag a cell with the rule text of the rule which + fired (and especially so when an exception is thrown. So a rule may be either + an ifn, or a list (ifn source-text). This function deals with despatching + on those two possibilities. `world` is also passed in in order to be able + to access neighbours." + ([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)))) + + +(defn- apply-rules + "Derive a cell from this `cell` of this `world` by applying these `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)))))) + + +(defn- transform-cell + "Derive a cell from this `cell` of this `world` by applying these `rules`. If an + exception is thrown, cache its message on the cell and set it's state to error" + [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})))) + + +(defn transform-world + "Return a world derived from this `world` by applying these `rules` to each cell." + [world rules] + (map-world world transform-cell (list rules))) + + +(defn- transform-world-state + "Consider this single argument as a map of `:world` and `:rules`; apply the rules + to transform the world, and return a map of the new, transformed `:world` and + these `:rules`. As a side effect, print the world." + [state] + (let [world (transform-world (:world state) (:rules state))] + ;;(world/print-world world) + {:world world :rules (:rules state)})) + + +(defn run-world + "Run this world with these rules for this number of generations. + + * `world` a world as discussed above; + * `init-rules` a sequence of rules as defined above, to be run once to initialise the world; + * `rules` a sequence of rules as defined above, to be run iteratively for each generation; + * `generations` an (integer) number of generations. + + Return the final generation of the world." + [world init-rules rules generations] + (reduce (fn [world iteration] + (l/info "Running iteration " iteration) + (transform-world world rules)) + (transform-world world init-rules) + (range generations))) + + + diff --git a/src/mw_engine/display.clj b/src/cljc/mw_engine/display.clj similarity index 100% rename from src/mw_engine/display.clj rename to src/cljc/mw_engine/display.clj diff --git a/src/mw_engine/drainage.clj b/src/cljc/mw_engine/drainage.clj similarity index 100% rename from src/mw_engine/drainage.clj rename to src/cljc/mw_engine/drainage.clj diff --git a/src/mw_engine/heightmap.clj b/src/cljc/mw_engine/heightmap.clj similarity index 90% rename from src/mw_engine/heightmap.clj rename to src/cljc/mw_engine/heightmap.clj index cde7002..7dfe9f7 100644 --- a/src/mw_engine/heightmap.clj +++ b/src/cljc/mw_engine/heightmap.clj @@ -1,9 +1,7 @@ (ns ^{:doc "Functions to apply a heightmap to a world." :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]] + (:require [mikera.image.core :refer [load-image 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]])) @@ -67,9 +65,9 @@ [world cell] (let [heights (remove nil? (map :altitude (get-neighbours world cell))) highest (cond (empty? heights) 0 ;; shouldn't happen - true (apply max heights)) + :else (apply max heights)) lowest (cond (empty? heights) 0 ;; shouldn't - true (apply min heights)) + :else (apply min heights)) gradient (- highest lowest)] (merge cell {:gradient gradient}))) @@ -106,16 +104,16 @@ a world the size of the heightmap will be created; * `imagepath` a file path or URL which indicates an (ideally greyscale) image file." ([world imagepath] - (let [heightmap (imagez/filter-image + (let [heightmap (filter-image (filters/grayscale) - (collage/load-image imagepath))] + (load-image imagepath))] (map-world (map-world world tag-altitude (list heightmap)) tag-gradient))) ([imagepath] - (let [heightmap (imagez/filter-image + (let [heightmap (filter-image (filters/grayscale) - (collage/load-image imagepath)) + (load-image imagepath)) world (make-world (.getWidth heightmap) (.getHeight heightmap))] (map-world (map-world world tag-altitude (list heightmap)) @@ -131,7 +129,7 @@ * `property` the property of each cell whose value should be added to from the intensity of the corresponding cell of the image." [world imagepath property] - (let [heightmap (imagez/filter-image + (let [heightmap (filter-image (filters/grayscale) - (collage/load-image imagepath))] + (load-image imagepath))] (map-world world tag-property (list property heightmap)))) diff --git a/src/mw_engine/natural_rules.clj b/src/cljc/mw_engine/natural_rules.clj similarity index 100% rename from src/mw_engine/natural_rules.clj rename to src/cljc/mw_engine/natural_rules.clj diff --git a/src/mw_engine/utils.clj b/src/cljc/mw_engine/utils.clj similarity index 83% rename from src/mw_engine/utils.clj rename to src/cljc/mw_engine/utils.clj index 53c359f..68e37ed 100644 --- a/src/mw_engine/utils.clj +++ b/src/cljc/mw_engine/utils.clj @@ -62,61 +62,72 @@ (defn in-bounds + "True if x, y are in bounds for this world (i.e., there is a cell at x, y) + else false. *DEPRECATED*: it's a predicate, prefer `in-bounds?`. + + * `world` a world as defined above; + * `x` a number which may or may not be a valid x coordinate within that world; + * `y` a number which may or may not be a valid y coordinate within that world." + {:deprecated "1.1.7"} + [world x y] + (and (>= x 0)(>= y 0)(< y (count world))(< x (count (first world))))) + +(defn in-bounds? "True if x, y are in bounds for this world (i.e., there is a cell at x, y) else false. * `world` a world as defined above; * `x` a number which may or may not be a valid x coordinate within that world; * `y` a number which may or may not be a valid y coordinate within that world." + {:added "1.1.7"} [world x y] (and (>= x 0)(>= y 0)(< y (count world))(< x (count (first world))))) - (defn map-world-n-n "Wholly non-parallel map world implementation; see documentation for `map-world`." ([world function] - (map-world-n-n world function nil)) + (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)))) + (into [] + (map (fn [row] + (into [] (map + #(apply function + (cons world (cons % additional-args))) + row))) + world)))) (defn map-world-p-p "Wholly parallel map-world implementation; see documentation for `map-world`." ([world function] - (map-world-p-p world function nil)) + (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)))) + (into [] + (pmap (fn [row] + (into [] (pmap + #(apply function + (cons world (cons % additional-args))) + row))) + world)))) (defn map-world "Apply this `function` to each cell in this `world` to produce a new world. - the arguments to the function will be the world, the cell, and any - `additional-args` supplied. Note that we parallel map over rows but - just map over cells within a row. That's because it isn't worth starting - a new thread for each cell, but there may be efficiency gains in - running rows in parallel." + the arguments to the function will be the world, the cell, and any + `additional-args` supplied. Note that we parallel map over rows but + just map over cells within a row. That's because it isn't worth starting + a new thread for each cell, but there may be efficiency gains in + running rows in parallel." ([world function] - (map-world world function nil)) + (map-world world function nil)) ([world function additional-args] - (into [] - (pmap (fn [row] - (into [] (map - #(apply function - (cons world (cons % additional-args))) - row))) - world)))) + (into [] + (pmap (fn [row] + (into [] (map + #(apply function + (cons world (cons % additional-args))) + row))) + world)))) (defn get-cell @@ -126,21 +137,21 @@ * `x` a number which may or may not be a valid x coordinate within that world; * `y` a number which may or may not be a valid y coordinate within that world." [world x y] - (cond (in-bounds world x y) + (when (in-bounds world x y) (nth (nth world y) x))) (defn get-int "Get the value of a property expected to be an integer from a map; if not present (or not an integer) return 0. - * `map` a map; - * `key` a symbol or keyword, presumed to be a key into the `map`." + * `map` a map; + * `key` a symbol or keyword, presumed to be a key into the `map`." [map key] - (cond (map? map) + (if (map? map) (let [v (map key)] (cond (and v (integer? v)) v true 0)) - true (throw (Exception. "No map passed?")))) + (throw (Exception. "No map passed?")))) (defn population @@ -256,11 +267,11 @@ ([cells property default] (cond (empty? cells) nil - true (let [downstream (get-least-cell (rest cells) property default)] + :else (let [downstream (get-least-cell (rest cells) property default)] (cond (< (or (property (first cells)) default) (or (property downstream) default)) (first cells) - true downstream)))) + :else downstream)))) ([cells property] (get-least-cell cells property (Integer/MAX_VALUE)))) @@ -273,8 +284,7 @@ (cond (and (= x (:x cell)) (= y (:y cell))) (merge cell {property value :rule "Set by user"}) - true - cell)) + :else cell)) (defn set-property diff --git a/src/mw_engine/world.clj b/src/cljc/mw_engine/world.clj similarity index 97% rename from src/mw_engine/world.clj rename to src/cljc/mw_engine/world.clj index 9001ed6..71387de 100644 --- a/src/mw_engine/world.clj +++ b/src/cljc/mw_engine/world.clj @@ -1,8 +1,8 @@ (ns ^{:doc "Functions to create and to print two dimensional cellular automata." :author "Simon Brooke"} mw-engine.world - (:require [clojure.string :as string :only [join]] - [mw-engine.utils :refer [population]])) + (:require [clojure.string :as string] + [mw-engine.utils :refer [population]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;