From 21cdff764fb8f2db36d08d3265714a80458cd3d0 Mon Sep 17 00:00:00 2001 From: simon Date: Sat, 13 Aug 2016 17:39:07 +0100 Subject: [PATCH 01/29] Added namespace documentation conforming to better practice; added GPL declaration; changed 'use' to 'require' passim. All tests pass but that's not proof we're all good yet. --- src/mw_engine/core.clj | 43 +++++++++++++++---- src/mw_engine/display.clj | 34 +++++++++++++-- src/mw_engine/drainage.clj | 74 +++++++++++++++++++++++---------- src/mw_engine/heightmap.clj | 44 ++++++++++++++++---- src/mw_engine/natural_rules.clj | 51 +++++++++++++++++------ src/mw_engine/utils.clj | 46 ++++++++++++++++---- src/mw_engine/version.clj | 10 +++-- src/mw_engine/world.clj | 36 ++++++++++++++-- 8 files changed, 267 insertions(+), 71 deletions(-) diff --git a/src/mw_engine/core.clj b/src/mw_engine/core.clj index 9b95b55..d471e08 100644 --- a/src/mw_engine/core.clj +++ b/src/mw_engine/core.clj @@ -1,11 +1,30 @@ -;; Functions to transform a world and run rules. - -(ns mw-engine.core - (:use mw-engine.utils) +(ns ^{:doc "Functions to transform a world and run rules." + :author "Simon Brooke"} + mw-engine.core (:require [clojure.core.reducers :as r] - [mw-engine.world :as world]) + [mw-engine.world :as world] + [mw-engine.utils :refer [get-int-or-zero map-world]]) (:gen-class)) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; 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. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ;; 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. @@ -26,7 +45,8 @@ ;; 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, @@ -37,14 +57,15 @@ to access neighbours." ([world cell rule] (cond - (ifn? rule) (apply-rule cell world rule nil) - (seq? rule) (let [[afn src] rule] (apply-rule cell world afn src)))) - ([cell world rule source] + (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] @@ -53,6 +74,7 @@ (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" @@ -67,13 +89,16 @@ (.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 diff --git a/src/mw_engine/display.clj b/src/mw_engine/display.clj index 0ad65e6..9c1f1c6 100644 --- a/src/mw_engine/display.clj +++ b/src/mw_engine/display.clj @@ -1,22 +1,46 @@ -(ns mw-engine.display - (:use mw-engine.utils - mw-engine.world) - (:require [hiccup.core :refer [html]])) +(ns ^{:doc "Simple functions to allow a world to be visualised." + :author "Simon Brooke"} + mw-engine.display + (:require [hiccup.core :refer [html]] + mw-engine.utils + mw-engine.world)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; 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. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (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)) + (defn format-image-path "Render this `state`, assumed to be a keyword indicating a state in the world, into a path which should recover the corresponding image file." [state] (format "img/tiles/%s.png" (format-css-class state))) + (defn format-mouseover [cell] (str cell)) + (defn render-cell "Render this world cell as a Hiccup table cell." [cell] @@ -25,11 +49,13 @@ [: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)}]]])) + (defn render-world-row "Render this world `row` as a Hiccup table row." [row] (apply vector (cons :tr (map render-cell row)))) + (defn render-world-table "Render this `world` as a Hiccup table." [world] diff --git a/src/mw_engine/drainage.clj b/src/mw_engine/drainage.clj index 925b1a0..4e844c4 100644 --- a/src/mw_engine/drainage.clj +++ b/src/mw_engine/drainage.clj @@ -1,11 +1,33 @@ -;; Experimental, probably of no interest to anyone else; attempt to compute drainage on a world, -;; assumed to have altitudes already set from a heighmap. +(ns ^{:doc "Experimental, probably of no interest to anyone else; attempt to + compute drainage on a world, assumed to have altitudes already set + from a heightmap." + :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]])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; 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. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(ns mw-engine.drainage - (:use mw-engine.utils - mw-engine.world - mw-engine.core) - (:require [mw-engine.heightmap :as heightmap])) (def ^:dynamic *sealevel* 10) @@ -18,37 +40,40 @@ [world] (map-world world (fn [world cell] (merge cell {:rainfall 1})))) + (defn flow-contributors "Return a list of the cells in this `world` which are higher than this - `cell` and for which this cell is the lowest neighbour, or which are at the + `cell` and for which this cell is the lowest neighbour, or which are at the same altitude and have greater flow" [cell world] (filter #(map? %) (map (fn [n] - (cond + (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 + (get-neighbours-with-property-value world (:x cell) (:y cell) 1 :altitude (or (:altitude cell) 0) >=)))) + (defn is-hollow - "Detects point hollows - that is, individual cells all of whose neighbours - are higher. Return true if this `cell` has an altitude lower than any of - its neighbours in this `world`" + "Detects point hollows - that is, individual cells all of whose neighbours + are higher. Return true if this `cell` has an altitude lower than any of + its neighbours in this `world`" [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 + (count (get-neighbours-with-property-value world (:x cell) (:y cell) 1 :altitude altitude >))))) + (defn flood-hollow - "Raise the altitude of a copy of this `cell` of this `world` to the altitude + "Raise the altitude of a copy of this `cell` of this `world` to the altitude of the lowest of its `neighbours`." ([world cell neighbours] (let [lowest (get-least-cell neighbours :altitude)] @@ -56,30 +81,33 @@ ([world cell] (flood-hollow world cell (get-neighbours world cell)))) -(defn flood-hollows + +(defn flood-hollows "Flood all local hollows in this `world`. At this stage only floods single cell hollows." [world] - (map-world world + (map-world world #(if (is-hollow %1 %2) (flood-hollow %1 %2) %2))) + (def max-altitude 255) (defn flow-nr - "Experimental non recursive flow algorithm, needs to be run on a world as + "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 `flow-world-nr`." [cell world] (if (= (- max-altitude (get-int-or-zero cell :generation)) (get-int-or-zero cell :altitude)) - (merge cell - {:flow (reduce + - (map + (merge cell + {:flow (reduce + + (map #(+ (get-int-or-zero % :rainfall) (get-int-or-zero % :flow)) (flow-contributors cell world)))}))) + (def flow "Compute the total flow upstream of this `cell` in this `world`, and return a cell identical to this one but having a value of its flow property set from that computation. The function is @@ -99,6 +127,7 @@ (map (fn [neighbour] (:flow (flow neighbour world))) (flow-contributors cell world))))}))))) + (defn flow-world-nr "Experimental non-recursive flow-world algorithm" [world] @@ -110,8 +139,9 @@ [world] (map-world (rain-world world) flow)) + (defn run-drainage [hmap] - "Create a world from the heightmap `hmap`, rain on it, and then compute river + "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))))) diff --git a/src/mw_engine/heightmap.clj b/src/mw_engine/heightmap.clj index 403cad0..d6009cb 100644 --- a/src/mw_engine/heightmap.clj +++ b/src/mw_engine/heightmap.clj @@ -1,15 +1,36 @@ -;; Functions to apply a heightmap to a world. +(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]] + [mikera.image.filters :as filters] + [mw-engine.utils :refer [abs get-int get-neighbours map-world]] + [mw-engine.world :refer [make-world]])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; 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. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Heightmaps are considered only as greyscale images, so colour is redundent (will be ;; ignored). Darker shades are higher. - -(ns mw-engine.heightmap - (:import [java.awt.image BufferedImage]) - (:use mw-engine.utils - mw-engine.world) - (:require [fivetonine.collage.util :as collage :only [load-image]] - [mikera.image.core :as imagez :only [filter-image get-pixels]] - [mikera.image.filters :as filters])) +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn tag-property @@ -35,6 +56,7 @@ (get-int cell :x) (get-int cell :y)) 256))))}))) + (defn tag-gradient "Set the `gradient` property of this `cell` of this `world` to the difference in altitude between its highest and lowest neghbours." @@ -47,12 +69,14 @@ gradient (- highest lowest)] (merge cell {:gradient gradient}))) + (defn tag-gradients "Set the `gradient` property of each cell in this `world` to the difference in altitude between its highest and lowest neghbours." [world] (map-world world tag-gradient)) + (defn tag-altitude "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. @@ -67,6 +91,7 @@ ([cell heightmap] (tag-property cell :altitude heightmap))) + (defn apply-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 @@ -92,6 +117,7 @@ (map-world world tag-altitude (list heightmap)) tag-gradient)))) + (defn apply-valuemap "Generalised from apply-heightmap, set an arbitrary property on each cell of this `world` from the values in this (ideally greyscale) heightmap. diff --git a/src/mw_engine/natural_rules.clj b/src/mw_engine/natural_rules.clj index 6032ca3..fd5f987 100644 --- a/src/mw_engine/natural_rules.clj +++ b/src/mw_engine/natural_rules.clj @@ -1,26 +1,52 @@ -;; A set of MicroWorld rules describing a simplified natural ecosystem. +(ns ^{:doc "A set of MicroWorld rules describing a simplified natural ecosystem." + :author "Simon Brooke"} + mw-engine.natural-rules + (:require mw-engine.utils + mw-engine.world)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; Since the completion of the rule language this is more or less obsolete - +;; 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. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; 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. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(ns mw-engine.natural-rules - (:use mw-engine.utils - mw-engine.world)) ;; 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 @@ -72,8 +98,8 @@ ;; Forest increases soil fertility (fn [cell world] (cond (member? (:state cell) '(:forest :climax)) - (merge cell {:fertility (+ (get-int cell :fertility) 1)}))) - )) + (merge cell {:fertility (+ (get-int cell :fertility) 1)}))))) + ;; rules describing herbivore behaviour (def herbivore-rules @@ -139,8 +165,8 @@ (fn [cell world] (cond (>= (get-int cell :wolves) 2) - (merge cell {:wolves (int (* (:wolves cell) 2))}))) - )) + (merge cell {:wolves (int (* (:wolves cell) 2))}))))) + ;; rules which initialise the world (def init-rules @@ -152,12 +178,11 @@ (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})) - ))) + (fn [cell world] (cond (= (:state cell) :new) (merge cell {:state :grassland}))))) + (def natural-rules (flatten (list vegetation-rules herbivore-rules - ;; predator-rules - ))) + predator-rules))) diff --git a/src/mw_engine/utils.clj b/src/mw_engine/utils.clj index 279ec18..fca4e3b 100644 --- a/src/mw_engine/utils.clj +++ b/src/mw_engine/utils.clj @@ -1,11 +1,29 @@ -;; Utility functions needed by MicroWorld and, specifically, in the -;; interpretation of MicroWorld rule. - -(ns mw-engine.utils +(ns ^{:doc " Utility functions needed by MicroWorld and, specifically, in the + interpretation of MicroWorld rule." + :author "Simon Brooke"} + mw-engine.utils (:require -;; [clojure.core.reducers :as r] [clojure.math.combinatorics :as combo])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; 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. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn abs "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, @@ -16,10 +34,12 @@ [n] (if (neg? n) (- 0 n) n)) + (defn member? "True if elt is a member of col." [elt col] (some #(= elt %) col)) + (defn get-int-or-zero "Return the value of this `property` from this `map` if it is a integer; otherwise return zero." @@ -27,6 +47,7 @@ (let [value (map property)] (if (integer? value) value 0))) + (defn init-generation "Return a cell like this `cell`, but having a value for :generation, zero if the cell passed had no integer value for generation, otherwise the value @@ -46,8 +67,9 @@ [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" + "Wholly non-parallel map world implementation; see documentation for `map-world`." ([world function] (map-world-n-n world function nil)) ([world function additional-args] @@ -59,8 +81,9 @@ row))) world)))) + (defn map-world-p-p - "Wholly parallel map world implementation" + "Wholly parallel map-world implementation; see documentation for `map-world`." ([world function] (map-world-p-p world function nil)) ([world function additional-args] @@ -91,6 +114,7 @@ row))) world)))) + (defn get-cell "Return the cell a x, y in this world, if any. @@ -101,6 +125,7 @@ (cond (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. @@ -113,6 +138,7 @@ true 0)) true (throw (Exception. "No map passed?")))) + (defn population "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 @@ -123,6 +149,7 @@ [cell species] (get-int cell species)) + (def memo-get-neighbours "Memoised get neighbours is more efficient when running deeply recursive algorithms on the same world. But it's less efficient when running the @@ -137,6 +164,7 @@ (range (- x depth) (+ x depth 1)) (range (- y depth) (+ y depth 1))))))))) + (defn get-neighbours "Get the neighbours to distance depth of a cell in this world. @@ -170,6 +198,7 @@ ([world cell] (get-neighbours world cell 1))) + (defn get-neighbours-with-property-value "Get the neighbours to distance depth of the cell at x, y in this world which have this value for this property. @@ -215,6 +244,7 @@ ([world cell state] (get-neighbours-with-state world cell 1 state))) + (defn get-least-cell "Return the cell from among these `cells` which has the lowest numeric value for this `property`; if the property is absent or not a number, use this @@ -242,6 +272,7 @@ true cell)) + (defn set-property "Return a world like this `world` but with the value of exactly one `property` of one `cell` changed to this `value`" @@ -258,6 +289,7 @@ row))) world)))) + (defn merge-cell "Return a world like this `world`, but merge the values from this `cell` with those from the cell in the world with the same co-ordinates" diff --git a/src/mw_engine/version.clj b/src/mw_engine/version.clj index d3fa41d..297456d 100644 --- a/src/mw_engine/version.clj +++ b/src/mw_engine/version.clj @@ -1,13 +1,15 @@ -(ns mw-engine.version +(ns ^{:doc "package documentation." + :author "Simon Brooke"} + mw-engine.version (:gen-class)) -(defn get-implementation-version +(defn get-implementation-version "Get the implementation version from the package of this namespace, which must - be compiled into a class (see clojure.java.interop). See + be compiled into a class (see clojure.java.interop). See http://stackoverflow.com/questions/12599889/how-to-get-runtime-access-to-version-number-of-a-running-clojure-application TODO: doesn't work yet." [] - (try + (try (.getImplementationVersion (.getPackage (eval 'mw-engine.version))) (catch Exception any "Unknown") )) diff --git a/src/mw_engine/world.clj b/src/mw_engine/world.clj index 2aa515f..a10289f 100644 --- a/src/mw_engine/world.clj +++ b/src/mw_engine/world.clj @@ -1,3 +1,28 @@ +(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]])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; 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. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ;; Functions to create and to print two dimensional cellular automata. Nothing in this ;; file should determine what states are possible within the automaton, except for the ;; initial state, :new. @@ -6,10 +31,9 @@ ;; ;; 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. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(ns mw-engine.world - (:use mw-engine.utils) - (:require [clojure.string :as string :only [join]])) (defn- make-cell "Create a minimal default cell at x, y @@ -19,6 +43,7 @@ [x y] {:x x :y y :state :new}) + (defn- make-world-row "Make the (remaining) cells in a row at this height in a world of this width. @@ -30,6 +55,7 @@ true (cons (make-cell index height) (make-world-row (inc index) width height)))) + (defn- make-world-rows "Make the (remaining) rows in a world of this width and height, from this index. @@ -51,6 +77,7 @@ [width height] (apply vector (make-world-rows 0 width height))) + (defn truncate-state "Truncate the print name of the state of this cell to at most limit characters." [cell limit] @@ -58,6 +85,7 @@ (cond (> (count (str s)) limit) (subs s 0 limit) true s))) + (defn format-cell "Return a formatted string summarising the current state of this cell." [cell] @@ -66,11 +94,13 @@ (population cell :deer) (population cell :wolves))) + (defn- format-world-row "Format one row in the state of a world for printing." [row] (string/join (map format-cell row))) + (defn print-world "Print the current state of this world, and return nil. From 944b54fc890d5a3ceeb6de7fe054eb62649a1028 Mon Sep 17 00:00:00 2001 From: simon Date: Sat, 13 Aug 2016 17:41:53 +0100 Subject: [PATCH 02/29] Deleted version.clj, which did not work anyway. --- src/mw_engine/version.clj | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/mw_engine/version.clj diff --git a/src/mw_engine/version.clj b/src/mw_engine/version.clj deleted file mode 100644 index 297456d..0000000 --- a/src/mw_engine/version.clj +++ /dev/null @@ -1,18 +0,0 @@ -(ns ^{:doc "package documentation." - :author "Simon Brooke"} - mw-engine.version - (:gen-class)) - -(defn get-implementation-version - "Get the implementation version from the package of this namespace, which must - be compiled into a class (see clojure.java.interop). See - http://stackoverflow.com/questions/12599889/how-to-get-runtime-access-to-version-number-of-a-running-clojure-application - TODO: doesn't work yet." - [] - (try - (.getImplementationVersion (.getPackage (eval 'mw-engine.version))) - (catch Exception any "Unknown") - )) - -(defn -main [] - (get-implementation-version )) From f1b35dc9487c3d8da7d4f2a48fd2a0ddce3ec18c Mon Sep 17 00:00:00 2001 From: simon Date: Sun, 21 Aug 2016 14:17:30 +0100 Subject: [PATCH 03/29] Standardised header documentation in line with current best practice. --- src/mw_engine/core.clj | 80 +++++++++++++++++---------------- src/mw_engine/display.clj | 36 ++++++++------- src/mw_engine/drainage.clj | 36 ++++++++------- src/mw_engine/heightmap.clj | 44 +++++++++--------- src/mw_engine/natural_rules.clj | 48 +++++++++++--------- src/mw_engine/utils.clj | 36 ++++++++------- src/mw_engine/world.clj | 56 ++++++++++++----------- 7 files changed, 182 insertions(+), 154 deletions(-) diff --git a/src/mw_engine/core.clj b/src/mw_engine/core.clj index d471e08..83c1a2e 100644 --- a/src/mw_engine/core.clj +++ b/src/mw_engine/core.clj @@ -7,45 +7,49 @@ (:gen-class)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; 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. -;; +;;;; +;;;; 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. -;; +;;;; +;;;; 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 diff --git a/src/mw_engine/display.clj b/src/mw_engine/display.clj index 9c1f1c6..7dca8ff 100644 --- a/src/mw_engine/display.clj +++ b/src/mw_engine/display.clj @@ -6,22 +6,26 @@ mw-engine.world)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; 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. -;; +;;;; +;;;; 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] diff --git a/src/mw_engine/drainage.clj b/src/mw_engine/drainage.clj index 4e844c4..87a7207 100644 --- a/src/mw_engine/drainage.clj +++ b/src/mw_engine/drainage.clj @@ -10,22 +10,26 @@ map-world]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; 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. -;; +;;;; +;;;; 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 +;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/mw_engine/heightmap.clj b/src/mw_engine/heightmap.clj index d6009cb..cde7002 100644 --- a/src/mw_engine/heightmap.clj +++ b/src/mw_engine/heightmap.clj @@ -9,27 +9,31 @@ [mw-engine.world :refer [make-world]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; 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. -;; +;;;; +;;;; 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. -;; +;;;; +;;;; Heightmaps are considered only as greyscale images, so colour is redundent +;;;; (will be ignored). Darker shades are higher. +;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/mw_engine/natural_rules.clj b/src/mw_engine/natural_rules.clj index fd5f987..af4a124 100644 --- a/src/mw_engine/natural_rules.clj +++ b/src/mw_engine/natural_rules.clj @@ -5,29 +5,33 @@ mw-engine.world)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; 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. -;; +;;;; +;;;; 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. -;; +;;;; +;;;; 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. +;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/mw_engine/utils.clj b/src/mw_engine/utils.clj index fca4e3b..53c359f 100644 --- a/src/mw_engine/utils.clj +++ b/src/mw_engine/utils.clj @@ -6,22 +6,26 @@ [clojure.math.combinatorics :as combo])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; 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. -;; +;;;; +;;;; 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 abs diff --git a/src/mw_engine/world.clj b/src/mw_engine/world.clj index a10289f..9001ed6 100644 --- a/src/mw_engine/world.clj +++ b/src/mw_engine/world.clj @@ -5,33 +5,37 @@ [mw-engine.utils :refer [population]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; 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. -;; +;;;; +;;;; 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 -;; file 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. -;; +;;;; +;;;; 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. +;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From 39b7cd608c8d66f5c5e935d5fd9b3ea3b17debcd Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 27 Dec 2016 15:22:41 +0000 Subject: [PATCH 04/29] Added Docker stuff; corrected usage message. --- buildall.sh | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/buildall.sh b/buildall.sh index 0eabd8e..2f19975 100755 --- a/buildall.sh +++ b/buildall.sh @@ -72,8 +72,9 @@ if [ $# -lt 1 ] then cat <<-EOF 1>&2 Usage: - -archive Create a tar archive of the current state of the source. - -build Build all components and commit to master. + -archive Create a tar archive of the current state of the source. + -build Build all components, commit and push to origin. + -docker Build and push a Docker image. -email [ADDRESS] Your email address, to be recorded in the build signature. -fullname [NAME] Your full name, to be recorded in the build signature. -pull Pull from remote git repository @@ -87,12 +88,14 @@ fi while (( "$#" )) do case $1 in - -a|-archive) - archive="TRUE";; + -a|-archive) + archive="TRUE";; -b|-build) # 'build' is the expected normal case. trial="FALSE"; ;; + -d|-docker) + docker="TRUE";; -e|-email) shift; email=$1;; @@ -126,7 +129,7 @@ do shift done -echo "Trial: ${trial}; email: ${email}; fullname ${fullname}; release: ${release}; webapps: $webappsdir" +echo "Trial: ${trial}; docker: ${docker}; email: ${email}; fullname ${fullname}; release: ${release}; webapps: $webappsdir" ls mw-* > /dev/null 2>&1 if [ $? -ne 0 ] @@ -200,12 +203,18 @@ do # probably deploy it to local Tomcat for test if [ "${dir}" = "mw-ui" -a "${webappsdir}" != "" ] then - lein ring uberwar + lein ring uberwar sudo cp target/microworld.war "${webappsdir}" echo "Deployed new WAR file to local Tomcat at ${webappsdir}" fi - # Then unset manifest properties prior to committing. + if [ "${dir}" = "mw-ui" -a "${docker}" = "TRUE" ] + then + lein docker build + lein docker push + fi + + # Then unset manifest properties prior to committing. cat project.clj > ${tmp}/project.bak.2 setup-build-sig sed -f ${tmp}/manifest.sed ${tmp}/project.bak.2 > project.clj From 47caea3eb81b4eae5c3ad36f0cf12d731a93dfd6 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 27 Dec 2016 15:44:57 +0000 Subject: [PATCH 05/29] Don't do anything with mw-explore during buildall - it contains unreliable junk. --- buildall.sh | 263 ++++++++++++++++++++++++++-------------------------- 1 file changed, 133 insertions(+), 130 deletions(-) diff --git a/buildall.sh b/buildall.sh index 2f19975..6152aed 100755 --- a/buildall.sh +++ b/buildall.sh @@ -140,138 +140,141 @@ fi for dir in mw-* do - pushd ${dir} - - # Make a temporary directory to keep the work-in-progress files. - if [ ! -d "${tmp}" ] - then - rm -f "${tmp}" - mkdir "${tmp}" - fi - - cat project.clj > ${tmp}/project.bak.1 - old=`cat project.clj | grep 'defproject mw' | sed 's/.*defproject mw-[a-z]* "\([A-Za-z0-9_.-]*\)".*/\1/'` - - if [ "${release}" != "" ] - then - message="Preparing ${old} for release" - - # Does the 'old' version tag end with the token "-SNAPSHOT"? it probably does! - echo "${old}" | grep 'SNAPSHOT' - if [ $? -eq 0 ] - then - # It does... - interim=`echo ${old} | sed 's/\([A-Za-z0-9_.-]*\)-SNAPSHOT.*/\1/'` - if [ "${interim}" = "" ] - then - echo "Failed to compute interim version tag from '${old}'" 1>&2 - exit 1; - fi - setup-build-sig "${old}" "${interim}" "${fullname}" "${email}" - message="Upversioned from ${old} to ${interim} for release" - old=${interim} - else - setup-build-sig "unset" "${old}" "${fullname}" "${email}" - fi - else - setup-build-sig "unset" "${old}" "${fullname}" "${email}" - fi - - sed -f ${tmp}/manifest.sed ${tmp}/project.bak.1 > project.clj - - echo $message - - lein clean - lein compile - if [ $? -ne 0 ] - then - echo "Sub-project ${dir} failed in compile" 1>&2 - exit 1 - fi - - lein test - if [ $? -ne 0 ] - then - echo "Sub-project ${dir} failed in test" 1>&2 - exit 1 - fi - - lein marg - lein install - - # If we're in the UI project, build the uberwar - and should - # probably deploy it to local Tomcat for test - if [ "${dir}" = "mw-ui" -a "${webappsdir}" != "" ] - then - lein ring uberwar - sudo cp target/microworld.war "${webappsdir}" - echo "Deployed new WAR file to local Tomcat at ${webappsdir}" - fi - - if [ "${dir}" = "mw-ui" -a "${docker}" = "TRUE" ] + if [ "${dir}" != "mw-explore" ] then - lein docker build - lein docker push + pushd ${dir} + + # Make a temporary directory to keep the work-in-progress files. + if [ ! -d "${tmp}" ] + then + rm -f "${tmp}" + mkdir "${tmp}" + fi + + cat project.clj > ${tmp}/project.bak.1 + old=`cat project.clj | grep 'defproject mw' | sed 's/.*defproject mw-[a-z]* "\([A-Za-z0-9_.-]*\)".*/\1/'` + + if [ "${release}" != "" ] + then + message="Preparing ${old} for release" + + # Does the 'old' version tag end with the token "-SNAPSHOT"? it probably does! + echo "${old}" | grep 'SNAPSHOT' + if [ $? -eq 0 ] + then + # It does... + interim=`echo ${old} | sed 's/\([A-Za-z0-9_.-]*\)-SNAPSHOT.*/\1/'` + if [ "${interim}" = "" ] + then + echo "Failed to compute interim version tag from '${old}'" 1>&2 + exit 1; + fi + setup-build-sig "${old}" "${interim}" "${fullname}" "${email}" + message="Upversioned from ${old} to ${interim} for release" + old=${interim} + else + setup-build-sig "unset" "${old}" "${fullname}" "${email}" + fi + else + setup-build-sig "unset" "${old}" "${fullname}" "${email}" + fi + + sed -f ${tmp}/manifest.sed ${tmp}/project.bak.1 > project.clj + + echo $message + + lein clean + lein compile + if [ $? -ne 0 ] + then + echo "Sub-project ${dir} failed in compile" 1>&2 + exit 1 + fi + + lein test + if [ $? -ne 0 ] + then + echo "Sub-project ${dir} failed in test" 1>&2 + exit 1 + fi + + lein marg + lein install + + # If we're in the UI project, build the uberwar - and should + # probably deploy it to local Tomcat for test + if [ "${dir}" = "mw-ui" -a "${webappsdir}" != "" ] + then + lein ring uberwar + sudo cp target/microworld.war "${webappsdir}" + echo "Deployed new WAR file to local Tomcat at ${webappsdir}" + fi + + if [ "${dir}" = "mw-ui" -a "${docker}" = "TRUE" ] + then + lein docker build + lein docker push + fi + + # Then unset manifest properties prior to committing. + cat project.clj > ${tmp}/project.bak.2 + setup-build-sig + sed -f ${tmp}/manifest.sed ${tmp}/project.bak.2 > project.clj + + if [ "${trial}" = "FALSE" ] + then + if [ "${message}" = "" ] + then + git commit -a + else + git commit -a -m "$message" + fi + git push origin master + fi + + if [ "${release}" != "" ] + then + branch="${old}_MAINTENANCE" + if [ "${trial}" = "FALSE" ] + then + git branch "${branch}" + git push origin "${branch}" + fi + + cat project.clj > ${tmp}/project.bak.3 + setup-build-sig "${old}" "${release}-SNAPSHOT" "${fullname}" "${email}" + sed -f ${tmp}/manifest.sed ${tmp}/project.bak.3 > project.clj + message="Upversioned from ${interim} to ${release}-SNAPSHOT" + + echo $message + + lein clean + lein compile + if [ $? -ne 0 ] + then + echo "Sub-project ${dir} failed in compile after branch to ${release}!" 1>&2 + exit 1 + fi + lein marg + lein install + + # Then unset manifest properties prior to committing. + cat project.clj > ${tmp}/project.bak.4 + setup-build-sig + sed -f ${tmp}/manifest.sed ${tmp}/project.bak.4 > project.clj + + if [ "${trial}" = "FALSE" ] + then + git commit -a -m "${message}" + echo ${message} + git push origin master + fi + fi + + # if nothing broke so far, clean up... + rm -rf "${tmp}" + popd fi - - # Then unset manifest properties prior to committing. - cat project.clj > ${tmp}/project.bak.2 - setup-build-sig - sed -f ${tmp}/manifest.sed ${tmp}/project.bak.2 > project.clj - - if [ "${trial}" = "FALSE" ] - then - if [ "${message}" = "" ] - then - git commit -a - else - git commit -a -m "$message" - fi - git push origin master - fi - - if [ "${release}" != "" ] - then - branch="${old}_MAINTENANCE" - if [ "${trial}" = "FALSE" ] - then - git branch "${branch}" - git push origin "${branch}" - fi - - cat project.clj > ${tmp}/project.bak.3 - setup-build-sig "${old}" "${release}-SNAPSHOT" "${fullname}" "${email}" - sed -f ${tmp}/manifest.sed ${tmp}/project.bak.3 > project.clj - message="Upversioned from ${interim} to ${release}-SNAPSHOT" - - echo $message - - lein clean - lein compile - if [ $? -ne 0 ] - then - echo "Sub-project ${dir} failed in compile after branch to ${release}!" 1>&2 - exit 1 - fi - lein marg - lein install - - # Then unset manifest properties prior to committing. - cat project.clj > ${tmp}/project.bak.4 - setup-build-sig - sed -f ${tmp}/manifest.sed ${tmp}/project.bak.4 > project.clj - - if [ "${trial}" = "FALSE" ] - then - git commit -a -m "${message}" - echo ${message} - git push origin master - fi - fi - - # if nothing broke so far, clean up... - rm -rf "${tmp}" - popd done From 3ca247e4710d164a7ef8a0510bc5192afbd8107f Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 27 Dec 2016 16:18:10 +0000 Subject: [PATCH 06/29] Upversioned from 0.1.5-SNAPSHOT to 0.1.5 for release --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index a2739ff..4a9ccb9 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject mw-engine "0.1.5-SNAPSHOT" +(defproject mw-engine "0.1.5" :description "Cellular automaton world builder." :url "http://www.journeyman.cc/microworld/" :manifest { From 519ca4e3bdb3756631f8f668ed20ce28d78b2393 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 27 Dec 2016 16:18:38 +0000 Subject: [PATCH 07/29] Upversioned from 0.1.5 to 0.1.6-SNAPSHOT --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 4a9ccb9..70cfad8 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject mw-engine "0.1.5" +(defproject mw-engine "0.1.6-SNAPSHOT" :description "Cellular automaton world builder." :url "http://www.journeyman.cc/microworld/" :manifest { From 2f2463da0e75077d55e06de6f91475de6ff02f29 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 3 Jun 2020 10:47:28 +0100 Subject: [PATCH 08/29] Sweep up of minor changes --- .gitignore | 10 + docs/uberdoc.html | 3997 +++++++++++++++++++++++++++++++ project.clj | 7 +- src/mw_engine/core.clj | 9 +- src/mw_engine/display.clj | 6 +- src/mw_engine/drainage.clj | 68 +- src/mw_engine/natural_rules.clj | 4 +- 7 files changed, 4092 insertions(+), 9 deletions(-) create mode 100644 .gitignore create mode 100644 docs/uberdoc.html 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-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.

+
(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 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]
+            (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 + 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 gradient west-east, given + remaining drops to distribute, and this overall map width.

+
(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 row, across which rainfall has been distributed; + if rain-probability is specified, it is the probable rainfall on a cell + with no gradient.

+
(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 world. TODO: Doesn't really work just now - should + rain more on west-facing slopes, and less to the east of high ground

+
(defn rain-world
+  [world]
+  (map
+    rain-row
+    world))

Return a list of the cells in this world which are higher than this + cell and for which this cell is the lowest neighbour, or which are at the + same altitude and have greater flow

+
(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 cell has an altitude lower than any of + its neighbours in this world

+
(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 cell of this world to the altitude + of the lowest of its neighbours.

+
(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 world. At this stage only floods single + cell hollows.

+
(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 + flow-world-nr.

+
(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 cell in this world, and return a cell identical + to this one but having a value of its flow property set from that computation. The function is + memoised because the consequence of mapping a recursive function across an array is that many + cells will be revisited - potentially many times.

+ +

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 world, but with cells tagged with the amount of + water flowing through them.

+
(defn flow-world
+  [world]
+  (map-world (rain-world world) flow))

Return a sequence of cells starting with this cell in this world which + form a contiguous lake

+
(defn explore-lake
+  [world cell])

If this cell in this world is not part of a lake, return nil. If it is, + return a cell like this cell tagged as part of a lake.

+
(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 property of this cell from the corresponding pixel of this heightmap. + If the heightmap you supply is smaller than the world, this will break.

+ +
    +
  • world not actually used, but present to enable this function to be + passed as an argument to mw-engine.utils/map-world, q.v.
  • +
  • cell a cell, as discussed in world.clj, q.v. Alternatively, a map;
  • +
  • property the property (normally a keyword) whose value will be set on the cell.
  • +
  • heightmap an (ideally) greyscale image, whose x and y dimensions should + exceed those of the world of which the cell forms part.
  • +
+
(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 gradient property of this cell of this world to the difference in + altitude between its highest and lowest neghbours.

+
(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 gradient property of each cell in this world to the difference in + altitude between its highest and lowest neghbours.

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

+ +
    +
  • world not actually used, but present to enable this function to be + passed as an argument to mw-engine.utils/map-world, q.v.;
  • +
  • cell a cell, as discussed in world.clj, q.v. Alternatively, a map;
  • +
  • heightmap an (ideally) greyscale image, whose x and y dimensions should + exceed those of the world of which the cell forms part.
  • +
+
(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 :altitude of each cell, this function also sets the :gradient.

+ +
    +
  • world a world, as defined in world.clj, q.v.; if world is not supplied, +a world the size of the heightmap will be created;
  • +
  • imagepath a file path or URL which indicates an (ideally greyscale) image file.
  • +
+
(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 world from the values in this (ideally greyscale) heightmap.

+ +
    +
  • world a world, as defined in world.clj, q.v.;
  • +
  • imagepath a file path or URL which indicates an (ideally greyscale) image file;
  • +
  • property the property of each cell whose value should be added to from the + intensity of the corresponding cell of the image.
  • +
+
(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.

+ +
    +
  • n a number, on the set of 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 property from this map if it is a integer; + otherwise return zero.

+
(defn get-int-or-zero
+  [map property]
+  (let [value (map property)]
+    (if (integer? value) value 0)))

Return a cell like this cell, but having a value for :generation, zero if + the cell passed had no integer value for generation, otherwise the value + taken from the cell passed. The world argument is present only for + consistency with the rule engine and is ignored.

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

+ +
    +
  • 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
+  [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))
+  ([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.

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

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

+ +
    +
  • map a map;
  • +
  • key a symbol or keyword, presumed to be a key into the map.
  • +
+
(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 + get-int, but may not always be (depending whether species are later + implemented as actors)

+ +
    +
  • cell a map;
  • +
  • species a keyword representing a species which may populate that cell.
  • +
+
(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.

+ +
Several overloads:
+* `world` a world, as described in world.clj;
+* `cell` a cell within that world
+Gets immediate neighbours of the specified cell.
+
+* `world` a world, as described in world.clj;
+* `cell` a cell within that world
+* `depth` an integer representing the depth to search from the
+  `cell`
+Gets neighbours within the specified distance of the cell.
+
+* `world` a world, as described in world.clj;
+* `x` an integer representing an x coordinate in that world;
+* `y` an integer representing an y coordinate in that world;
+* `depth` an integer representing the distance from [x,y] that
+  should be searched
+Gets the neighbours within the specified distance of the cell at
+coordinates [x,y] 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.

+ +
* `world` a world, as described in `world.clj`;
+* `cell` a cell within that world;
+* `depth` an integer representing the distance from [x,y] that
+  should be searched (optional);
+* `property` a keyword representing a property of the neighbours;
+* `value` a value of that property (or, possibly, the name of another);
+* `op` a comparator function to use in place of `=` (optional).
+
+ +

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.

+ +
* `world` a world, as described in `world.clj`;
+* `cell` a cell within that world;
+* `depth` an integer representing the distance from [x,y] that
+  should be searched;
+* `state` a keyword representing a state in the world.
+
+
(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 cells which has the lowest numeric value + for this property; if the property is absent or not a number, use this + default

+
(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 cells x and y properties are equal to these x and y values, + return a cell like this cell but with the value of this property set to + this value. Otherwise, just return this cell.

+
(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 world but with the value of exactly one property + of one cell changed to this value

+
(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 world, but merge the values from this cell with + those from the cell in the world with the same co-ordinates

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

+ +
    +
  • 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)
 
\ No newline at end of file diff --git a/project.clj b/project.clj index 70cfad8..383341f 100644 --- a/project.clj +++ b/project.clj @@ -12,10 +12,11 @@ :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.6.0"] + :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"] - [fivetonine/collage "0.2.0"]]) + [net.mikera/imagez "0.3.1"]]) diff --git a/src/mw_engine/core.clj b/src/mw_engine/core.clj index 83c1a2e..8b5062e 100644 --- a/src/mw_engine/core.clj +++ b/src/mw_engine/core.clj @@ -2,8 +2,11 @@ :author "Simon Brooke"} mw-engine.core (:require [clojure.core.reducers :as r] + [clojure.string :refer [join]] + [clojure.tools.cli :refer [parse-opts]] [mw-engine.world :as world] - [mw-engine.utils :refer [get-int-or-zero map-world]]) + [mw-engine.utils :refer [get-int-or-zero map-world]] + [taoensso.timbre :as l]) (:gen-class)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -123,9 +126,11 @@ Return the final generation of the world." [world init-rules rules generations] - (reduce (fn [world _iteration] + (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/mw_engine/display.clj index 7dca8ff..8cddd11 100644 --- a/src/mw_engine/display.clj +++ b/src/mw_engine/display.clj @@ -28,6 +28,10 @@ ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(def ^:dynamic *image-base* + "Base url (i.e., url of directory) from which to load tile images." + "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" @@ -38,7 +42,7 @@ "Render this `state`, assumed to be a keyword indicating a state in the world, into a path which should recover the corresponding image file." [state] - (format "img/tiles/%s.png" (format-css-class state))) + (format "%s/%s.png" *image-base* (format-css-class state))) (defn format-mouseover [cell] diff --git a/src/mw_engine/drainage.clj b/src/mw_engine/drainage.clj index 87a7207..603bf89 100644 --- a/src/mw_engine/drainage.clj +++ b/src/mw_engine/drainage.clj @@ -38,11 +38,54 @@ ;; forward declaration of flow, to allow for a wee bit of mutual recursion. (declare flow) +(defn rainfall + "Compute rainfall for a cell with this `gradient` west-east, given + `remaining` drops to distribute, and this overall map width." + [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)) + +(defn rain-row + "Return a row like this `row`, across which rainfall has been distributed; + if `rain-probability` is specified, it is the probable rainfall on a cell + with no gradient." + ([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)))) + + (defn rain-world "Simulate rainfall on this `world`. TODO: Doesn't really work just now - should rain more on west-facing slopes, and less to the east of high ground" [world] - (map-world world (fn [world cell] (merge cell {:rainfall 1})))) + (map + rain-row + world)) (defn flow-contributors @@ -143,6 +186,29 @@ [world] (map-world (rain-world world) flow)) +(defn explore-lake + "Return a sequence of cells starting with this `cell` in this `world` which + form a contiguous lake" + [world cell] + ) + +(defn is-lake? + "If this `cell` in this `world` is not part of a lake, return nil. If it is, + return a cell like this `cell` tagged as part of a 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] diff --git a/src/mw_engine/natural_rules.clj b/src/mw_engine/natural_rules.clj index af4a124..86de92b 100644 --- a/src/mw_engine/natural_rules.clj +++ b/src/mw_engine/natural_rules.clj @@ -1,8 +1,8 @@ (ns ^{:doc "A set of MicroWorld rules describing a simplified natural ecosystem." :author "Simon Brooke"} mw-engine.natural-rules - (:require mw-engine.utils - mw-engine.world)) + (:require [mw-engine.utils :refer :all] + [mw-engine.world :refer :all])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; From 1cb613e6e66b5af18e1cf03d83ee1db86912f985 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sat, 6 Jun 2020 12:21:54 +0100 Subject: [PATCH 09/29] #2: Minor preparatory changes. --- src/mw_engine/core.clj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mw_engine/core.clj b/src/mw_engine/core.clj index 8b5062e..866087d 100644 --- a/src/mw_engine/core.clj +++ b/src/mw_engine/core.clj @@ -3,11 +3,9 @@ mw-engine.core (:require [clojure.core.reducers :as r] [clojure.string :refer [join]] - [clojure.tools.cli :refer [parse-opts]] [mw-engine.world :as world] [mw-engine.utils :refer [get-int-or-zero map-world]] - [taoensso.timbre :as l]) - (:gen-class)) + [taoensso.timbre :as l])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; From 2cb3a6af6f8e709ce2df8ffb5b444bdceb2356fa Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 9 Dec 2021 20:02:33 +0000 Subject: [PATCH 10/29] Trying to address bit-rot --- .gitignore | 8 + docs/uberdoc.html | 1065 +++++++++----------- project.clj | 35 +- src/cljc/mw_engine/core.clj | 134 +++ src/{ => cljc}/mw_engine/display.clj | 0 src/{ => cljc}/mw_engine/drainage.clj | 0 src/{ => cljc}/mw_engine/heightmap.clj | 20 +- src/{ => cljc}/mw_engine/natural_rules.clj | 0 src/{ => cljc}/mw_engine/utils.clj | 88 +- src/{ => cljc}/mw_engine/world.clj | 4 +- 10 files changed, 717 insertions(+), 637 deletions(-) create mode 100644 src/cljc/mw_engine/core.clj rename src/{ => cljc}/mw_engine/display.clj (100%) rename src/{ => cljc}/mw_engine/drainage.clj (100%) rename src/{ => cljc}/mw_engine/heightmap.clj (90%) rename src/{ => cljc}/mw_engine/natural_rules.clj (100%) rename src/{ => cljc}/mw_engine/utils.clj (83%) rename src/{ => cljc}/mw_engine/world.clj (97%) 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\s*$/g,sb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"

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]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; From 67a43279f6b04225bcdf9a3cce7e10e43c95433d Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 9 Dec 2021 21:20:31 +0000 Subject: [PATCH 11/29] Fix linting issues --- src/cljc/mw_engine/core.clj | 21 ++++----------------- src/cljc/mw_engine/display.clj | 8 +++----- src/cljc/mw_engine/drainage.clj | 10 +++++----- src/cljc/mw_engine/natural_rules.clj | 3 +-- src/cljc/mw_engine/utils.clj | 6 +++--- src/cljc/mw_engine/world.clj | 6 +++--- 6 files changed, 19 insertions(+), 35 deletions(-) diff --git a/src/cljc/mw_engine/core.clj b/src/cljc/mw_engine/core.clj index 866087d..60ef26a 100644 --- a/src/cljc/mw_engine/core.clj +++ b/src/cljc/mw_engine/core.clj @@ -1,10 +1,7 @@ (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]] + (:require [mw-engine.utils :refer [get-int-or-zero map-world]] [taoensso.timbre :as l])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -68,16 +65,16 @@ (let [result (apply rule (list cell world))] (cond (and result source) (merge result {:rule source}) - true result)))) + :else 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))] + :else (let [result (apply-rule world cell (first rules))] (cond result result - true (apply-rules world cell (rest rules)))))) + :else (apply-rules world cell (rest rules)))))) (defn- transform-cell @@ -104,16 +101,6 @@ (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. diff --git a/src/cljc/mw_engine/display.clj b/src/cljc/mw_engine/display.clj index 8cddd11..1847943 100644 --- a/src/cljc/mw_engine/display.clj +++ b/src/cljc/mw_engine/display.clj @@ -1,9 +1,6 @@ (ns ^{:doc "Simple functions to allow a world to be visualised." :author "Simon Brooke"} - mw-engine.display - (:require [hiccup.core :refer [html]] - mw-engine.utils - mw-engine.world)) + mw-engine.display) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -32,9 +29,10 @@ "Base url (i.e., url of directory) from which to load tile images." "img/tiles") -(defn format-css-class [state] +(defn format-css-class "Format this `state`, assumed to be a keyword indicating a state in the world, into a CSS class" + [state] (subs (str state) 1)) diff --git a/src/cljc/mw_engine/drainage.clj b/src/cljc/mw_engine/drainage.clj index 603bf89..e826eda 100644 --- a/src/cljc/mw_engine/drainage.clj +++ b/src/cljc/mw_engine/drainage.clj @@ -145,7 +145,7 @@ if applied sequentially from the highest altitude to the lowest, see `flow-world-nr`." [cell world] - (if (= (- max-altitude (get-int-or-zero cell :generation)) + (when (= (- max-altitude (get-int-or-zero cell :generation)) (get-int-or-zero cell :altitude)) (merge cell {:flow (reduce + @@ -167,7 +167,7 @@ (cond (not (nil? (:flow cell))) cell (<= (or (:altitude cell) 0) *sealevel*) cell - true + :else (merge cell {:flow (+ (:rainfall cell) (apply + @@ -200,8 +200,8 @@ ;; 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 + [outflow (apply min (map :altitude (get-neighbours world cell)))] + (when-not (> (:altitude cell) outflow) (assoc cell :lake true))))) @@ -211,7 +211,7 @@ ) (defn run-drainage - [hmap] "Create a world from the heightmap `hmap`, rain on it, and then compute river flows." + [hmap] (flow-world (rain-world (flood-hollows (heightmap/apply-heightmap hmap))))) diff --git a/src/cljc/mw_engine/natural_rules.clj b/src/cljc/mw_engine/natural_rules.clj index 86de92b..4c85e49 100644 --- a/src/cljc/mw_engine/natural_rules.clj +++ b/src/cljc/mw_engine/natural_rules.clj @@ -1,8 +1,7 @@ (ns ^{:doc "A set of MicroWorld rules describing a simplified natural ecosystem." :author "Simon Brooke"} mw-engine.natural-rules - (:require [mw-engine.utils :refer :all] - [mw-engine.world :refer :all])) + (:require [mw-engine.utils :refer [get-int get-neighbours get-neighbours-with-state member?]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; diff --git a/src/cljc/mw_engine/utils.clj b/src/cljc/mw_engine/utils.clj index 68e37ed..79dbd7a 100644 --- a/src/cljc/mw_engine/utils.clj +++ b/src/cljc/mw_engine/utils.clj @@ -137,7 +137,7 @@ * `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] - (when (in-bounds world x y) + (when (in-bounds? world x y) (nth (nth world y) x))) @@ -150,7 +150,7 @@ (if (map? map) (let [v (map key)] (cond (and v (integer? v)) v - true 0)) + :else 0)) (throw (Exception. "No map passed?")))) @@ -308,7 +308,7 @@ "Return a world like this `world`, but merge the values from this `cell` with those from the cell in the world with the same co-ordinates" [world cell] - (if (in-bounds world (:x cell) (:y cell)) + (if (in-bounds? world (:x cell) (:y cell)) (map-world world #(if (and diff --git a/src/cljc/mw_engine/world.clj b/src/cljc/mw_engine/world.clj index 71387de..34d1e1b 100644 --- a/src/cljc/mw_engine/world.clj +++ b/src/cljc/mw_engine/world.clj @@ -56,7 +56,7 @@ * `height` y coordinate of the next cell to be created." [index width height] (cond (= index width) nil - true (cons (make-cell index height) + :else (cons (make-cell index height) (make-world-row (inc index) width height)))) @@ -69,7 +69,7 @@ * `height` total height of the matrix, in cells." [index width height] (cond (= index height) nil - true (cons (apply vector (make-world-row 0 width index)) + :else (cons (apply vector (make-world-row 0 width index)) (make-world-rows (inc index) width height)))) (defn make-world @@ -87,7 +87,7 @@ [cell limit] (let [s (:state cell)] (cond (> (count (str s)) limit) (subs s 0 limit) - true s))) + :else s))) (defn format-cell From f4d4e9b6949c71b5dcf0f7e7e629af9b891e1a3e Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sat, 8 Jul 2023 10:21:41 +0100 Subject: [PATCH 12/29] Upgraded everything. This may be a mistake! --- .calva/output-window/output.calva-repl | 171 +++++++++++++++++++++++++ project.clj | 10 +- src/cljc/mw_engine/display.clj | 2 +- src/cljc/mw_engine/heightmap.clj | 26 ++-- src/cljc/mw_engine/natural_rules.clj | 37 +++--- src/cljc/mw_engine/utils.clj | 15 +-- 6 files changed, 204 insertions(+), 57 deletions(-) create mode 100644 .calva/output-window/output.calva-repl diff --git a/.calva/output-window/output.calva-repl b/.calva/output-window/output.calva-repl new file mode 100644 index 0000000..9fea8a2 --- /dev/null +++ b/.calva/output-window/output.calva-repl @@ -0,0 +1,171 @@ +; This is the Calva evaluation results output window. +; TIPS: The keyboard shortcut `ctrl+alt+o o` shows and focuses this window +; when connected to a REPL session. +; Please see https://calva.io/output/ for more info. +; Happy coding! ♥️ + +; Connecting ... +; Reading port file: file:///Users/simon/workspace/MicroWorld/mw-engine/.nrepl-port ... +; Using host:port localhost:58100 ... +; Hooking up nREPL sessions ... +; Connected session: clj +; TIPS: +; - You can edit the contents here. Use it as a REPL if you like. +; - `alt+enter` evaluates the current top level form. +; - `ctrl+enter` evaluates the current form. +; - `alt+up` and `alt+down` traverse up and down the REPL command history +; when the cursor is after the last contents at the prompt +; - Clojure lines in stack traces are peekable and clickable. +; Evaluating code from settings: 'calva.autoEvaluateCode.onConnect.clj' +nil +clj꞉user꞉>  ; Use `alt+enter` to evaluate +clj꞉user꞉>  +nil +; WARNING: abs already refers to: #'clojure.core/abs in namespace: mw-engine.utils, being replaced by: #'mw-engine.utils/abs +clj꞉mw-engine.core꞉>  +nil +clj꞉mw-engine.core꞉>  +#'mw-engine.core/apply-rule +clj꞉mw-engine.core꞉>  +#'mw-engine.core/apply-rules +clj꞉mw-engine.core꞉>  +#'mw-engine.core/transform-cell +clj꞉mw-engine.core꞉>  +#'mw-engine.core/transform-world +clj꞉mw-engine.core꞉>  +#'mw-engine.core/run-world +clj꞉mw-engine.core꞉>  +#'mw-engine.display/*image-base* +clj꞉mw-engine.display꞉>  +; Syntax error compiling at (src/cljc/mw_engine/display.clj:32:1). +; Unable to resolve symbol: defn in this context +clj꞉mw-engine.display꞉>  +; Syntax error compiling at (src/cljc/mw_engine/display.clj:32:1). +; Unable to resolve symbol: defn in this context +clj꞉mw-engine.display꞉>  +nil +clj꞉mw-engine.display꞉>  +#'mw-engine.display/*image-base* +clj꞉mw-engine.display꞉>  +#'mw-engine.display/format-css-class +clj꞉mw-engine.display꞉>  +#'mw-engine.display/format-image-path +clj꞉mw-engine.display꞉>  +#'mw-engine.display/format-mouseover +clj꞉mw-engine.display꞉>  +#'mw-engine.display/render-cell +clj꞉mw-engine.display꞉>  +#'mw-engine.display/render-world-row +clj꞉mw-engine.display꞉>  +#'mw-engine.display/render-world-table +clj꞉mw-engine.display꞉>  +nil +; WARNING: abs already refers to: #'clojure.core/abs in namespace: mw-engine.heightmap, being replaced by: #'mw-engine.utils/abs +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/*sealevel* +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/flow +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/rainfall +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/rain-row +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/rain-world +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/flow-contributors +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/is-hollow +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/flood-hollow +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/flood-hollows +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/max-altitude +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/flow-nr +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/flow +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/flow-world-nr +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/flow-world +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/explore-lake +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/is-lake? +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/find-lakes +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/run-drainage +clj꞉mw-engine.drainage꞉>  +#'mw-engine.drainage/run-drainage +clj꞉mw-engine.drainage꞉>  +nil +clj꞉mw-engine.heightmap꞉>  +#'mw-engine.heightmap/tag-property +clj꞉mw-engine.heightmap꞉>  +#'mw-engine.heightmap/tag-gradient +clj꞉mw-engine.heightmap꞉>  +#'mw-engine.heightmap/tag-gradients +clj꞉mw-engine.heightmap꞉>  +#'mw-engine.heightmap/tag-altitude +clj꞉mw-engine.heightmap꞉>  +#'mw-engine.heightmap/apply-heightmap +clj꞉mw-engine.heightmap꞉>  +nil +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/member? +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/get-int-or-zero +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/in-bounds +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/in-bounds? +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/map-world-n-n +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/map-world-p-p +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/map-world +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/get-cell +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/get-int +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/population +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/memo-get-neighbours +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/get-neighbours +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/get-neighbours-with-property-value +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/get-neighbours-with-state +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/get-least-cell +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/set-cell-property +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/set-property +clj꞉mw-engine.utils꞉>  +#'mw-engine.utils/merge-cell +clj꞉mw-engine.utils꞉>  +nil +clj꞉mw-engine.world꞉>  +#'mw-engine.world/make-cell +clj꞉mw-engine.world꞉>  +#'mw-engine.world/make-world-row +clj꞉mw-engine.world꞉>  +#'mw-engine.world/make-world-rows +clj꞉mw-engine.world꞉>  +#'mw-engine.world/make-world +clj꞉mw-engine.world꞉>  +#'mw-engine.world/truncate-state +clj꞉mw-engine.world꞉>  +#'mw-engine.world/format-cell +clj꞉mw-engine.world꞉>  +#'mw-engine.world/format-world-row +clj꞉mw-engine.world꞉>  +#'mw-engine.world/print-world +clj꞉mw-engine.world꞉>  +; nREPL Connection was closed diff --git a/project.clj b/project.clj index edd1265..21b692e 100644 --- a/project.clj +++ b/project.clj @@ -1,10 +1,10 @@ (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"] + :dependencies [[org.clojure/clojure "1.11.1"] + [org.clojure/clojurescript "1.11.60" :scope "provided"] + [org.clojure/math.combinatorics "0.2.0"] [org.clojure/tools.trace "0.7.11"] - [org.clojure/tools.namespace "1.1.1"] - [com.taoensso/timbre "5.1.2"] + [org.clojure/tools.namespace "1.4.4"] + [com.taoensso/timbre "6.2.1"] [fivetonine/collage "0.3.0"] [hiccup "1.0.5"] [net.mikera/imagez "0.12.0"]] diff --git a/src/cljc/mw_engine/display.clj b/src/cljc/mw_engine/display.clj index 1847943..d0e48a3 100644 --- a/src/cljc/mw_engine/display.clj +++ b/src/cljc/mw_engine/display.clj @@ -29,7 +29,7 @@ "Base url (i.e., url of directory) from which to load tile images." "img/tiles") -(defn format-css-class +(defn format-css-class "Format this `state`, assumed to be a keyword indicating a state in the world, into a CSS class" [state] diff --git a/src/cljc/mw_engine/heightmap.clj b/src/cljc/mw_engine/heightmap.clj index 7dfe9f7..e282dcd 100644 --- a/src/cljc/mw_engine/heightmap.clj +++ b/src/cljc/mw_engine/heightmap.clj @@ -1,9 +1,9 @@ (ns ^{:doc "Functions to apply a heightmap to a world." :author "Simon Brooke"} mw-engine.heightmap - (:require [mikera.image.core :refer [load-image filter-image get-pixels]] + (:require [mikera.image.core :refer [load-image filter-image]] [mikera.image.filters :as filters] - [mw-engine.utils :refer [abs get-int get-neighbours map-world]] + [mw-engine.utils :refer [get-int get-neighbours map-world]] [mw-engine.world :refer [make-world]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -34,7 +34,6 @@ ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - (defn tag-property "Set the value of this `property` of this cell from the corresponding pixel of this `heightmap`. If the heightmap you supply is smaller than the world, this will break. @@ -45,7 +44,7 @@ * `property` the property (normally a keyword) whose value will be set on the cell. * `heightmap` an (ideally) greyscale image, whose x and y dimensions should exceed those of the world of which the `cell` forms part." - ([world cell property heightmap] + ([_ cell property heightmap] (tag-property cell property heightmap)) ([cell property heightmap] (merge cell @@ -58,7 +57,6 @@ (get-int cell :x) (get-int cell :y)) 256))))}))) - (defn tag-gradient "Set the `gradient` property of this `cell` of this `world` to the difference in altitude between its highest and lowest neghbours." @@ -71,14 +69,12 @@ gradient (- highest lowest)] (merge cell {:gradient gradient}))) - (defn tag-gradients "Set the `gradient` property of each cell in this `world` to the difference in altitude between its highest and lowest neghbours." [world] (map-world world tag-gradient)) - (defn tag-altitude "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. @@ -88,12 +84,11 @@ * `cell` a cell, as discussed in world.clj, q.v. Alternatively, a map; * `heightmap` an (ideally) greyscale image, whose x and y dimensions should exceed those of the world of which the `cell` forms part." - ([world cell heightmap] + ([_ cell heightmap] (tag-property cell :altitude heightmap)) ([cell heightmap] (tag-property cell :altitude heightmap))) - (defn apply-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 @@ -105,21 +100,20 @@ * `imagepath` a file path or URL which indicates an (ideally greyscale) image file." ([world imagepath] (let [heightmap (filter-image - (filters/grayscale) - (load-image imagepath))] + (load-image imagepath) + (filters/grayscale))] (map-world (map-world world tag-altitude (list heightmap)) tag-gradient))) ([imagepath] (let [heightmap (filter-image - (filters/grayscale) - (load-image imagepath)) + (load-image imagepath) + (filters/grayscale)) world (make-world (.getWidth heightmap) (.getHeight heightmap))] (map-world (map-world world tag-altitude (list heightmap)) tag-gradient)))) - (defn apply-valuemap "Generalised from apply-heightmap, set an arbitrary property on each cell of this `world` from the values in this (ideally greyscale) heightmap. @@ -130,6 +124,6 @@ intensity of the corresponding cell of the image." [world imagepath property] (let [heightmap (filter-image - (filters/grayscale) - (load-image imagepath))] + (load-image imagepath) + (filters/grayscale))] (map-world world tag-property (list property heightmap)))) diff --git a/src/cljc/mw_engine/natural_rules.clj b/src/cljc/mw_engine/natural_rules.clj index 4c85e49..b655704 100644 --- a/src/cljc/mw_engine/natural_rules.clj +++ b/src/cljc/mw_engine/natural_rules.clj @@ -33,46 +33,41 @@ ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; 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}))) + (fn [cell _] (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] + (fn [cell _] (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}))) + (fn [cell _] (cond (= (:state cell) :scrub) (merge cell {:state :forest}))) ;; Forest on fertile land grows to climax - (fn [cell world] + (fn [cell _] (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}))) + (fn [cell _] (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 @@ -81,7 +76,7 @@ (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}))) + (fn [cell _] (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] @@ -95,11 +90,11 @@ (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] + (fn [cell _] (cond (= (:state cell) :waste) (merge cell {:state :grassland}))) ;; Forest increases soil fertility - (fn [cell world] + (fn [cell _] (cond (member? (:state cell) '(:forest :climax)) (merge cell {:fertility (+ (get-int cell :fertility) 1)}))))) @@ -109,7 +104,7 @@ (list ;; if there are too many deer for the fertility of the area to sustain, ;; some die or move on. - (fn [cell world] + (fn [cell _] (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. @@ -128,7 +123,7 @@ (>= n 2)) (merge cell {:deer (int (/ n 2))})))) ;; deer breed. - (fn [cell world] + (fn [cell _] (cond (>= (get-int cell :deer) 2) (merge cell {:deer (int (* (:deer cell) 2))}))))) @@ -137,7 +132,7 @@ (def predator-rules (list ;; wolves eat deer - (fn [cell world] + (fn [cell _] (cond (>= (get-int cell :wolves) 1) (merge cell {:deer (max 0 (- (get-int cell :deer) (get-int cell :wolves)))}))) @@ -146,7 +141,7 @@ ;; (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] + (fn [cell _] (cond (> (get-int cell :wolves) (get-int cell :deer)) (merge cell {:wolves 0}))) ;; wolves arrive occasionally at the edge of the map. @@ -165,7 +160,7 @@ (>= n 2)) (merge cell {:wolves 2})))) ;; wolves breed. - (fn [cell world] + (fn [cell _] (cond (>= (get-int cell :wolves) 2) (merge cell {:wolves (int (* (:wolves cell) 2))}))))) @@ -175,13 +170,13 @@ (def init-rules (list ;; below the waterline, we have water. - (fn [cell world] + (fn [cell _] (cond (and (= (:state cell) :new) (< (get-int cell :altitude) waterline)) (merge cell {:state :water}))) ;; above the snowline, we have snow. - (fn [cell world] + (fn [cell _] (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}))))) + (fn [cell _] (cond (= (:state cell) :new) (merge cell {:state :grassland}))))) (def natural-rules (flatten diff --git a/src/cljc/mw_engine/utils.clj b/src/cljc/mw_engine/utils.clj index 79dbd7a..f84abe4 100644 --- a/src/cljc/mw_engine/utils.clj +++ b/src/cljc/mw_engine/utils.clj @@ -28,22 +28,10 @@ ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn abs - "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. - - * `n` a number, on the set of real numbers." - [n] - (if (neg? n) (- 0 n) n)) - - (defn member? "True if elt is a member of col." [elt col] (some #(= elt %) col)) - (defn get-int-or-zero "Return the value of this `property` from this `map` if it is a integer; otherwise return zero." @@ -51,13 +39,12 @@ (let [value (map property)] (if (integer? value) value 0))) - (defn init-generation "Return a cell like this `cell`, but having a value for :generation, zero if the cell passed had no integer value for generation, otherwise the value taken from the cell passed. The `world` argument is present only for consistency with the rule engine and is ignored." - [world cell] + [_ cell] (merge cell {:generation (get-int-or-zero cell :generation)})) From 5ef93ef4dff95bf5191235b2e6867107339681ad Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 9 Jul 2023 22:27:20 +0100 Subject: [PATCH 13/29] Work on flows, close to complete but no cigar. --- .gitignore | 2 +- src/cljc/mw_engine/flow.clj | 113 +++++++++++++++++++++++++ src/cljc/mw_engine/utils.clj | 150 +++++++++++++++++----------------- test/mw_engine/flow_test.clj | 55 +++++++++++++ test/mw_engine/utils_test.clj | 18 +++- 5 files changed, 263 insertions(+), 75 deletions(-) create mode 100644 src/cljc/mw_engine/flow.clj create mode 100644 test/mw_engine/flow_test.clj diff --git a/.gitignore b/.gitignore index 2cf4294..ea35471 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ - +.calva/ target/ pom.xml diff --git a/src/cljc/mw_engine/flow.clj b/src/cljc/mw_engine/flow.clj new file mode 100644 index 0000000..2047802 --- /dev/null +++ b/src/cljc/mw_engine/flow.clj @@ -0,0 +1,113 @@ +(ns mw-engine.flow + "Allow flows of values between cells in the world." + (:require [mw-engine.utils :refer [get-cell get-num merge-cell]] + [taoensso.timbre :refer [warn]])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; 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. +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; OK, the design here is: a flow object is a map with the following properties: +;; 1. :source, whose value is a location; +;; 2. :destination, whose value is a location; +;; 3. :property, whose value is a keyword; +;; 4. :quantity, whose value is a positive real number. +;; +;; A location object is a map with the following properties: +;; 1. :x, whose value is a natural number not greater than the extent of the world; +;; 2. :y, whose value is a natural number not greater than the extent of the world. +;; +;; to execute a flow is transfer the quantity specified of the property specified +;; from the cell at the source specified to the cell at the destination specified; +;; if the source doesn't have sufficient of the property, then all it has should +;; be transferred, but no more. + +(defn coordinate? + "Return `true` if this object `o` is a valid coordinate with respect to + this `world`, else `false`. Assumes square worlds." + [o world] + (try + (and (or (zero? o) (pos-int? o)) + (< o (count world))) + (catch Exception e + (warn (format "Not a valid coordinate: %s; %s" o (.getMessage e))) + false))) + +(defn location? + "Return `true` if this object `o` is a location as defined above with respect to + this `world`, else `false`. Assumes square worlds." + [o world] + (try + (and (map? o) + (coordinate? (:x o) world) + (coordinate? (:y o) world)) + (catch Exception e + (warn (format "Not a valid location: %s; %s" o (.getMessage e))) + false))) + +(defn flow? + "Return `true` if this object `o` is a flow as defined above with respect to + this `world`, else `false`. Assumes square worlds." + [o world] + (try + (and (map? o) + (location? (:source o) world) + (location? (:destination o) world) + (keyword? (:property o)) + (pos? (:quantity o))) + (catch Exception e + (warn (format "Not a valid flow: %s; %s" o (.getMessage e))) + false))) + +(defn execute + "Return a world like this `world`, except with the quantity of the property + described in this `flow` object transferred from the source of that flow + to its destination." + [flow world] + (try + (let [source (get-cell world (-> flow :source :x) (-> flow :source :y)) + dest (get-cell world (-> flow :destination :x) (-> flow :destination :y)) + p (:property flow) + q (min (:quantity flow) (get-num source p)) + s' (assoc source p (- (source p) q)) + d' (assoc dest p (+ (get-num dest p) q))] + (merge-cell (merge-cell world s') d')) + (catch Exception e + (warn "Failed to execute flow %s: %s" flow (.getMessage e)) + ;; return the world unmodified. + world))) + +(defn execute-flows + "Return a world like this `world`, but with each of these flows executed." + [flows world] + (reduce execute world (filter #(flow? % world) flows))) \ No newline at end of file diff --git a/src/cljc/mw_engine/utils.clj b/src/cljc/mw_engine/utils.clj index f84abe4..338dc95 100644 --- a/src/cljc/mw_engine/utils.clj +++ b/src/cljc/mw_engine/utils.clj @@ -1,9 +1,9 @@ (ns ^{:doc " Utility functions needed by MicroWorld and, specifically, in the interpretation of MicroWorld rule." :author "Simon Brooke"} - mw-engine.utils + mw-engine.utils (:require - [clojure.math.combinatorics :as combo])) + [clojure.math.combinatorics :as combo])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -57,7 +57,7 @@ * `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))))) + (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) @@ -68,7 +68,7 @@ * `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))))) + (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`." @@ -78,9 +78,9 @@ (into [] (map (fn [row] (into [] (map - #(apply function - (cons world (cons % additional-args))) - row))) + #(apply function + (cons world (cons % additional-args))) + row))) world)))) @@ -92,9 +92,9 @@ (into [] (pmap (fn [row] (into [] (pmap - #(apply function - (cons world (cons % additional-args))) - row))) + #(apply function + (cons world (cons % additional-args))) + row))) world)))) @@ -111,12 +111,11 @@ (into [] (pmap (fn [row] (into [] (map - #(apply function - (cons world (cons % additional-args))) - row))) + #(apply function + (cons world (cons % additional-args))) + row))) world)))) - (defn get-cell "Return the cell a x, y in this world, if any. @@ -127,9 +126,9 @@ (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. + "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`." @@ -140,6 +139,18 @@ :else 0)) (throw (Exception. "No map passed?")))) +(defn get-num + "Get the value of a property expected to be a number from a map; if not + present (or not a number) return 0. + + * `map` a map; + * `key` a symbol or keyword, presumed to be a key into the `map`." + [map key] + (if (map? map) + (let [v (map key)] + (cond (and v (number? v)) v + :else 0)) + (throw (Exception. "No map passed?")))) (defn population "Return the population of this species in this cell. Currently a synonym for @@ -151,7 +162,6 @@ [cell species] (get-int cell species)) - (def memo-get-neighbours "Memoised get neighbours is more efficient when running deeply recursive algorithms on the same world. But it's less efficient when running the @@ -163,12 +173,11 @@ (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))))))))) - + (range (- x depth) (+ x depth 1)) + (range (- y depth) (+ y depth 1))))))))) (defn get-neighbours - "Get the neighbours to distance depth of a cell in this world. + "Get the neighbours to distance depth of a cell in this world. Several overloads: * `world` a world, as described in world.clj; @@ -188,18 +197,17 @@ should be searched Gets the neighbours within the specified distance of the cell at coordinates [x,y] in this world." - ([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))) - + ([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))) (defn get-neighbours-with-property-value "Get the neighbours to distance depth of the cell at x, y in this world which @@ -215,20 +223,20 @@ It gets messy." ([world x y depth property value op] - (filter - #(eval - (list op - (or (get % property) (get-int % property)) - value)) - (get-neighbours world x y depth))) + (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 =)) + (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)) + (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-neighbours-with-property-value world cell 1 + property value))) (defn get-neighbours-with-state "Get the neighbours to distance depth of the cell at x, y in this world which @@ -240,27 +248,24 @@ should be searched; * `state` a keyword representing a state in the world." ([world x y depth state] - (filter #(= (:state %) state) (get-neighbours world x y depth))) + (filter #(= (:state %) state) (get-neighbours world x y depth))) ([world cell depth state] - (get-neighbours-with-state world (:x cell) (:y 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))) + (get-neighbours-with-state world cell 1 state))) (defn get-least-cell "Return the cell from among these `cells` which has the lowest numeric value - for this `property`; if the property is absent or not a number, use this - `default`" - ([cells property default] - (cond - (empty? cells) nil - :else (let [downstream (get-least-cell (rest cells) property default)] - (cond (< - (or (property (first cells)) default) - (or (property downstream) default)) (first cells) - :else downstream)))) - ([cells property] - (get-least-cell cells property (Integer/MAX_VALUE)))) + for this `property`." + [cells property] + (first (sort-by property (filter #(number? (property %)) cells)))) + +(defn get-most-cell + "Return the cell from among these `cells` which has the highest numeric value + for this `property`." + [cells property] + (last (sort-by property cells))) (defn- set-cell-property @@ -278,18 +283,17 @@ "Return a world like this `world` but with the value of exactly one `property` of one `cell` changed to this `value`" ([world cell property value] - (set-property world (:x cell) (:y 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)))) - + (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)))) (defn merge-cell "Return a world like this `world`, but merge the values from this `cell` with @@ -298,9 +302,9 @@ (if (in-bounds? world (:x cell) (:y cell)) (map-world world #(if - (and - (= (:x cell)(:x %2)) - (= (:y cell)(:y %2))) + (and + (= (:x cell) (:x %2)) + (= (:y cell) (:y %2))) (merge %2 cell) %2)) world)) diff --git a/test/mw_engine/flow_test.clj b/test/mw_engine/flow_test.clj new file mode 100644 index 0000000..60ebcea --- /dev/null +++ b/test/mw_engine/flow_test.clj @@ -0,0 +1,55 @@ +(ns mw-engine.flow-test + (:require [clojure.test :refer [deftest is testing]] + [mw-engine.flow :refer [coordinate? execute execute-flows flow? + location?]] + [mw-engine.utils :refer [get-cell merge-cell]] + [mw-engine.world :refer [make-world]])) + +(deftest coordinate-tests + (testing "coordinates" + (let [world (make-world 3 3)] + (is (not (coordinate? -1 world)) "Not a coordinate: negative") + (is (not (coordinate? 4 world)) "Not a coordinate: out of bounds") + (is (not (coordinate? 3 world)) "Not a coordinate: boundary") + (is (not (coordinate? :three world)) "Not a coordinate: keyword") + (is (not (coordinate? 3.14 world)) "Not a coordinate: floating point") + (is (coordinate? 0 world) "should be a coordinate: zero") + (is (coordinate? 1 world) "should be a coordinate: middle")))) + +(deftest location-tests + (testing "locations" + (let [world (make-world 3 3) + in1 {:x 0 :y 0} + in2 {:x 1 :y 2} + out1 {:p 0 :q 0} + out2 {:x -1 :y 2}] + (is (location? in1 world) "should be a location: top left") + (is (location? in2 world) "should be a location: middle bottom") + (is (not (location? out1 world)) "should not be a location: wrong keys") + (is (not (location? out2 world)) "should not be a location: negative coordinate")))) + +(deftest flow-tests + (testing "flows" + (let [world (make-world 3 3) + world' (merge-cell world {:x 0, :y 0, :state :new :q 5.3}) + valid {:source {:x 0 :y 0} + :destination {:x 1 :y 1} + :property :q + :quantity 2.4}] + (is (flow? valid world)) + (let [transferred (execute valid world') + source-q (:q (get-cell transferred 0 0)) + dest-q (:q (get-cell transferred 1 1))] + (is (= source-q 2.9)) + (is (= dest-q 2.4))) + (let [valid2 {:source {:x 1 :y 1} + :destination {:x 0 :y 1} + :property :q + :quantity 1} + transferred (execute-flows (list valid valid2) world') + source-q (:q (get-cell transferred 0 0)) + inter-q (:q (get-cell transferred 1 1)) + dest-q (:q (get-cell transferred 0 1))] + (is (= source-q 2.9)) + (is (= inter-q 1.4)) + (is (= dest-q 1)))))) \ No newline at end of file diff --git a/test/mw_engine/utils_test.clj b/test/mw_engine/utils_test.clj index 0077ceb..91dacbd 100644 --- a/test/mw_engine/utils_test.clj +++ b/test/mw_engine/utils_test.clj @@ -178,4 +178,20 @@ (is (:test (get-cell w3c 2 2)) "The cell with :test set is at 2, 2")))) - \ No newline at end of file +(deftest most-least-tests + (let [cells [{:x 0, :y 0, :state :new, :prop 0.4406204774301924} + {:x 1, :y 0, :state :new, :prop 0.26475629405490275} + {:x 2, :y 0, :state :new, :prop 0.34018209505715813} + {:x 0, :y 1, :state :new, :prop 0.35104719397171424} + {:x 1, :y 1, :state :new, :prop 0.6009298123397215} ;; <- max + {:x 2, :y 1, :state :new, :prop 0.5580383897506066} + {:x 0, :y 2, :state :new, :prop 0.1780241365266907} ;; <- min + {:x 1, :y 2, :state :new, :prop 0.3255028139128574} + {:x 2, :y 2, :state :new, :prop 0.3449965660347397}]] + (let [expected {:x 1, :y 1, :state :new, :prop 0.6009298123397215} + actual (get-most-cell cells :prop)] + (is (= actual expected) "get-most-cell failed") + ) + (let [expected {:x 0, :y 2, :state :new, :prop 0.1780241365266907} + actual (get-least-cell cells :prop)] + (is (= actual expected) "get-least-cell failed")))) \ No newline at end of file From f60fdb944bdea213b305102f29f862116237a324 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Mon, 10 Jul 2023 08:15:09 +0100 Subject: [PATCH 14/29] Executing flows now works. --- src/cljc/mw_engine/flow.clj | 6 +++--- test/mw_engine/flow_test.clj | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cljc/mw_engine/flow.clj b/src/cljc/mw_engine/flow.clj index 2047802..b045cf6 100644 --- a/src/cljc/mw_engine/flow.clj +++ b/src/cljc/mw_engine/flow.clj @@ -93,7 +93,7 @@ "Return a world like this `world`, except with the quantity of the property described in this `flow` object transferred from the source of that flow to its destination." - [flow world] + [world flow] (try (let [source (get-cell world (-> flow :source :x) (-> flow :source :y)) dest (get-cell world (-> flow :destination :x) (-> flow :destination :y)) @@ -103,11 +103,11 @@ d' (assoc dest p (+ (get-num dest p) q))] (merge-cell (merge-cell world s') d')) (catch Exception e - (warn "Failed to execute flow %s: %s" flow (.getMessage e)) + (warn (format "Failed to execute flow %s: %s" flow (.getMessage e))) ;; return the world unmodified. world))) (defn execute-flows "Return a world like this `world`, but with each of these flows executed." - [flows world] + [world flows] (reduce execute world (filter #(flow? % world) flows))) \ No newline at end of file diff --git a/test/mw_engine/flow_test.clj b/test/mw_engine/flow_test.clj index 60ebcea..cfd2198 100644 --- a/test/mw_engine/flow_test.clj +++ b/test/mw_engine/flow_test.clj @@ -37,7 +37,7 @@ :property :q :quantity 2.4}] (is (flow? valid world)) - (let [transferred (execute valid world') + (let [transferred (execute world' valid) source-q (:q (get-cell transferred 0 0)) dest-q (:q (get-cell transferred 1 1))] (is (= source-q 2.9)) @@ -46,7 +46,7 @@ :destination {:x 0 :y 1} :property :q :quantity 1} - transferred (execute-flows (list valid valid2) world') + transferred (execute-flows world' (list valid valid2)) source-q (:q (get-cell transferred 0 0)) inter-q (:q (get-cell transferred 1 1)) dest-q (:q (get-cell transferred 0 1))] From 8b3639edd501a96846d13cd387f4129e73f03d6b Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Mon, 10 Jul 2023 10:44:33 +0100 Subject: [PATCH 15/29] Logging of flows; better `member?` --- src/cljc/mw_engine/core.clj | 7 ++----- src/cljc/mw_engine/flow.clj | 12 +++++++++--- src/cljc/mw_engine/utils.clj | 19 +++++-------------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/cljc/mw_engine/core.clj b/src/cljc/mw_engine/core.clj index 60ef26a..1483c57 100644 --- a/src/cljc/mw_engine/core.clj +++ b/src/cljc/mw_engine/core.clj @@ -67,7 +67,6 @@ (and result source) (merge result {:rule source}) :else result)))) - (defn- apply-rules "Derive a cell from this `cell` of this `world` by applying these `rules`." [world cell rules] @@ -94,12 +93,10 @@ :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))) - + ([world rules] + (map-world world transform-cell (list rules)))) (defn run-world "Run this world with these rules for this number of generations. diff --git a/src/cljc/mw_engine/flow.clj b/src/cljc/mw_engine/flow.clj index b045cf6..778d8a7 100644 --- a/src/cljc/mw_engine/flow.clj +++ b/src/cljc/mw_engine/flow.clj @@ -1,7 +1,7 @@ (ns mw-engine.flow "Allow flows of values between cells in the world." (:require [mw-engine.utils :refer [get-cell get-num merge-cell]] - [taoensso.timbre :refer [warn]])) + [taoensso.timbre :refer [info warn]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -95,12 +95,18 @@ to its destination." [world flow] (try - (let [source (get-cell world (-> flow :source :x) (-> flow :source :y)) - dest (get-cell world (-> flow :destination :x) (-> flow :destination :y)) + (let [sx (-> flow :source :x) + sy (-> flow :source :y) + source (get-cell world sx sy) + dx (-> flow :destination :x) + dy (-> flow :destination :y) + dest (get-cell world dx dy) p (:property flow) q (min (:quantity flow) (get-num source p)) s' (assoc source p (- (source p) q)) d' (assoc dest p (+ (get-num dest p) q))] + (info (format "Moving %f units of %s from %d,%d to %d,%d" + (float q) (name p) sx sy dx dy)) (merge-cell (merge-cell world s') d')) (catch Exception e (warn (format "Failed to execute flow %s: %s" flow (.getMessage e))) diff --git a/src/cljc/mw_engine/utils.clj b/src/cljc/mw_engine/utils.clj index 338dc95..2254f77 100644 --- a/src/cljc/mw_engine/utils.clj +++ b/src/cljc/mw_engine/utils.clj @@ -29,8 +29,8 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn member? - "True if elt is a member of col." - [elt col] (some #(= elt %) col)) + "Return 'true' if elt is a member of col, else 'false'." + [elt col] (boolean ((set col) elt))) (defn get-int-or-zero "Return the value of this `property` from this `map` if it is a integer; @@ -97,7 +97,6 @@ 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 @@ -198,16 +197,11 @@ Gets the neighbours within the specified distance of the cell at coordinates [x,y] in this world." ([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))))))) + (memo-get-neighbours world x y depth)) ([world cell depth] (memo-get-neighbours world (:x cell) (:y cell) depth)) ([world cell] - (get-neighbours world cell 1))) + (memo-get-neighbours world (:x cell) (:y cell) 1))) (defn get-neighbours-with-property-value "Get the neighbours to distance depth of the cell at x, y in this world which @@ -254,7 +248,6 @@ ([world cell state] (get-neighbours-with-state world cell 1 state))) - (defn get-least-cell "Return the cell from among these `cells` which has the lowest numeric value for this `property`." @@ -265,8 +258,7 @@ "Return the cell from among these `cells` which has the highest numeric value for this `property`." [cells property] - (last (sort-by property cells))) - + (last (sort-by property (filter #(number? (property %)) cells)))) (defn- set-cell-property "If this `cell`s x and y properties are equal to these `x` and `y` values, @@ -278,7 +270,6 @@ (merge cell {property value :rule "Set by user"}) :else cell)) - (defn set-property "Return a world like this `world` but with the value of exactly one `property` of one `cell` changed to this `value`" From 4f35557b381f88a0b74079be2b502e27438bb626 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Mon, 10 Jul 2023 13:46:42 +0100 Subject: [PATCH 16/29] Upversioning whole system to 0.2.0, for flow feature --- project.clj | 2 +- src/cljc/mw_engine/core.clj | 73 +++++++++++++------------- src/cljc/mw_engine/display.clj | 5 -- src/cljc/mw_engine/flow.clj | 69 ++++++++++++------------ src/cljc/mw_engine/heightmap.clj | 10 ++-- src/cljc/mw_engine/natural_rules.clj | 18 +++---- src/cljc/mw_engine/utils.clj | 1 - src/cljc/mw_engine/world.clj | 78 +++++++++------------------- 8 files changed, 104 insertions(+), 152 deletions(-) diff --git a/project.clj b/project.clj index 21b692e..e21d537 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject mw-engine "0.1.6-SNAPSHOT" +(defproject mw-engine "0.2.0-SNAPSHOT" :dependencies [[org.clojure/clojure "1.11.1"] [org.clojure/clojurescript "1.11.60" :scope "provided"] [org.clojure/math.combinatorics "0.2.0"] diff --git a/src/cljc/mw_engine/core.clj b/src/cljc/mw_engine/core.clj index 1483c57..e598f60 100644 --- a/src/cljc/mw_engine/core.clj +++ b/src/cljc/mw_engine/core.clj @@ -1,6 +1,27 @@ -(ns ^{:doc "Functions to transform a world and run rules." +(ns ^{:doc "Functions to transform a world and run rules. + + 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 to that cell." :author "Simon Brooke"} - mw-engine.core + mw-engine.core (:require [mw-engine.utils :refer [get-int-or-zero map-world]] [taoensso.timbre :as l])) @@ -26,29 +47,6 @@ ;;;; 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, @@ -62,19 +60,18 @@ (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}) - :else result)))) + (let [result (apply rule (list cell world))] + (cond + (and result source) (merge result {:rule source}) + :else result)))) (defn- apply-rules "Derive a cell from this `cell` of this `world` by applying these `rules`." [world cell rules] (cond (empty? rules) cell - :else (let [result (apply-rule world cell (first rules))] - (cond result result - :else (apply-rules world cell (rest rules)))))) - + :else (let [result (apply-rule world cell (first rules))] + (cond result result + :else (apply-rules world cell (rest rules)))))) (defn- transform-cell "Derive a cell from this `cell` of this `world` by applying these `rules`. If an @@ -82,8 +79,8 @@ [world cell rules] (try (merge - (apply-rules world cell rules) - {:generation (+ (get-int-or-zero cell :generation) 1)}) + (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" @@ -96,7 +93,7 @@ (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)))) + (map-world world transform-cell (list rules)))) (defn run-world "Run this world with these rules for this number of generations. @@ -111,8 +108,8 @@ (reduce (fn [world iteration] (l/info "Running iteration " iteration) (transform-world world rules)) - (transform-world world init-rules) - (range generations))) + (transform-world world init-rules) + (range generations))) diff --git a/src/cljc/mw_engine/display.clj b/src/cljc/mw_engine/display.clj index d0e48a3..4a52445 100644 --- a/src/cljc/mw_engine/display.clj +++ b/src/cljc/mw_engine/display.clj @@ -35,18 +35,15 @@ [state] (subs (str state) 1)) - (defn format-image-path "Render this `state`, assumed to be a keyword indicating a state in the world, into a path which should recover the corresponding image file." [state] (format "%s/%s.png" *image-base* (format-css-class state))) - (defn format-mouseover [cell] (str cell)) - (defn render-cell "Render this world cell as a Hiccup table cell." [cell] @@ -55,13 +52,11 @@ [: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)}]]])) - (defn render-world-row "Render this world `row` as a Hiccup table row." [row] (apply vector (cons :tr (map render-cell row)))) - (defn render-world-table "Render this `world` as a Hiccup table." [world] diff --git a/src/cljc/mw_engine/flow.clj b/src/cljc/mw_engine/flow.clj index 778d8a7..78bff33 100644 --- a/src/cljc/mw_engine/flow.clj +++ b/src/cljc/mw_engine/flow.clj @@ -1,6 +1,26 @@ (ns mw-engine.flow - "Allow flows of values between cells in the world." - (:require [mw-engine.utils :refer [get-cell get-num merge-cell]] + "Allow flows of values between cells in the world. + + The design here is: a flow object is a map with the following properties: + 1. :source, whose value is a location; + 2. :destination, whose value is a location; + 3. :property, whose value is a keyword; + 4. :quantity, whose value is a positive real number. + + A location object is a map with the following properties: + 1. :x, whose value is a natural number not greater than the extent of the world; + 2. :y, whose value is a natural number not greater than the extent of the world. + + To execute a flow is transfer the quantity specified of the property specified + from the cell at the source specified to the cell at the destination specified; + if the source doesn't have sufficient of the property, then all it has should + be transferred, but no more: properties to be flowed cannot be pulled negative. + + Flowing values through the world is consequently a two stage process: firstly + there's a planning stage, in which all the flows to be executed are computed + without changing the world, and then an execution stage, where they're all + executed. This namespace deals with mainly with execution." + (:require [mw-engine.utils :refer [get-cell get-num in-bounds? merge-cell]] [taoensso.timbre :refer [info warn]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -25,32 +45,6 @@ ;;;; 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. -;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; OK, the design here is: a flow object is a map with the following properties: -;; 1. :source, whose value is a location; -;; 2. :destination, whose value is a location; -;; 3. :property, whose value is a keyword; -;; 4. :quantity, whose value is a positive real number. -;; -;; A location object is a map with the following properties: -;; 1. :x, whose value is a natural number not greater than the extent of the world; -;; 2. :y, whose value is a natural number not greater than the extent of the world. -;; -;; to execute a flow is transfer the quantity specified of the property specified -;; from the cell at the source specified to the cell at the destination specified; -;; if the source doesn't have sufficient of the property, then all it has should -;; be transferred, but no more. (defn coordinate? "Return `true` if this object `o` is a valid coordinate with respect to @@ -65,12 +59,13 @@ (defn location? "Return `true` if this object `o` is a location as defined above with respect to - this `world`, else `false`. Assumes square worlds." + this `world`, else `false`." [o world] (try (and (map? o) - (coordinate? (:x o) world) - (coordinate? (:y o) world)) + (integer? (:x o)) + (integer? (:y o)) + (in-bounds? world (:x o) (:y o))) (catch Exception e (warn (format "Not a valid location: %s; %s" o (.getMessage e))) false))) @@ -98,16 +93,16 @@ (let [sx (-> flow :source :x) sy (-> flow :source :y) source (get-cell world sx sy) - dx (-> flow :destination :x) + dx (-> flow :destination :x) dy (-> flow :destination :y) dest (get-cell world dx dy) - p (:property flow) - q (min (:quantity flow) (get-num source p)) - s' (assoc source p (- (source p) q)) - d' (assoc dest p (+ (get-num dest p) q))] + p (:property flow) + q (min (:quantity flow) (get-num source p)) + s' (assoc source p (- (source p) q)) + d' (assoc dest p (+ (get-num dest p) q))] (info (format "Moving %f units of %s from %d,%d to %d,%d" (float q) (name p) sx sy dx dy)) - (merge-cell (merge-cell world s') d')) + (merge-cell (merge-cell world s') d')) (catch Exception e (warn (format "Failed to execute flow %s: %s" flow (.getMessage e))) ;; return the world unmodified. diff --git a/src/cljc/mw_engine/heightmap.clj b/src/cljc/mw_engine/heightmap.clj index e282dcd..24c250d 100644 --- a/src/cljc/mw_engine/heightmap.clj +++ b/src/cljc/mw_engine/heightmap.clj @@ -1,4 +1,7 @@ -(ns ^{:doc "Functions to apply a heightmap to a world." +(ns ^{:doc "Functions to apply a heightmap to a world. + + Heightmaps are considered only as greyscale images, so colour is redundent + (will be ignored). Darker shades are higher." :author "Simon Brooke"} mw-engine.heightmap (:require [mikera.image.core :refer [load-image filter-image]] @@ -28,11 +31,6 @@ ;;;; Copyright (C) 2014 Simon Brooke ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;; -;;;; Heightmaps are considered only as greyscale images, so colour is redundent -;;;; (will be ignored). Darker shades are higher. -;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn tag-property "Set the value of this `property` of this cell from the corresponding pixel of this `heightmap`. diff --git a/src/cljc/mw_engine/natural_rules.clj b/src/cljc/mw_engine/natural_rules.clj index b655704..f695409 100644 --- a/src/cljc/mw_engine/natural_rules.clj +++ b/src/cljc/mw_engine/natural_rules.clj @@ -1,5 +1,10 @@ -(ns ^{:doc "A set of MicroWorld rules describing a simplified natural ecosystem." - :author "Simon Brooke"} +(ns ^{:doc "A set of MicroWorld rules describing a simplified natural ecosystem. + + 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. " + :author " Simon Brooke "} mw-engine.natural-rules (:require [mw-engine.utils :refer [get-int get-neighbours get-neighbours-with-state member?]])) @@ -25,13 +30,6 @@ ;;;; 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) @@ -183,4 +181,4 @@ (list vegetation-rules herbivore-rules - predator-rules))) + predator-rules))) \ No newline at end of file diff --git a/src/cljc/mw_engine/utils.clj b/src/cljc/mw_engine/utils.clj index 2254f77..1be5a61 100644 --- a/src/cljc/mw_engine/utils.clj +++ b/src/cljc/mw_engine/utils.clj @@ -47,7 +47,6 @@ [_ cell] (merge cell {:generation (get-int-or-zero cell :generation)})) - (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?`. diff --git a/src/cljc/mw_engine/world.clj b/src/cljc/mw_engine/world.clj index 34d1e1b..b5fcd17 100644 --- a/src/cljc/mw_engine/world.clj +++ b/src/cljc/mw_engine/world.clj @@ -1,8 +1,16 @@ -(ns ^{:doc "Functions to create and to print two dimensional cellular automata." - :author "Simon Brooke"} - mw-engine.world - (:require [clojure.string :as string] - [mw-engine.utils :refer [population]])) +(ns ^{:doc "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." + :author "Simon Brooke"} + mw-engine.world + (:require [clojure.string :as string] + [mw-engine.utils :refer [population]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -26,51 +34,14 @@ ;;;; 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. -;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- make-cell +(defmacro make-cell "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." [x y] - {:x x :y y :state :new}) - - -(defn- make-world-row - "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." - [index width height] - (cond (= index width) nil - :else (cons (make-cell index height) - (make-world-row (inc index) width height)))) - - -(defn- make-world-rows - "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." - [index width height] - (cond (= index height) nil - :else (cons (apply vector (make-world-row 0 width index)) - (make-world-rows (inc index) width height)))) + `{:x ~x :y ~y :state :new}) (defn make-world "Make a world width cells from east to west, and height cells from north to @@ -79,16 +50,17 @@ * `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." [width height] - (apply vector (make-world-rows 0 width height))) - + (apply vector + (map (fn [h] + (apply vector (map #(make-cell % h) (range width)))) + (range height)))) (defn truncate-state "Truncate the print name of the state of this cell to at most limit characters." [cell limit] (let [s (:state cell)] (cond (> (count (str s)) limit) (subs s 0 limit) - :else s))) - + :else s))) (defn format-cell "Return a formatted string summarising the current state of this cell." @@ -98,13 +70,11 @@ (population cell :deer) (population cell :wolves))) - (defn- format-world-row "Format one row in the state of a world for printing." [row] (string/join (map format-cell row))) - (defn print-world "Print the current state of this world, and return nil. @@ -112,8 +82,8 @@ [world] (println) (dorun - (map - #(println - (format-world-row %)) - world)) + (map + #(println + (format-world-row %)) + world)) nil) From 866c00bea045613a18b77102191114803176392f Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 11 Jul 2023 08:22:54 +0100 Subject: [PATCH 17/29] Some work on flow, but mainly tidyup. --- docs/cloverage/coverage.css | 40 + docs/cloverage/index.html | 157 ++ docs/cloverage/mw_engine/core.clj.html | 353 +++++ docs/cloverage/mw_engine/display.clj.html | 203 +++ docs/cloverage/mw_engine/drainage.clj.html | 659 ++++++++ docs/cloverage/mw_engine/flow.clj.html | 431 ++++++ docs/cloverage/mw_engine/heightmap.clj.html | 389 +++++ .../mw_engine/natural_rules.clj.html | 560 +++++++ docs/cloverage/mw_engine/utils.clj.html | 911 ++++++++++++ docs/cloverage/mw_engine/world.clj.html | 275 ++++ docs/codox/css/default.css | 551 +++++++ docs/codox/css/highlight.css | 97 ++ docs/codox/index.html | 11 + docs/codox/intro.html | 5 + docs/codox/js/highlight.min.js | 2 + docs/codox/js/jquery.min.js | 4 + docs/codox/js/page_effects.js | 112 ++ docs/codox/mw-engine.core.html | 19 + docs/codox/mw-engine.display.html | 11 + docs/codox/mw-engine.drainage.html | 22 + docs/codox/mw-engine.flow.html | 27 + docs/codox/mw-engine.heightmap.html | 31 + docs/codox/mw-engine.natural-rules.html | 14 + docs/codox/mw-engine.utils.html | 78 + docs/codox/mw-engine.world.html | 23 + docs/uberdoc.html | 1322 ++++++++++------- project.clj | 10 +- src/cljc/mw_engine/core.clj | 2 +- src/cljc/mw_engine/drainage.clj | 6 +- src/cljc/mw_engine/flow.clj | 43 +- src/cljc/mw_engine/utils.clj | 19 +- test/mw_engine/core_test.clj | 6 +- test/mw_engine/drainage_test.clj | 6 +- test/mw_engine/flow_test.clj | 34 +- test/mw_engine/heightmap_test.clj | 11 +- test/mw_engine/utils_test.clj | 15 +- test/mw_engine/world_test.clj | 4 +- 37 files changed, 5840 insertions(+), 623 deletions(-) create mode 100644 docs/cloverage/coverage.css create mode 100644 docs/cloverage/index.html create mode 100644 docs/cloverage/mw_engine/core.clj.html create mode 100644 docs/cloverage/mw_engine/display.clj.html create mode 100644 docs/cloverage/mw_engine/drainage.clj.html create mode 100644 docs/cloverage/mw_engine/flow.clj.html create mode 100644 docs/cloverage/mw_engine/heightmap.clj.html create mode 100644 docs/cloverage/mw_engine/natural_rules.clj.html create mode 100644 docs/cloverage/mw_engine/utils.clj.html create mode 100644 docs/cloverage/mw_engine/world.clj.html create mode 100644 docs/codox/css/default.css create mode 100644 docs/codox/css/highlight.css create mode 100644 docs/codox/index.html create mode 100644 docs/codox/intro.html create mode 100644 docs/codox/js/highlight.min.js create mode 100644 docs/codox/js/jquery.min.js create mode 100644 docs/codox/js/page_effects.js create mode 100644 docs/codox/mw-engine.core.html create mode 100644 docs/codox/mw-engine.display.html create mode 100644 docs/codox/mw-engine.drainage.html create mode 100644 docs/codox/mw-engine.flow.html create mode 100644 docs/codox/mw-engine.heightmap.html create mode 100644 docs/codox/mw-engine.natural-rules.html create mode 100644 docs/codox/mw-engine.utils.html create mode 100644 docs/codox/mw-engine.world.html diff --git a/docs/cloverage/coverage.css b/docs/cloverage/coverage.css new file mode 100644 index 0000000..2be4e57 --- /dev/null +++ b/docs/cloverage/coverage.css @@ -0,0 +1,40 @@ +.covered { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: #558B55; +} + +.not-covered { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: red; +} + +.partial { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: orange; +} + +.not-tracked { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; +} + +.blank { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; +} + +td { + padding-right: 10px; +} + +td.with-bar { + width: 250px; + text-align: center; +} + +td.with-number { + text-align: right; +} + +td.ns-name { + min-width: 150px; + padding-right: 25px; +} diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html new file mode 100644 index 0000000..87b7ba8 --- /dev/null +++ b/docs/cloverage/index.html @@ -0,0 +1,157 @@ + + + + + Coverage Summary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Namespace Forms Forms % Lines Lines %TotalBlankInstrumented
mw-engine.core
51
103
33.12 %
12
1
20
39.39 %1151533
mw-engine.display
9
79
10.23 %
8
11
42.11 %65819
mw-engine.drainage
88
301
22.62 %
31
59
34.44 %2173090
mw-engine.flow
447
69
86.63 %
47
4
4
92.73 %1411555
mw-engine.heightmap
136
11
92.52 %
40
2
2
95.45 %1271144
mw-engine.natural-rules
51
605
7.77 %
35
5
66
37.74 %18414106
mw-engine.utils
376
162
69.89 %
82
1
29
74.11 %30135112
mw-engine.world
63
53
54.31 %
11
1
14
46.15 %891226
Totals:46.89 %57.73 %
+ + diff --git a/docs/cloverage/mw_engine/core.clj.html b/docs/cloverage/mw_engine/core.clj.html new file mode 100644 index 0000000..e540e94 --- /dev/null +++ b/docs/cloverage/mw_engine/core.clj.html @@ -0,0 +1,353 @@ + + + + mw_engine/core.clj + + + + 001  (ns ^{:doc "Functions to transform a world and run rules. +
+ + 002               +
+ + 003              Every rule is a function of two arguments, a cell and a world. If the rule +
+ + 004              fires, it returns a new cell, which should have the same values for `:x` and +
+ + 005              `:y` as the old cell. Anything else can be modified. +
+ + 006   +
+ + 007              While any function of two arguments can be used as a rule, a special high +
+ + 008              level rule language is provided by the `mw-parser` package, which compiles +
+ + 009              rules expressed in a subset of English rules into suitable functions. +
+ + 010   +
+ + 011              A cell is a map containing at least values for the keys :x, :y, and :state; +
+ + 012              a transformation should not alter the values of :x or :y, and should not +
+ + 013              return a cell without a keyword as the value of :state. Anything else is +
+ + 014              legal. +
+ + 015   +
+ + 016              A world is a two dimensional matrix (sequence of sequences) of cells, such +
+ + 017              that every cell's `:x` and `:y` properties reflect its place in the matrix. +
+ + 018              See `world.clj`. +
+ + 019   +
+ + 020              Each time the world is transformed (see `transform-world`), for each cell, +
+ + 021              rules are applied in turn until one matches. Once one rule has matched no +
+ + 022              further rules can be applied to that cell." +
+ + 023        :author "Simon Brooke"} +
+ + 024   mw-engine.core +
+ + 025    (:require [mw-engine.utils :refer [get-int-or-zero map-world]] +
+ + 026              [taoensso.timbre :as l])) +
+ + 027   +
+ + 028  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 029  ;;;; +
+ + 030  ;;;; mw-engine: the state/transition engine of MicroWorld. +
+ + 031  ;;;; +
+ + 032  ;;;; This program is free software; you can redistribute it and/or +
+ + 033  ;;;; modify it under the terms of the GNU General Public License +
+ + 034  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 035  ;;;; of the License, or (at your option) any later version. +
+ + 036  ;;;; +
+ + 037  ;;;; This program is distributed in the hope that it will be useful, +
+ + 038  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 039  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 040  ;;;; GNU General Public License for more details. +
+ + 041  ;;;; +
+ + 042  ;;;; You should have received a copy of the GNU General Public License +
+ + 043  ;;;; along with this program; if not, write to the Free Software +
+ + 044  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 045  ;;;; USA. +
+ + 046  ;;;; +
+ + 047  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 048  ;;;; +
+ + 049  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 050   +
+ + 051  (defn apply-rule +
+ + 052    "Apply a single `rule` to a `cell`. What this is about is that I want to be able, +
+ + 053     for debugging purposes, to tag a cell with the rule text of the rule which +
+ + 054     fired (and especially so when an exception is thrown. So a rule may be either +
+ + 055     an ifn, or a list (ifn source-text). This function deals with despatching +
+ + 056     on those two possibilities. `world` is also passed in in order to be able +
+ + 057     to access neighbours." +
+ + 058    ([world cell rule] +
+ + 059     (cond +
+ + 060       (ifn? rule) (apply-rule world cell rule nil) +
+ + 061       (seq? rule) (let [[afn src] rule] (apply-rule world cell afn src)))) +
+ + 062    ([world cell rule source] +
+ + 063     (let [result (apply rule (list cell world))] +
+ + 064       (cond +
+ + 065         (and result source) (merge result {:rule source}) +
+ + 066         :else result)))) +
+ + 067   +
+ + 068  (defn- apply-rules +
+ + 069    "Derive a cell from this `cell` of this `world` by applying these `rules`." +
+ + 070    [world cell rules] +
+ + 071    (cond (empty? rules) cell +
+ + 072          :else (let [result (apply-rule world cell (first rules))] +
+ + 073                  (cond result result +
+ + 074                        :else (apply-rules world cell (rest rules)))))) +
+ + 075   +
+ + 076  (defn- transform-cell +
+ + 077    "Derive a cell from this `cell` of this `world` by applying these `rules`. If an +
+ + 078     exception is thrown, cache its message on the cell and set it's state to error" +
+ + 079    [world cell rules] +
+ + 080    (try +
+ + 081      (merge +
+ + 082       (apply-rules world cell rules) +
+ + 083       {:generation (+ (get-int-or-zero cell :generation) 1)}) +
+ + 084      (catch Exception e +
+ + 085        (merge cell {:error +
+ + 086                     (format "%s at generation %d when in state %s" +
+ + 087                             (.getMessage e) +
+ + 088                             (:generation cell) +
+ + 089                             (:state cell)) +
+ + 090                     :stacktrace (map #(.toString %) (.getStackTrace e)) +
+ + 091                     :state :error})))) +
+ + 092   +
+ + 093  (defn transform-world +
+ + 094    "Return a world derived from this `world` by applying these `rules` to each cell." +
+ + 095    ([world rules] +
+ + 096     (map-world world transform-cell (list rules)))) +
+ + 097   +
+ + 098  (defn run-world +
+ + 099    "Run this world with these rules for this number of generations. +
+ + 100   +
+ + 101    * `world` a world as discussed above; +
+ + 102    * `init-rules` a sequence of rules as defined above, to be run once to initialise the world; +
+ + 103    * `rules` a sequence of rules as defined above, to be run iteratively for each generation; +
+ + 104    * `generations` an (integer) number of generations. +
+ + 105   +
+ + 106    Return the final generation of the world." +
+ + 107    [world init-rules rules generations] +
+ + 108    (reduce (fn [world iteration] +
+ + 109              (l/info "Running iteration " iteration) +
+ + 110              (transform-world world rules)) +
+ + 111            (transform-world world init-rules) +
+ + 112            (range generations))) +
+ + 113   +
+ + 114   +
+ + 115   +
+ + diff --git a/docs/cloverage/mw_engine/display.clj.html b/docs/cloverage/mw_engine/display.clj.html new file mode 100644 index 0000000..a370627 --- /dev/null +++ b/docs/cloverage/mw_engine/display.clj.html @@ -0,0 +1,203 @@ + + + + mw_engine/display.clj + + + + 001  (ns ^{:doc "Simple functions to allow a world to be visualised." +
+ + 002        :author "Simon Brooke"} +
+ + 003    mw-engine.display) +
+ + 004   +
+ + 005  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 006  ;;;; +
+ + 007  ;;;; mw-engine: the state/transition engine of MicroWorld. +
+ + 008  ;;;; +
+ + 009  ;;;; This program is free software; you can redistribute it and/or +
+ + 010  ;;;; modify it under the terms of the GNU General Public License +
+ + 011  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 012  ;;;; of the License, or (at your option) any later version. +
+ + 013  ;;;; +
+ + 014  ;;;; This program is distributed in the hope that it will be useful, +
+ + 015  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 016  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 017  ;;;; GNU General Public License for more details. +
+ + 018  ;;;; +
+ + 019  ;;;; You should have received a copy of the GNU General Public License +
+ + 020  ;;;; along with this program; if not, write to the Free Software +
+ + 021  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 022  ;;;; USA. +
+ + 023  ;;;; +
+ + 024  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 025  ;;;; +
+ + 026  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 027   +
+ + 028  (def ^:dynamic *image-base* +
+ + 029    "Base url (i.e., url of directory) from which to load tile images." +
+ + 030    "img/tiles") +
+ + 031   +
+ + 032  (defn format-css-class +
+ + 033    "Format this `state`, assumed to be a keyword indicating a state in the +
+ + 034     world, into a CSS class" +
+ + 035    [state] +
+ + 036    (subs (str state) 1)) +
+ + 037   +
+ + 038  (defn format-image-path +
+ + 039    "Render this `state`, assumed to be a keyword indicating a state in the +
+ + 040     world, into a path which should recover the corresponding image file." +
+ + 041    [state] +
+ + 042    (format "%s/%s.png" *image-base* (format-css-class state))) +
+ + 043   +
+ + 044  (defn format-mouseover [cell] +
+ + 045    (str cell)) +
+ + 046   +
+ + 047  (defn render-cell +
+ + 048    "Render this world cell as a Hiccup table cell." +
+ + 049    [cell] +
+ + 050    (let [state (:state cell)] +
+ + 051      [:td {:class (format-css-class state) :title (format-mouseover cell)} +
+ + 052       [:a {:href (format "inspect?x=%d&y=%d" (:x cell) (:y cell))} +
+ + 053        [:img {:alt (:state cell) :width 32 :height 32 :src (format-image-path state)}]]])) +
+ + 054   +
+ + 055  (defn render-world-row +
+ + 056    "Render this world `row` as a Hiccup table row." +
+ + 057    [row] +
+ + 058    (apply vector (cons :tr (map render-cell row)))) +
+ + 059   +
+ + 060  (defn render-world-table +
+ + 061    "Render this `world` as a Hiccup table." +
+ + 062    [world] +
+ + 063    (apply vector +
+ + 064      (cons :table +
+ + 065        (map render-world-row world)))) +
+ + diff --git a/docs/cloverage/mw_engine/drainage.clj.html b/docs/cloverage/mw_engine/drainage.clj.html new file mode 100644 index 0000000..5b8d0b9 --- /dev/null +++ b/docs/cloverage/mw_engine/drainage.clj.html @@ -0,0 +1,659 @@ + + + + mw_engine/drainage.clj + + + + 001  (ns ^{:doc "Experimental, probably of no interest to anyone else; attempt to +
+ + 002        compute drainage on a world, assumed to have altitudes already set +
+ + 003        from a heightmap." +
+ + 004        :author "Simon Brooke"} +
+ + 005    mw-engine.drainage +
+ + 006    (:require [mw-engine.core :refer [run-world]] +
+ + 007              [mw-engine.heightmap :as heightmap] +
+ + 008              [mw-engine.utils :refer [get-int-or-zero get-least-cell get-neighbours +
+ + 009                                       get-neighbours-with-property-value +
+ + 010                                       map-world]])) +
+ + 011   +
+ + 012  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 013  ;;;; +
+ + 014  ;;;; mw-engine: the state/transition engine of MicroWorld. +
+ + 015  ;;;; +
+ + 016  ;;;; This program is free software; you can redistribute it and/or +
+ + 017  ;;;; modify it under the terms of the GNU General Public License +
+ + 018  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 019  ;;;; of the License, or (at your option) any later version. +
+ + 020  ;;;; +
+ + 021  ;;;; This program is distributed in the hope that it will be useful, +
+ + 022  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 023  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 024  ;;;; GNU General Public License for more details. +
+ + 025  ;;;; +
+ + 026  ;;;; You should have received a copy of the GNU General Public License +
+ + 027  ;;;; along with this program; if not, write to the Free Software +
+ + 028  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 029  ;;;; USA. +
+ + 030  ;;;; +
+ + 031  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 032  ;;;; +
+ + 033  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 034   +
+ + 035   +
+ + 036  (def ^:dynamic *sealevel* 10) +
+ + 037   +
+ + 038  ;; forward declaration of flow, to allow for a wee bit of mutual recursion. +
+ + 039  (declare flow) +
+ + 040   +
+ + 041  (defn rainfall +
+ + 042    "Compute rainfall for a cell with this `gradient` west-east, given +
+ + 043    `remaining` drops to distribute, and this overall map width." +
+ + 044    [gradient remaining map-width] +
+ + 045      (cond +
+ + 046        ;; if there's no rain left in the cloud, it can't fall; +
+ + 047        (zero? remaining) +
+ + 048        0 +
+ + 049        (pos? gradient) +
+ + 050        ;; rain, on prevailing westerly wind, falls preferentially on rising ground; +
+ + 051        (int (rand gradient)) +
+ + 052        ;; rain falls randomly across the width of the map... +
+ + 053        (zero? (int (rand map-width))) 1 +
+ + 054        :else +
+ + 055        0)) +
+ + 056   +
+ + 057  (defn rain-row +
+ + 058    "Return a row like this `row`, across which rainfall has been distributed; +
+ + 059    if `rain-probability` is specified, it is the probable rainfall on a cell +
+ + 060    with no gradient." +
+ + 061    ([row] +
+ + 062     (rain-row row 1)) +
+ + 063    ([row rain-probability] +
+ + 064     (rain-row row (count row) 0 (int (* (count row) rain-probability)))) +
+ + 065    ([row map-width previous-altitude drops-in-cloud] +
+ + 066     (cond +
+ + 067       (empty? row) nil +
+ + 068       (pos? drops-in-cloud) +
+ + 069       (let [cell (first row) +
+ + 070             alt (or (:altitude cell) 0) +
+ + 071             rising (- alt previous-altitude) +
+ + 072             fall (rainfall rising drops-in-cloud map-width)] +
+ + 073         (cons +
+ + 074           (assoc cell :rainfall fall) +
+ + 075           (rain-row (rest row) map-width alt (- drops-in-cloud fall)))) +
+ + 076       :else +
+ + 077       (map +
+ + 078         #(assoc % :rainfall 0) +
+ + 079         row)))) +
+ + 080   +
+ + 081   +
+ + 082  (defn rain-world +
+ + 083    "Simulate rainfall on this `world`. TODO: Doesn't really work just now - should +
+ + 084     rain more on west-facing slopes, and less to the east of high ground" +
+ + 085    [world] +
+ + 086    (map +
+ + 087      rain-row +
+ + 088      world)) +
+ + 089   +
+ + 090   +
+ + 091  (defn flow-contributors +
+ + 092    "Return a list of the cells in this `world` which are higher than this +
+ + 093    `cell` and for which this cell is the lowest neighbour, or which are at the +
+ + 094     same altitude and have greater flow" +
+ + 095    [cell world] +
+ + 096    (filter #(map? %) +
+ + 097            (map +
+ + 098              (fn [n] +
+ + 099                (cond +
+ + 100                  (= cell (get-least-cell (get-neighbours world n) :altitude)) n +
+ + 101                  (and (= (:altitude cell) (:altitude n)) +
+ + 102                       (> (or (:flow n) 0) (or (:flow cell) 0))) n)) +
+ + 103              (get-neighbours-with-property-value +
+ + 104                world (:x cell) (:y cell) 1 :altitude +
+ + 105                (or (:altitude cell) 0) >=)))) +
+ + 106   +
+ + 107   +
+ + 108  (defn is-hollow +
+ + 109    "Detects point hollows - that is, individual cells all of whose neighbours +
+ + 110     are higher. Return true if this `cell` has an altitude lower than any of +
+ + 111     its neighbours in this `world`" +
+ + 112    [world cell] +
+ + 113    ;; quicker to count the elements of the list and compare equality of numbers +
+ + 114    ;; than recursive equality check on members, I think. But worth benchmarking. +
+ + 115    (let [neighbours (get-neighbours world cell) +
+ + 116          altitude (get-int-or-zero cell :altitude)] +
+ + 117      (= (count neighbours) +
+ + 118         (count (get-neighbours-with-property-value +
+ + 119                  world (:x cell) (:y cell) 1 :altitude altitude >))))) +
+ + 120   +
+ + 121   +
+ + 122  (defn flood-hollow +
+ + 123    "Raise the altitude of a copy of this `cell` of this `world` to the altitude +
+ + 124     of the lowest of its `neighbours`." +
+ + 125    ([_world cell neighbours] +
+ + 126      (let [lowest (get-least-cell neighbours :altitude)] +
+ + 127        (merge cell {:state :water :altitude (:altitude lowest)}))) +
+ + 128    ([world cell] +
+ + 129      (flood-hollow world cell (get-neighbours world cell)))) +
+ + 130   +
+ + 131   +
+ + 132  (defn flood-hollows +
+ + 133    "Flood all local hollows in this `world`. At this stage only floods single +
+ + 134     cell hollows." +
+ + 135    [world] +
+ + 136    (map-world world +
+ + 137               #(if (is-hollow %1 %2) (flood-hollow %1 %2) %2))) +
+ + 138   +
+ + 139   +
+ + 140  (def max-altitude 255) +
+ + 141   +
+ + 142  (defn flow-nr +
+ + 143    "Experimental non recursive flow algorithm, needs to be run on a world as +
+ + 144     many times as there are distinct altitude values. This algorithm works only +
+ + 145     if applied sequentially from the highest altitude to the lowest, see +
+ + 146     `flow-world-nr`." +
+ + 147    [cell world] +
+ + 148    (when (= (- max-altitude (get-int-or-zero cell :generation)) +
+ + 149           (get-int-or-zero cell :altitude)) +
+ + 150      (merge cell +
+ + 151             {:flow (reduce + +
+ + 152                            (map +
+ + 153                              #(+ (get-int-or-zero % :rainfall) +
+ + 154                                  (get-int-or-zero % :flow)) +
+ + 155                              (flow-contributors cell world)))}))) +
+ + 156   +
+ + 157   +
+ + 158  (def flow +
+ + 159    "Compute the total flow upstream of this `cell` in this `world`, and return a cell identical +
+ + 160    to this one but having a value of its flow property set from that computation. The function is +
+ + 161    memoised because the consequence of mapping a recursive function across an array is that many +
+ + 162    cells will be revisited - potentially many times. +
+ + 163   +
+ + 164    Flow comes from a higher cell to a lower only if the lower is the lowest neighbour of the higher." +
+ + 165    (memoize +
+ + 166     (fn [cell world] +
+ + 167       (cond +
+ + 168        (not (nil? (:flow cell))) cell +
+ + 169        (<= (or (:altitude cell) 0) *sealevel*) cell +
+ + 170        :else +
+ + 171        (merge cell +
+ + 172               {:flow (+ (:rainfall cell) +
+ + 173                         (apply + +
+ + 174                                (map (fn [neighbour] (:flow (flow neighbour world))) +
+ + 175                                     (flow-contributors cell world))))}))))) +
+ + 176   +
+ + 177   +
+ + 178  (defn flow-world-nr +
+ + 179    "Experimental non-recursive flow-world algorithm" +
+ + 180    [world] +
+ + 181    (run-world world nil (list flow-nr) max-altitude)) +
+ + 182   +
+ + 183  (defn flow-world +
+ + 184    "Return a world like this `world`, but with cells tagged with the amount of +
+ + 185     water flowing through them." +
+ + 186    [world] +
+ + 187    (map-world (rain-world world) flow)) +
+ + 188   +
+ + 189  (defn explore-lake +
+ + 190    "Return a sequence of cells starting with this `cell` in this `world` which +
+ + 191    form a contiguous lake" +
+ + 192    [_world _cell] +
+ + 193    ) +
+ + 194   +
+ + 195  (defn is-lake? +
+ + 196    "If this `cell` in this `world` is not part of a lake, return nil. If it is, +
+ + 197    return a cell like this `cell` tagged as part of a lake." +
+ + 198    [world cell] +
+ + 199    (if +
+ + 200      ;; if it's already tagged as a lake, it's a lake +
+ + 201      (:lake cell) cell +
+ + 202      (let +
+ + 203        [outflow (apply min (map :altitude (get-neighbours world cell)))] +
+ + 204        (when-not +
+ + 205          (> (:altitude cell) outflow) +
+ + 206          (assoc cell :lake true))))) +
+ + 207   +
+ + 208   +
+ + 209  (defn find-lakes +
+ + 210    [_world] +
+ + 211    ) +
+ + 212   +
+ + 213  (defn run-drainage +
+ + 214    "Create a world from the heightmap `hmap`, rain on it, and then compute river +
+ + 215     flows." +
+ + 216    [hmap] +
+ + 217    (flow-world (rain-world (flood-hollows (heightmap/apply-heightmap hmap))))) +
+ + diff --git a/docs/cloverage/mw_engine/flow.clj.html b/docs/cloverage/mw_engine/flow.clj.html new file mode 100644 index 0000000..8246897 --- /dev/null +++ b/docs/cloverage/mw_engine/flow.clj.html @@ -0,0 +1,431 @@ + + + + mw_engine/flow.clj + + + + 001  (ns mw-engine.flow +
+ + 002    "Allow flows of values between cells in the world. +
+ + 003      +
+ + 004     The design here is: a flow object is a map with the following properties: +
+ + 005   +
+ + 006     1. `:source`, whose value is a location; +
+ + 007     2. `:destination`, whose value is a location; +
+ + 008     3. `:property`, whose value is a keyword; +
+ + 009     4. `:quantity`, whose value is a positive real number. +
+ + 010   +
+ + 011     A location object is a map with the following properties: +
+ + 012   +
+ + 013     1. `:x`, whose value is a natural number not greater than the extent of the world; +
+ + 014     2. `:y`, whose value is a natural number not greater than the extent of the world. +
+ + 015   +
+ + 016     To execute a flow is transfer the quantity specified of the property specified +
+ + 017     from the cell at the source specified to the cell at the destination specified; +
+ + 018     if the source doesn't have sufficient of the property, then all it has should +
+ + 019     be transferred, but no more: properties to be flowed cannot be pulled negative. +
+ + 020      +
+ + 021     Flowing values through the world is consequently a two stage process: firstly +
+ + 022     there's a planning stage, in which all the flows to be executed are computed +
+ + 023     without changing the world, and then an execution stage, where they're all  +
+ + 024     executed. This namespace deals with mainly with execution." +
+ + 025    (:require [mw-engine.utils :refer [get-cell get-num in-bounds? merge-cell]] +
+ + 026              [taoensso.timbre :refer [info warn]])) +
+ + 027   +
+ + 028  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 029  ;;;; +
+ + 030  ;;;; mw-engine: the state/transition engine of MicroWorld. +
+ + 031  ;;;; +
+ + 032  ;;;; This program is free software; you can redistribute it and/or +
+ + 033  ;;;; modify it under the terms of the GNU General Public License +
+ + 034  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 035  ;;;; of the License, or (at your option) any later version. +
+ + 036  ;;;; +
+ + 037  ;;;; This program is distributed in the hope that it will be useful, +
+ + 038  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 039  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 040  ;;;; GNU General Public License for more details. +
+ + 041  ;;;; +
+ + 042  ;;;; You should have received a copy of the GNU General Public License +
+ + 043  ;;;; along with this program; if not, write to the Free Software +
+ + 044  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 045  ;;;; USA. +
+ + 046  ;;;; +
+ + 047  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 048  ;;;; +
+ + 049  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 050   +
+ + 051  (defn coordinate? +
+ + 052    "Return `true` if this object `o` is a valid coordinate with respect to +
+ + 053       this `world`, else `false`. Assumes square worlds." +
+ + 054    [o world] +
+ + 055    (try +
+ + 056      (and (or (zero? o) (pos-int? o)) +
+ + 057           (< o (count world))) +
+ + 058      (catch Exception e +
+ + 059        (warn (format "Not a valid coordinate: %s; %s" o (.getMessage e))) +
+ + 060        false))) +
+ + 061   +
+ + 062  (defn location? +
+ + 063    "Return `true` if this object `o` is a location as defined above with respect to +
+ + 064     this `world`, else `false`." +
+ + 065    [o world] +
+ + 066    (try +
+ + 067      (and (map? o) +
+ + 068           (integer? (:x o)) +
+ + 069           (integer? (:y o)) +
+ + 070           (in-bounds? world (:x o) (:y o))) +
+ + 071      (catch Exception e +
+ + 072        (warn (format "Not a valid location: %s; %s" o (.getMessage e))) +
+ + 073        false))) +
+ + 074   +
+ + 075  (defn flow? +
+ + 076    "Return `true` if this object `o` is a flow as defined above with respect to +
+ + 077     this `world`, else `false`. Assumes square worlds." +
+ + 078    [o world] +
+ + 079    (try +
+ + 080      (and (map? o) +
+ + 081           (location? (:source o) world) +
+ + 082           (location? (:destination o) world) +
+ + 083           (keyword? (:property o)) +
+ + 084           (pos? (:quantity o))) +
+ + 085      (catch Exception e +
+ + 086        (warn (format "Not a valid flow: %s; %s" o (.getMessage e))) +
+ + 087        false))) +
+ + 088   +
+ + 089  (defn execute +
+ + 090    "Return a world like this `world`, except with the quantity of the property +
+ + 091     described in this `flow` object transferred from the source of that flow +
+ + 092     to its destination." +
+ + 093    [world flow] +
+ + 094    (try +
+ + 095      (let [sx (-> flow :source :x) +
+ + 096            sy (-> flow :source :y) +
+ + 097            source (get-cell world sx sy) +
+ + 098            dx (-> flow :destination :x) +
+ + 099            dy (-> flow :destination :y) +
+ + 100            dest (get-cell world dx dy) +
+ + 101            p (:property flow) +
+ + 102            q (min (:quantity flow) (get-num source p)) +
+ + 103            s' (assoc source p (- (source p) q)) +
+ + 104            d' (assoc dest p (+ (get-num dest p) q))] +
+ + 105        (if (= q (:quantity flow)) +
+ + 106          (info (format "Moving %f units of %s from %d,%d to %d,%d" +
+ + 107                      (float q) (name p) sx sy dx dy)) +
+ + 108          (warn (format "Moving %s from %d,%d to %d,%d; %f units ordered but only %f available" +
+ + 109                        (name p) sx sy dx dy (float (:quantity flow)) (float q)))) +
+ + 110        (merge-cell (merge-cell world s') d')) +
+ + 111      (catch Exception e +
+ + 112        (warn (format "Failed to execute flow %s: %s" flow (.getMessage e))) +
+ + 113        ;; return the world unmodified. +
+ + 114        world))) +
+ + 115   +
+ + 116  (defn execute-flows +
+ + 117    "Return a world like this `world`, but with each of these flows executed." +
+ + 118    [world flows] +
+ + 119    (reduce execute world (filter #(flow? % world) flows))) +
+ + 120   +
+ + 121  ;; building blocks for compiled flow rules +
+ + 122   +
+ + 123  (defmacro create-location +
+ + 124    [cell] +
+ + 125    `(select-keys ~cell [:x :y])) +
+ + 126   +
+ + 127  (defmacro create-flow-quantity +
+ + 128    [source dest prop quantity] +
+ + 129    `{:source (create-location ~source) +
+ + 130      :destination (create-location ~dest) +
+ + 131      :prop ~prop +
+ + 132      :quantity ~quantity}) +
+ + 133   +
+ + 134  (defmacro create-flow-fraction +
+ + 135    [source dest prop fraction] +
+ + 136    `(create-flow-quantity ~source ~dest ~prop +
+ + 137                           (* ~fraction (get-num ~source ~prop)))) +
+ + 138   +
+ + 139  (defmacro create-flow-percent +
+ + 140    [source dest prop percent] +
+ + 141    `(create-flow-fraction ~source ~dest ~prop (/ ~percent 100))) +
+ + diff --git a/docs/cloverage/mw_engine/heightmap.clj.html b/docs/cloverage/mw_engine/heightmap.clj.html new file mode 100644 index 0000000..3926ecb --- /dev/null +++ b/docs/cloverage/mw_engine/heightmap.clj.html @@ -0,0 +1,389 @@ + + + + mw_engine/heightmap.clj + + + + 001  (ns ^{:doc "Functions to apply a heightmap to a world. +
+ + 002               +
+ + 003              Heightmaps are considered only as greyscale images, so colour is redundent +
+ + 004              (will be ignored). Darker shades are higher." +
+ + 005        :author "Simon Brooke"} +
+ + 006    mw-engine.heightmap +
+ + 007    (:require [mikera.image.core :refer [load-image filter-image]] +
+ + 008              [mikera.image.filters :as filters] +
+ + 009              [mw-engine.utils :refer [get-int get-neighbours map-world]] +
+ + 010              [mw-engine.world :refer [make-world]])) +
+ + 011   +
+ + 012  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 013  ;;;; +
+ + 014  ;;;; mw-engine: the state/transition engine of MicroWorld. +
+ + 015  ;;;; +
+ + 016  ;;;; This program is free software; you can redistribute it and/or +
+ + 017  ;;;; modify it under the terms of the GNU General Public License +
+ + 018  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 019  ;;;; of the License, or (at your option) any later version. +
+ + 020  ;;;; +
+ + 021  ;;;; This program is distributed in the hope that it will be useful, +
+ + 022  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 023  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 024  ;;;; GNU General Public License for more details. +
+ + 025  ;;;; +
+ + 026  ;;;; You should have received a copy of the GNU General Public License +
+ + 027  ;;;; along with this program; if not, write to the Free Software +
+ + 028  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 029  ;;;; USA. +
+ + 030  ;;;; +
+ + 031  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 032  ;;;; +
+ + 033  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 034   +
+ + 035  (defn tag-property +
+ + 036    "Set the value of this `property` of this cell from the corresponding pixel of this `heightmap`. +
+ + 037     If the heightmap you supply is smaller than the world, this will break. +
+ + 038   +
+ + 039     * `world` not actually used, but present to enable this function to be +
+ + 040       passed as an argument to `mw-engine.utils/map-world`, q.v. +
+ + 041     * `cell` a cell, as discussed in world.clj, q.v. Alternatively, a map; +
+ + 042     * `property` the property (normally a keyword) whose value will be set on the cell. +
+ + 043     * `heightmap` an (ideally) greyscale image, whose x and y dimensions should +
+ + 044       exceed those of the world of which the `cell` forms part." +
+ + 045    ([_ cell property heightmap] +
+ + 046      (tag-property cell property heightmap)) +
+ + 047    ([cell property heightmap] +
+ + 048      (merge cell +
+ + 049             {property +
+ + 050              (+ (get-int cell property) +
+ + 051                 (- 256 +
+ + 052                    (abs +
+ + 053                      (mod +
+ + 054                        (.getRGB heightmap +
+ + 055                          (get-int cell :x) +
+ + 056                          (get-int cell :y)) 256))))}))) +
+ + 057   +
+ + 058  (defn tag-gradient +
+ + 059    "Set the `gradient` property of this `cell` of this `world` to the difference in +
+ + 060     altitude between its highest and lowest neghbours." +
+ + 061    [world cell] +
+ + 062    (let [heights (remove nil? (map :altitude (get-neighbours world cell))) +
+ + 063          highest (cond (empty? heights) 0 ;; shouldn't happen +
+ + 064                    :else (apply max heights)) +
+ + 065          lowest (cond (empty? heights) 0 ;; shouldn't +
+ + 066                   :else (apply min heights)) +
+ + 067          gradient (- highest lowest)] +
+ + 068      (merge cell {:gradient gradient}))) +
+ + 069   +
+ + 070  (defn tag-gradients +
+ + 071    "Set the `gradient` property of each cell in this `world` to the difference in +
+ + 072     altitude between its highest and lowest neghbours." +
+ + 073    [world] +
+ + 074    (map-world world tag-gradient)) +
+ + 075   +
+ + 076  (defn tag-altitude +
+ + 077    "Set the altitude of this cell from the corresponding pixel of this heightmap. +
+ + 078     If the heightmap you supply is smaller than the world, this will break. +
+ + 079   +
+ + 080     * `world` not actually used, but present to enable this function to be +
+ + 081       passed as an argument to `mw-engine.utils/map-world`, q.v.; +
+ + 082     * `cell` a cell, as discussed in world.clj, q.v. Alternatively, a map; +
+ + 083     * `heightmap` an (ideally) greyscale image, whose x and y dimensions should +
+ + 084       exceed those of the world of which the `cell` forms part." +
+ + 085    ([_ cell heightmap] +
+ + 086      (tag-property cell :altitude heightmap)) +
+ + 087    ([cell heightmap] +
+ + 088      (tag-property cell :altitude heightmap))) +
+ + 089   +
+ + 090  (defn apply-heightmap +
+ + 091    "Apply the image file loaded from this path to this world, and return a world whose +
+ + 092    altitudes are modified (added to) by the altitudes in the heightmap. It is assumed that +
+ + 093    the heightmap is at least as large in x and y dimensions as the world. Note that, in +
+ + 094    addition to setting the `:altitude` of each cell, this function also sets the `:gradient`. +
+ + 095   +
+ + 096    * `world` a world, as defined in `world.clj`, q.v.; if world is not supplied, +
+ + 097      a world the size of the heightmap will be created; +
+ + 098    * `imagepath` a file path or URL which indicates an (ideally greyscale) image file." +
+ + 099    ([world imagepath] +
+ + 100      (let [heightmap (filter-image +
+ + 101                       (load-image imagepath) +
+ + 102                        (filters/grayscale))] +
+ + 103        (map-world +
+ + 104          (map-world world tag-altitude (list heightmap)) +
+ + 105          tag-gradient))) +
+ + 106     ([imagepath] +
+ + 107      (let [heightmap (filter-image +
+ + 108                        (load-image imagepath) +
+ + 109                        (filters/grayscale)) +
+ + 110            world (make-world (.getWidth heightmap) (.getHeight heightmap))] +
+ + 111        (map-world +
+ + 112          (map-world world tag-altitude (list heightmap)) +
+ + 113          tag-gradient)))) +
+ + 114   +
+ + 115  (defn apply-valuemap +
+ + 116    "Generalised from apply-heightmap, set an arbitrary property on each cell +
+ + 117     of this `world` from the values in this (ideally greyscale) heightmap. +
+ + 118   +
+ + 119     * `world` a world, as defined in `world.clj`, q.v.; +
+ + 120     * `imagepath` a file path or URL which indicates an (ideally greyscale) image file; +
+ + 121     * `property` the property of each cell whose value should be added to from the +
+ + 122        intensity of the corresponding cell of the image." +
+ + 123    [world imagepath property] +
+ + 124      (let [heightmap (filter-image +
+ + 125                        (load-image imagepath) +
+ + 126                        (filters/grayscale))] +
+ + 127        (map-world world tag-property (list property heightmap)))) +
+ + diff --git a/docs/cloverage/mw_engine/natural_rules.clj.html b/docs/cloverage/mw_engine/natural_rules.clj.html new file mode 100644 index 0000000..d3b0094 --- /dev/null +++ b/docs/cloverage/mw_engine/natural_rules.clj.html @@ -0,0 +1,560 @@ + + + + mw_engine/natural_rules.clj + + + + 001  (ns ^{:doc "A set of MicroWorld rules describing a simplified natural ecosystem. +
+ + 002   +
+ + 003              Since the completion of the rule language this is more or less obsolete - +
+ + 004              there are still a few things that you can do with rules written in Clojure +
+ + 005              that you can't do in the rule language, but not many and I doubt they're +
+ + 006              important. " +
+ + 007         :author " Simon Brooke "} +
+ + 008    mw-engine.natural-rules +
+ + 009    (:require [mw-engine.utils :refer [get-int get-neighbours get-neighbours-with-state member?]])) +
+ + 010   +
+ + 011  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 012  ;;;; +
+ + 013  ;;;; mw-engine: the state/transition engine of MicroWorld. +
+ + 014  ;;;; +
+ + 015  ;;;; This program is free software; you can redistribute it and/or +
+ + 016  ;;;; modify it under the terms of the GNU General Public License +
+ + 017  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 018  ;;;; of the License, or (at your option) any later version. +
+ + 019  ;;;; +
+ + 020  ;;;; This program is distributed in the hope that it will be useful, +
+ + 021  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 022  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 023  ;;;; GNU General Public License for more details. +
+ + 024  ;;;; +
+ + 025  ;;;; You should have received a copy of the GNU General Public License +
+ + 026  ;;;; along with this program; if not, write to the Free Software +
+ + 027  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 028  ;;;; USA. +
+ + 029  ;;;; +
+ + 030  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 031  ;;;; +
+ + 032  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 033   +
+ + 034  ;; treeline at arbitrary altitude. +
+ + 035  (def treeline 150) +
+ + 036   +
+ + 037  ;; waterline also at arbitrary altitude. +
+ + 038  (def waterline 10) +
+ + 039   +
+ + 040  ;; and finally snowline is also arbitrary. +
+ + 041  (def snowline 200) +
+ + 042   +
+ + 043  ;; Rare chance of lightning strikes +
+ + 044  (def lightning-probability 500) +
+ + 045   +
+ + 046  ;; rules describing vegetation +
+ + 047  (def vegetation-rules +
+ + 048    (list +
+ + 049      ;; Randomly, birds plant tree seeds into grassland. +
+ + 050      (fn [cell _] (cond (and (= (:state cell) :grassland)(< (rand 10) 1))(merge cell {:state :heath}))) +
+ + 051      ;; heath below the treeline grows gradually into forest, providing browsing pressure is not to high +
+ + 052      (fn [cell _] +
+ + 053        (cond (and +
+ + 054                (= (:state cell) :heath) +
+ + 055                ;; browsing limit really ought to vary with soil fertility, but... +
+ + 056                (< (+ (get-int cell :deer)(get-int cell :sheep)) 6) +
+ + 057                (< (get-int cell :altitude) treeline)) +
+ + 058          (merge cell {:state :scrub}))) +
+ + 059      (fn [cell _] (cond (= (:state cell) :scrub) (merge cell {:state :forest}))) +
+ + 060      ;; Forest on fertile land grows to climax +
+ + 061      (fn [cell _] +
+ + 062        (cond +
+ + 063          (and +
+ + 064            (= (:state cell) :forest) +
+ + 065            (> (get-int cell :fertility) 10)) +
+ + 066          (merge cell {:state :climax}))) +
+ + 067      ;; Climax forest occasionally catches fire (e.g. lightning strikes) +
+ + 068      (fn [cell _] (cond (and (= (:state cell) :climax)(< (rand lightning-probability) 1)) (merge cell {:state :fire}))) +
+ + 069      ;; Climax forest neighbouring fires is likely to catch fire +
+ + 070      (fn [cell world] +
+ + 071        (cond +
+ + 072          (and (= (:state cell) :climax) +
+ + 073               (< (rand 3) 1) +
+ + 074               (not (empty? (get-neighbours-with-state world (:x cell) (:y cell) 1 :fire)))) +
+ + 075          (merge cell {:state :fire}))) +
+ + 076      ;; After fire we get waste +
+ + 077      (fn [cell _] (cond (= (:state cell) :fire) (merge cell {:state :waste}))) +
+ + 078      ;; And after waste we get pioneer species; if there's a woodland seed +
+ + 079      ;; source, it's going to be heath, otherwise grassland. +
+ + 080      (fn [cell world] +
+ + 081        (cond +
+ + 082          (and (= (:state cell) :waste) +
+ + 083               (not +
+ + 084                 (empty? +
+ + 085                   (flatten +
+ + 086                     (list +
+ + 087                       (get-neighbours-with-state world (:x cell) (:y cell) 1 :scrub) +
+ + 088                       (get-neighbours-with-state world (:x cell) (:y cell) 1 :forest) +
+ + 089                       (get-neighbours-with-state world (:x cell) (:y cell) 1 :climax)))))) +
+ + 090          (merge cell {:state :heath}))) +
+ + 091      (fn [cell _] +
+ + 092        (cond (= (:state cell) :waste) +
+ + 093          (merge cell {:state :grassland}))) +
+ + 094      ;; Forest increases soil fertility +
+ + 095      (fn [cell _] +
+ + 096        (cond (member? (:state cell) '(:forest :climax)) +
+ + 097          (merge cell {:fertility (+ (get-int cell :fertility) 1)}))))) +
+ + 098   +
+ + 099   +
+ + 100  ;; rules describing herbivore behaviour +
+ + 101  (def herbivore-rules +
+ + 102    (list +
+ + 103      ;; if there are too many deer for the fertility of the area to sustain, +
+ + 104      ;; some die or move on. +
+ + 105      (fn [cell _] +
+ + 106        (cond (> (get-int cell :deer) (get-int cell :fertility)) +
+ + 107          (merge cell {:deer (get-int cell :fertility)}))) +
+ + 108      ;; deer arrive occasionally at the edge of the map. +
+ + 109      (fn [cell world] +
+ + 110        (cond (and (< (count (get-neighbours world cell)) 8) +
+ + 111                   (< (rand 50) 1) +
+ + 112                   (> (get-int cell :fertility) 0) +
+ + 113                   (= (get-int cell :deer) 0)) +
+ + 114          (merge cell {:deer 2}))) +
+ + 115      ;; deer gradually spread through the world by breeding or migrating. +
+ + 116      (fn [cell world] +
+ + 117        (let [n (apply + (map #(get-int % :deer) (get-neighbours world cell)))] +
+ + 118          (cond (and +
+ + 119                  (> (get-int cell :fertility) 0) +
+ + 120                  (= (get-int cell :deer) 0) +
+ + 121                  (>= n 2)) +
+ + 122            (merge cell {:deer (int (/ n 2))})))) +
+ + 123      ;; deer breed. +
+ + 124      (fn [cell _] +
+ + 125        (cond +
+ + 126          (>= (get-int cell :deer) 2) +
+ + 127          (merge cell {:deer (int (* (:deer cell) 2))}))))) +
+ + 128   +
+ + 129    ;; rules describing predator behaviour +
+ + 130    (def predator-rules +
+ + 131      (list +
+ + 132       ;; wolves eat deer +
+ + 133       (fn [cell _] +
+ + 134        (cond +
+ + 135         (>= (get-int cell :wolves) 1) +
+ + 136         (merge cell {:deer (max 0 (- (get-int cell :deer) (get-int cell :wolves)))}))) +
+ + 137  ;;      ;; not more than eight wolves in a pack, for now (hack because wolves are not dying) +
+ + 138  ;;      (fn [cell world] +
+ + 139  ;;        (cond (> (get-int cell :wolves) 8) (merge cell {:wolves 8}))) +
+ + 140      ;; if there are not enough deer to sustain the get-int of wolves, +
+ + 141      ;; some wolves die or move on. (doesn't seem to be working?) +
+ + 142      (fn [cell _] +
+ + 143         (cond (> (get-int cell :wolves) (get-int cell :deer)) +
+ + 144           (merge cell {:wolves 0}))) +
+ + 145      ;; wolves arrive occasionally at the edge of the map. +
+ + 146      (fn [cell world] +
+ + 147        (cond (and (< (count (get-neighbours world cell)) 8) +
+ + 148                   (< (rand 50) 1) +
+ + 149                   (not (= (:state cell) :water)) +
+ + 150                   (= (get-int cell :wolves) 0)) +
+ + 151          (merge cell {:wolves 2}))) +
+ + 152      ;; wolves gradually spread through the world by breeding or migrating. +
+ + 153      (fn [cell world] +
+ + 154        (let [n (apply + (map #(get-int % :wolves) (get-neighbours world cell)))] +
+ + 155          (cond (and +
+ + 156                  (not (= (:state cell) :water)) +
+ + 157                  (= (get-int cell :wolves) 0) +
+ + 158                  (>= n 2)) +
+ + 159            (merge cell {:wolves 2})))) +
+ + 160      ;; wolves breed. +
+ + 161      (fn [cell _] +
+ + 162        (cond +
+ + 163          (>= (get-int cell :wolves) 2) +
+ + 164          (merge cell {:wolves (int (* (:wolves cell) 2))}))))) +
+ + 165   +
+ + 166   +
+ + 167    ;; rules which initialise the world +
+ + 168    (def init-rules +
+ + 169      (list +
+ + 170       ;; below the waterline, we have water. +
+ + 171       (fn [cell _] +
+ + 172         (cond (and (= (:state cell) :new) (< (get-int cell :altitude) waterline)) (merge cell {:state :water}))) +
+ + 173       ;; above the snowline, we have snow. +
+ + 174       (fn [cell _] +
+ + 175         (cond (and (= (:state cell) :new) (> (get-int cell :altitude) snowline)) (merge cell {:state :snow}))) +
+ + 176       ;; in between, we have a wasteland. +
+ + 177       (fn [cell _] (cond (= (:state cell) :new) (merge cell {:state :grassland}))))) +
+ + 178   +
+ + 179   +
+ + 180  (def natural-rules (flatten +
+ + 181                      (list +
+ + 182                       vegetation-rules +
+ + 183                       herbivore-rules +
+ + 184                       predator-rules))) +
+ + diff --git a/docs/cloverage/mw_engine/utils.clj.html b/docs/cloverage/mw_engine/utils.clj.html new file mode 100644 index 0000000..0ff50d2 --- /dev/null +++ b/docs/cloverage/mw_engine/utils.clj.html @@ -0,0 +1,911 @@ + + + + mw_engine/utils.clj + + + + 001  (ns ^{:doc " Utility functions needed by MicroWorld and, specifically, in the +
+ + 002        interpretation of MicroWorld rule." +
+ + 003        :author "Simon Brooke"} +
+ + 004   mw-engine.utils +
+ + 005    (:require +
+ + 006     [clojure.math.combinatorics :as combo])) +
+ + 007   +
+ + 008  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 009  ;;;; +
+ + 010  ;;;; mw-engine: the state/transition engine of MicroWorld. +
+ + 011  ;;;; +
+ + 012  ;;;; This program is free software; you can redistribute it and/or +
+ + 013  ;;;; modify it under the terms of the GNU General Public License +
+ + 014  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 015  ;;;; of the License, or (at your option) any later version. +
+ + 016  ;;;; +
+ + 017  ;;;; This program is distributed in the hope that it will be useful, +
+ + 018  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 019  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 020  ;;;; GNU General Public License for more details. +
+ + 021  ;;;; +
+ + 022  ;;;; You should have received a copy of the GNU General Public License +
+ + 023  ;;;; along with this program; if not, write to the Free Software +
+ + 024  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 025  ;;;; USA. +
+ + 026  ;;;; +
+ + 027  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 028  ;;;; +
+ + 029  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 030   +
+ + 031  (defn member? +
+ + 032    "Return 'true' if elt is a member of col, else 'false'." +
+ + 033    [elt col]  +
+ + 034    (contains? (set col) elt)) +
+ + 035   +
+ + 036  (defn get-int-or-zero +
+ + 037    "Return the value of this `property` from this `map` if it is a integer; +
+ + 038     otherwise return zero." +
+ + 039    [map property] +
+ + 040    (let [value (map property)] +
+ + 041      (if (integer? value) value 0))) +
+ + 042   +
+ + 043  (defn init-generation +
+ + 044    "Return a cell like this `cell`, but having a value for :generation, zero if +
+ + 045     the cell passed had no integer value for generation, otherwise the value +
+ + 046     taken from the cell passed. The `world` argument is present only for +
+ + 047     consistency with the rule engine and is ignored." +
+ + 048    [_ cell] +
+ + 049    (merge cell {:generation (get-int-or-zero cell :generation)})) +
+ + 050   +
+ + 051  (defn in-bounds +
+ + 052    "True if x, y are in bounds for this world (i.e., there is a cell at x, y) +
+ + 053     else false. *DEPRECATED*: it's a predicate, prefer `in-bounds?`. +
+ + 054   +
+ + 055    * `world` a world as defined in [world.clj](mw-engine.world.html); +
+ + 056    * `x` a number which may or may not be a valid x coordinate within that world; +
+ + 057    * `y` a number which may or may not be a valid y coordinate within that world." +
+ + 058    {:deprecated "1.1.7"} +
+ + 059    [world x y] +
+ + 060    (and (>= x 0) (>= y 0) (< y (count world)) (< x (count (first world))))) +
+ + 061   +
+ + 062  (defn in-bounds? +
+ + 063    "True if x, y are in bounds for this world (i.e., there is a cell at x, y) +
+ + 064     else false. +
+ + 065   +
+ + 066    * `world` a world as defined in [world.clj](mw-engine.world.html); +
+ + 067    * `x` a number which may or may not be a valid x coordinate within that world; +
+ + 068    * `y` a number which may or may not be a valid y coordinate within that world." +
+ + 069    {:added "1.1.7"} +
+ + 070    [world x y] +
+ + 071    (and (>= x 0) (>= y 0) (< y (count world)) (< x (count (first world))))) +
+ + 072   +
+ + 073  (defn map-world-n-n +
+ + 074    "Wholly non-parallel map world implementation; see documentation for `map-world`." +
+ + 075    ([world function] +
+ + 076     (map-world-n-n world function nil)) +
+ + 077    ([world function additional-args] +
+ + 078     (into [] +
+ + 079           (map (fn [row] +
+ + 080                  (into [] (map +
+ + 081                            #(apply function +
+ + 082                                    (cons world (cons % additional-args))) +
+ + 083                            row))) +
+ + 084                world)))) +
+ + 085   +
+ + 086   +
+ + 087  (defn map-world-p-p +
+ + 088    "Wholly parallel map-world implementation; see documentation for `map-world`." +
+ + 089    ([world function] +
+ + 090     (map-world-p-p world function nil)) +
+ + 091    ([world function additional-args] +
+ + 092     (into [] +
+ + 093           (pmap (fn [row] +
+ + 094                   (into [] (pmap +
+ + 095                             #(apply function +
+ + 096                                     (cons world (cons % additional-args))) +
+ + 097                             row))) +
+ + 098                 world)))) +
+ + 099   +
+ + 100  (defn map-world +
+ + 101    "Apply this `function` to each cell in this `world` to produce a new world. +
+ + 102    the arguments to the function will be the world, the cell, and any +
+ + 103    `additional-args` supplied. Note that we parallel map over rows but +
+ + 104    just map over cells within a row. That's because it isn't worth starting +
+ + 105    a new thread for each cell, but there may be efficiency gains in +
+ + 106    running rows in parallel." +
+ + 107    ([world function] +
+ + 108     (map-world world function nil)) +
+ + 109    ([world function additional-args] +
+ + 110     (into [] +
+ + 111           (pmap (fn [row] +
+ + 112                   (into [] (map +
+ + 113                             #(apply function +
+ + 114                                     (cons world (cons % additional-args))) +
+ + 115                             row))) +
+ + 116                 world)))) +
+ + 117   +
+ + 118  (defn get-cell +
+ + 119    "Return the cell a x, y in this world, if any. +
+ + 120   +
+ + 121    * `world` a world as defined in [world.clj](mw-engine.world.html); +
+ + 122    * `x` a number which may or may not be a valid x coordinate within that world; +
+ + 123    * `y` a number which may or may not be a valid y coordinate within that world." +
+ + 124    [world x y] +
+ + 125    (when (in-bounds? world x y) +
+ + 126      (nth (nth world y) x))) +
+ + 127   +
+ + 128  (defn get-int +
+ + 129    "Get the value of a property expected to be an integer from a map; if not +
+ + 130     present (or not an integer) return 0. +
+ + 131   +
+ + 132    * `map` a map; +
+ + 133    * `key` a symbol or keyword, presumed to be a key into the `map`." +
+ + 134    [map key] +
+ + 135    (if (map? map) +
+ + 136      (let [v (map key)] +
+ + 137        (cond (and v (integer? v)) v +
+ + 138              :else 0)) +
+ + 139      (throw (Exception. "No map passed?")))) +
+ + 140   +
+ + 141  (defn get-num +
+ + 142    "Get the value of a property expected to be a number from a map; if not +
+ + 143     present (or not a number) return 0. +
+ + 144   +
+ + 145    * `map` a map; +
+ + 146    * `key` a symbol or keyword, presumed to be a key into the `map`." +
+ + 147    [map key] +
+ + 148    (if (map? map) +
+ + 149      (let [v (map key)] +
+ + 150        (cond (and v (number? v)) v +
+ + 151              :else 0)) +
+ + 152      (throw (Exception. "No map passed?")))) +
+ + 153   +
+ + 154  (defn population +
+ + 155    "Return the population of this species in this cell. Currently a synonym for +
+ + 156     `get-int`, but may not always be (depending whether species are later +
+ + 157     implemented as actors) +
+ + 158   +
+ + 159    * `cell` a map; +
+ + 160    * `species` a keyword representing a species which may populate that cell." +
+ + 161    [cell species] +
+ + 162    (get-int cell species)) +
+ + 163   +
+ + 164  (def memo-get-neighbours +
+ + 165    "Memoised get neighbours is more efficient when running deeply recursive +
+ + 166     algorithms on the same world. But it's less efficient when running the +
+ + 167     engine in its normal iterative style, because then we will rarely call +
+ + 168     get naighbours on the same cell of the same world twice." +
+ + 169    (memoize +
+ + 170     (fn [world x y depth] +
+ + 171       (remove nil? +
+ + 172               (map #(get-cell world (first %) (first (rest %))) +
+ + 173                    (remove #(= % (list x y)) +
+ + 174                            (combo/cartesian-product +
+ + 175                             (range (- x depth) (+ x depth 1)) +
+ + 176                             (range (- y depth) (+ y depth 1))))))))) +
+ + 177   +
+ + 178  (defn get-neighbours +
+ + 179    "Get the neighbours to distance depth of a cell in this world. +
+ + 180   +
+ + 181      Several overloads: +
+ + 182      * `world` a world, as described in [world.clj](mw-engine.world.html); +
+ + 183      * `cell` a cell within that world +
+ + 184      Gets immediate neighbours of the specified cell. +
+ + 185   +
+ + 186      * `world` a world, as described in[world.clj](mw-engine.world.html); +
+ + 187      * `cell` a cell within that world +
+ + 188      * `depth` an integer representing the depth to search from the +
+ + 189        `cell` +
+ + 190      Gets neighbours within the specified distance of the cell. +
+ + 191   +
+ + 192      * `world` a world, as described in[world.clj](mw-engine.world.html); +
+ + 193      * `x` an integer representing an x coordinate in that world; +
+ + 194      * `y` an integer representing an y coordinate in that world; +
+ + 195      * `depth` an integer representing the distance from [x,y] that +
+ + 196        should be searched +
+ + 197      Gets the neighbours within the specified distance of the cell at +
+ + 198      coordinates [x,y] in this world." +
+ + 199    ([world x y depth] +
+ + 200     (memo-get-neighbours world x y depth)) +
+ + 201    ([world cell depth] +
+ + 202     (memo-get-neighbours world (:x cell) (:y cell) depth)) +
+ + 203    ([world cell] +
+ + 204     (memo-get-neighbours world (:x cell) (:y cell) 1))) +
+ + 205   +
+ + 206  (defn get-neighbours-with-property-value +
+ + 207    "Get the neighbours to distance depth of the cell at x, y in this world which +
+ + 208     have this value for this property. +
+ + 209   +
+ + 210      * `world` a world, as described in [world.clj](mw-engine.world.html); +
+ + 211      * `cell` a cell within that world; +
+ + 212      * `depth` an integer representing the distance from [x,y] that +
+ + 213        should be searched (optional); +
+ + 214      * `property` a keyword representing a property of the neighbours; +
+ + 215      * `value` a value of that property (or, possibly, the name of another); +
+ + 216      * `op` a comparator function to use in place of `=` (optional). +
+ + 217   +
+ + 218     It gets messy." +
+ + 219    ([world x y depth property value op] +
+ + 220     (filter +
+ + 221      #(eval +
+ + 222        (list op +
+ + 223              (or (get % property) (get-int % property)) +
+ + 224              value)) +
+ + 225      (get-neighbours world x y depth))) +
+ + 226    ([world x y depth property value] +
+ + 227     (get-neighbours-with-property-value world x y depth property value =)) +
+ + 228    ([world cell depth property value] +
+ + 229     (get-neighbours-with-property-value world (:x cell) (:y cell) depth +
+ + 230                                         property value)) +
+ + 231    ([world cell property value] +
+ + 232     (get-neighbours-with-property-value world cell 1 +
+ + 233                                         property value))) +
+ + 234   +
+ + 235  (defn get-neighbours-with-state +
+ + 236    "Get the neighbours to distance depth of the cell at x, y in this world which +
+ + 237     have this state. +
+ + 238   +
+ + 239      * `world` a world, as described in [world.clj](mw-engine.world.html); +
+ + 240      * `cell` a cell within that world; +
+ + 241      * `depth` an integer representing the distance from [x,y] that +
+ + 242        should be searched; +
+ + 243      * `state` a keyword representing a state in the world." +
+ + 244    ([world x y depth state] +
+ + 245     (filter #(= (:state %) state) (get-neighbours world x y depth))) +
+ + 246    ([world cell depth state] +
+ + 247     (get-neighbours-with-state world (:x cell) (:y cell) depth state)) +
+ + 248    ([world cell state] +
+ + 249     (get-neighbours-with-state world cell 1 state))) +
+ + 250   +
+ + 251  (defn get-least-cell +
+ + 252    "Return the cell from among these `cells` which has the lowest numeric value +
+ + 253    for this `property`." +
+ + 254    [cells property] +
+ + 255    (first (sort-by property (filter #(number? (property %)) cells)))) +
+ + 256   +
+ + 257  (defn get-most-cell +
+ + 258    "Return the cell from among these `cells` which has the highest numeric value +
+ + 259    for this `property`." +
+ + 260    [cells property] +
+ + 261    (last (sort-by property (filter #(number? (property %)) cells)))) +
+ + 262   +
+ + 263  (defn- set-cell-property +
+ + 264    "If this `cell`s x and y properties are equal to these `x` and `y` values, +
+ + 265     return a cell like this cell but with the value of this `property` set to +
+ + 266     this `value`. Otherwise, just return this `cell`." +
+ + 267    [cell x y property value] +
+ + 268    (cond +
+ + 269      (and (= x (:x cell)) (= y (:y cell))) +
+ + 270      (merge cell {property value :rule "Set by user"}) +
+ + 271      :else cell)) +
+ + 272   +
+ + 273  (defn set-property +
+ + 274    "Return a world like this `world` but with the value of exactly one `property` +
+ + 275     of one `cell` changed to this `value`" +
+ + 276    ([world cell property value] +
+ + 277     (set-property world (:x cell) (:y cell) property value)) +
+ + 278    ([world x y property value] +
+ + 279     (apply +
+ + 280      vector ;; we want a vector of vectors, not a list of lists, for efficiency +
+ + 281      (map +
+ + 282       (fn [row] +
+ + 283         (apply +
+ + 284          vector +
+ + 285          (map #(set-cell-property % x y property value) +
+ + 286               row))) +
+ + 287       world)))) +
+ + 288   +
+ + 289  (defn merge-cell +
+ + 290    "Return a world like this `world`, but merge the values from this `cell` with +
+ + 291     those from the cell in the world with the same co-ordinates" +
+ + 292    [world cell] +
+ + 293    (if (in-bounds? world (:x cell) (:y cell)) +
+ + 294      (map-world world +
+ + 295                 #(if +
+ + 296                   (and +
+ + 297                    (= (:x cell) (:x %2)) +
+ + 298                    (= (:y cell) (:y %2))) +
+ + 299                    (merge %2 cell) +
+ + 300                    %2)) +
+ + 301      world)) +
+ + diff --git a/docs/cloverage/mw_engine/world.clj.html b/docs/cloverage/mw_engine/world.clj.html new file mode 100644 index 0000000..2bfd094 --- /dev/null +++ b/docs/cloverage/mw_engine/world.clj.html @@ -0,0 +1,275 @@ + + + + mw_engine/world.clj + + + + 001  (ns ^{:doc "Functions to create and to print two dimensional cellular automata. +
+ + 002               +
+ + 003              Nothing in this namespace should determine what states are possible within +
+ + 004              the automaton, except for the initial state, :new. +
+ + 005   +
+ + 006              A cell is a map containing at least values for the keys `:x`, `:y`, and `:state`. +
+ + 007   +
+ + 008              A world is a two dimensional matrix (sequence of sequences) of cells, such +
+ + 009              that every cell's `:x` and `:y` properties reflect its place in the matrix." +
+ + 010        :author "Simon Brooke"} +
+ + 011   mw-engine.world +
+ + 012    (:require [clojure.string :as string] +
+ + 013              [mw-engine.utils :refer [population]])) +
+ + 014   +
+ + 015  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 016  ;;;; +
+ + 017  ;;;; mw-engine: the state/transition engine of MicroWorld. +
+ + 018  ;;;; +
+ + 019  ;;;; This program is free software; you can redistribute it and/or +
+ + 020  ;;;; modify it under the terms of the GNU General Public License +
+ + 021  ;;;; as published by the Free Software Foundation; either version 2 +
+ + 022  ;;;; of the License, or (at your option) any later version. +
+ + 023  ;;;; +
+ + 024  ;;;; This program is distributed in the hope that it will be useful, +
+ + 025  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 026  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 027  ;;;; GNU General Public License for more details. +
+ + 028  ;;;; +
+ + 029  ;;;; You should have received a copy of the GNU General Public License +
+ + 030  ;;;; along with this program; if not, write to the Free Software +
+ + 031  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, +
+ + 032  ;;;; USA. +
+ + 033  ;;;; +
+ + 034  ;;;; Copyright (C) 2014 Simon Brooke +
+ + 035  ;;;; +
+ + 036  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +
+ + 037   +
+ + 038  (defmacro make-cell +
+ + 039    "Create a minimal default cell at x, y +
+ + 040   +
+ + 041    * `x` the x coordinate at which this cell is created; +
+ + 042    * `y` the y coordinate at which this cell is created." +
+ + 043    [x y] +
+ + 044    `{:x ~x :y ~y :state :new}) +
+ + 045   +
+ + 046  (defn make-world +
+ + 047    "Make a world width cells from east to west, and height cells from north to +
+ + 048     south. +
+ + 049   +
+ + 050    * `width` a natural number representing the width of the matrix to be created; +
+ + 051    * `height` a natural number representing the height of the matrix to be created." +
+ + 052    [width height] +
+ + 053    (apply vector +
+ + 054           (map (fn [h] +
+ + 055                  (apply vector (map #(make-cell % h) (range width)))) +
+ + 056                (range height)))) +
+ + 057   +
+ + 058  (defn truncate-state +
+ + 059    "Truncate the print name of the state of this cell to at most limit characters." +
+ + 060    [cell limit] +
+ + 061    (let [s (:state cell)] +
+ + 062      (cond (> (count (str s)) limit) (subs s 0 limit) +
+ + 063            :else s))) +
+ + 064   +
+ + 065  (defn format-cell +
+ + 066    "Return a formatted string summarising the current state of this cell." +
+ + 067    [cell] +
+ + 068    (format "%10s(%2d/%2d)" +
+ + 069            (truncate-state cell 10) +
+ + 070            (population cell :deer) +
+ + 071            (population cell :wolves))) +
+ + 072   +
+ + 073  (defn- format-world-row +
+ + 074    "Format one row in the state of a world for printing." +
+ + 075    [row] +
+ + 076    (string/join (map format-cell row))) +
+ + 077   +
+ + 078  (defn print-world +
+ + 079    "Print the current state of this world, and return nil. +
+ + 080   +
+ + 081    * `world` a world as defined above." +
+ + 082    [world] +
+ + 083    (println) +
+ + 084    (dorun +
+ + 085     (map +
+ + 086      #(println +
+ + 087        (format-world-row %)) +
+ + 088      world)) +
+ + 089    nil) +
+ + diff --git a/docs/codox/css/default.css b/docs/codox/css/default.css new file mode 100644 index 0000000..33f78fe --- /dev/null +++ b/docs/codox/css/default.css @@ -0,0 +1,551 @@ +body { + font-family: Helvetica, Arial, sans-serif; + font-size: 15px; +} + +pre, code { + font-family: Monaco, DejaVu Sans Mono, Consolas, monospace; + font-size: 9pt; + margin: 15px 0; +} + +h1 { + font-weight: normal; + font-size: 29px; + margin: 10px 0 2px 0; + padding: 0; +} + +h2 { + font-weight: normal; + font-size: 25px; +} + +h5.license { + margin: 9px 0 22px 0; + color: #555; + font-weight: normal; + font-size: 12px; + font-style: italic; +} + +.document h1, .namespace-index h1 { + font-size: 32px; + margin-top: 12px; +} + +#header, #content, .sidebar { + position: fixed; +} + +#header { + top: 0; + left: 0; + right: 0; + height: 22px; + color: #f5f5f5; + padding: 5px 7px; +} + +#content { + top: 32px; + right: 0; + bottom: 0; + overflow: auto; + background: #fff; + color: #333; + padding: 0 18px; +} + +.sidebar { + position: fixed; + top: 32px; + bottom: 0; + overflow: auto; +} + +.sidebar.primary { + background: #e2e2e2; + border-right: solid 1px #cccccc; + left: 0; + width: 250px; +} + +.sidebar.secondary { + background: #f2f2f2; + border-right: solid 1px #d7d7d7; + left: 251px; + width: 200px; +} + +#content.namespace-index, #content.document { + left: 251px; +} + +#content.namespace-docs { + left: 452px; +} + +#content.document { + padding-bottom: 10%; +} + +#header { + background: #3f3f3f; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); + z-index: 100; +} + +#header h1 { + margin: 0; + padding: 0; + font-size: 18px; + font-weight: lighter; + text-shadow: -1px -1px 0px #333; +} + +#header h1 .project-version { + font-weight: normal; +} + +.project-version { + padding-left: 0.15em; +} + +#header a, .sidebar a { + display: block; + text-decoration: none; +} + +#header a { + color: #f5f5f5; +} + +.sidebar a { + color: #333; +} + +#header h2 { + float: right; + font-size: 9pt; + font-weight: normal; + margin: 4px 3px; + padding: 0; + color: #bbb; +} + +#header h2 a { + display: inline; +} + +.sidebar h3 { + margin: 0; + padding: 10px 13px 0 13px; + font-size: 19px; + font-weight: lighter; +} + +.sidebar h3 a { + color: #444; +} + +.sidebar h3.no-link { + color: #636363; +} + +.sidebar ul { + padding: 7px 0 6px 0; + margin: 0; +} + +.sidebar ul.index-link { + padding-bottom: 4px; +} + +.sidebar li { + display: block; + vertical-align: middle; +} + +.sidebar li a, .sidebar li .no-link { + border-left: 3px solid transparent; + padding: 0 10px; + white-space: nowrap; +} + +.sidebar li .no-link { + display: block; + color: #777; + font-style: italic; +} + +.sidebar li .inner { + display: inline-block; + padding-top: 7px; + height: 24px; +} + +.sidebar li a, .sidebar li .tree { + height: 31px; +} + +.depth-1 .inner { padding-left: 2px; } +.depth-2 .inner { padding-left: 6px; } +.depth-3 .inner { padding-left: 20px; } +.depth-4 .inner { padding-left: 34px; } +.depth-5 .inner { padding-left: 48px; } +.depth-6 .inner { padding-left: 62px; } + +.sidebar li .tree { + display: block; + float: left; + position: relative; + top: -10px; + margin: 0 4px 0 0; + padding: 0; +} + +.sidebar li.depth-1 .tree { + display: none; +} + +.sidebar li .tree .top, .sidebar li .tree .bottom { + display: block; + margin: 0; + padding: 0; + width: 7px; +} + +.sidebar li .tree .top { + border-left: 1px solid #aaa; + border-bottom: 1px solid #aaa; + height: 19px; +} + +.sidebar li .tree .bottom { + height: 22px; +} + +.sidebar li.branch .tree .bottom { + border-left: 1px solid #aaa; +} + +.sidebar.primary li.current a { + border-left: 3px solid #a33; + color: #a33; +} + +.sidebar.secondary li.current a { + border-left: 3px solid #33a; + color: #33a; +} + +.namespace-index h2 { + margin: 30px 0 0 0; +} + +.namespace-index h3 { + font-size: 16px; + font-weight: bold; + margin-bottom: 0; +} + +.namespace-index .topics { + padding-left: 30px; + margin: 11px 0 0 0; +} + +.namespace-index .topics li { + padding: 5px 0; +} + +.namespace-docs h3 { + font-size: 18px; + font-weight: bold; +} + +.public h3 { + margin: 0; + float: left; +} + +.usage { + clear: both; +} + +.public { + margin: 0; + border-top: 1px solid #e0e0e0; + padding-top: 14px; + padding-bottom: 6px; +} + +.public:last-child { + margin-bottom: 20%; +} + +.members .public:last-child { + margin-bottom: 0; +} + +.members { + margin: 15px 0; +} + +.members h4 { + color: #555; + font-weight: normal; + font-variant: small-caps; + margin: 0 0 5px 0; +} + +.members .inner { + padding-top: 5px; + padding-left: 12px; + margin-top: 2px; + margin-left: 7px; + border-left: 1px solid #bbb; +} + +#content .members .inner h3 { + font-size: 12pt; +} + +.members .public { + border-top: none; + margin-top: 0; + padding-top: 6px; + padding-bottom: 0; +} + +.members .public:first-child { + padding-top: 0; +} + +h4.type, +h4.dynamic, +h4.added, +h4.deprecated { + float: left; + margin: 3px 10px 15px 0; + font-size: 15px; + font-weight: bold; + font-variant: small-caps; +} + +.public h4.type, +.public h4.dynamic, +.public h4.added, +.public h4.deprecated { + font-size: 13px; + font-weight: bold; + margin: 3px 0 0 10px; +} + +.members h4.type, +.members h4.added, +.members h4.deprecated { + margin-top: 1px; +} + +h4.type { + color: #717171; +} + +h4.dynamic { + color: #9933aa; +} + +h4.added { + color: #508820; +} + +h4.deprecated { + color: #880000; +} + +.namespace { + margin-bottom: 30px; +} + +.namespace:last-child { + margin-bottom: 10%; +} + +.index { + padding: 0; + font-size: 80%; + margin: 15px 0; + line-height: 16px; +} + +.index * { + display: inline; +} + +.index p { + padding-right: 3px; +} + +.index li { + padding-right: 5px; +} + +.index ul { + padding-left: 0; +} + +.type-sig { + clear: both; + color: #088; +} + +.type-sig pre { + padding-top: 10px; + margin: 0; +} + +.usage code { + display: block; + color: #008; + margin: 2px 0; +} + +.usage code:first-child { + padding-top: 10px; +} + +p { + margin: 15px 0; +} + +.public p:first-child, .public pre.plaintext { + margin-top: 12px; +} + +.doc { + margin: 0 0 26px 0; + clear: both; +} + +.public .doc { + margin: 0; +} + +.namespace-index .doc { + margin-bottom: 20px; +} + +.namespace-index .namespace .doc { + margin-bottom: 10px; +} + +.markdown p, .markdown li, .markdown dt, .markdown dd, .markdown td { + line-height: 22px; +} + +.markdown li { + padding: 2px 0; +} + +.markdown h2 { + font-weight: normal; + font-size: 25px; + margin: 30px 0 10px 0; +} + +.markdown h3 { + font-weight: normal; + font-size: 20px; + margin: 30px 0 0 0; +} + +.markdown h4 { + font-size: 15px; + margin: 22px 0 -4px 0; +} + +.doc, .public, .namespace .index { + max-width: 680px; + overflow-x: visible; +} + +.markdown pre > code { + display: block; + padding: 10px; +} + +.markdown pre > code, .src-link a { + border: 1px solid #e4e4e4; + border-radius: 2px; +} + +.markdown code:not(.hljs), .src-link a { + background: #f6f6f6; +} + +pre.deps { + display: inline-block; + margin: 0 10px; + border: 1px solid #e4e4e4; + border-radius: 2px; + padding: 10px; + background-color: #f6f6f6; +} + +.markdown hr { + border-style: solid; + border-top: none; + color: #ccc; +} + +.doc ul, .doc ol { + padding-left: 30px; +} + +.doc table { + border-collapse: collapse; + margin: 0 10px; +} + +.doc table td, .doc table th { + border: 1px solid #dddddd; + padding: 4px 6px; +} + +.doc table th { + background: #f2f2f2; +} + +.doc dl { + margin: 0 10px 20px 10px; +} + +.doc dl dt { + font-weight: bold; + margin: 0; + padding: 3px 0; + border-bottom: 1px solid #ddd; +} + +.doc dl dd { + padding: 5px 0; + margin: 0 0 5px 10px; +} + +.doc abbr { + border-bottom: 1px dotted #333; + font-variant: none; + cursor: help; +} + +.src-link { + margin-bottom: 15px; +} + +.src-link a { + font-size: 70%; + padding: 1px 4px; + text-decoration: none; + color: #5555bb; +} diff --git a/docs/codox/css/highlight.css b/docs/codox/css/highlight.css new file mode 100644 index 0000000..d0cdaa3 --- /dev/null +++ b/docs/codox/css/highlight.css @@ -0,0 +1,97 @@ +/* +github.com style (c) Vasily Polovnyov +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #d14; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #009926; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/docs/codox/index.html b/docs/codox/index.html new file mode 100644 index 0000000..1c9e38a --- /dev/null +++ b/docs/codox/index.html @@ -0,0 +1,11 @@ + +Mw-engine 0.2.0-SNAPSHOT

Mw-engine 0.2.0-SNAPSHOT

Released under the GNU General Public License v2

Cellular automaton world builder.

Installation

To install, add the following dependency to your project or build file:

[mw-engine "0.2.0-SNAPSHOT"]

Topics

Namespaces

mw-engine.core

Functions to transform a world and run rules.

+

Public variables and functions:

mw-engine.display

Simple functions to allow a world to be visualised.

+

mw-engine.drainage

Experimental, probably of no interest to anyone else; attempt to compute drainage on a world, assumed to have altitudes already set from a heightmap.

+

mw-engine.flow

Allow flows of values between cells in the world.

+

mw-engine.heightmap

Functions to apply a heightmap to a world.

+

mw-engine.natural-rules

A set of MicroWorld rules describing a simplified natural ecosystem.

+

mw-engine.world

Functions to create and to print two dimensional cellular automata.

+

Public variables and functions:

\ No newline at end of file diff --git a/docs/codox/intro.html b/docs/codox/intro.html new file mode 100644 index 0000000..2dd3792 --- /dev/null +++ b/docs/codox/intro.html @@ -0,0 +1,5 @@ + +Introduction to mw-engine

Introduction to mw-engine

+

TODO: write great documentation

+
\ No newline at end of file diff --git a/docs/codox/js/highlight.min.js b/docs/codox/js/highlight.min.js new file mode 100644 index 0000000..6486ffd --- /dev/null +++ b/docs/codox/js/highlight.min.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.6.0 | BSD3 License | git.io/hljslicense */ +!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/[&<>]/gm,function(e){return I[e]})}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return R(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||R(i))return i}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):E(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"===e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function h(e,n,t,r){var a=r?"":y.classPrefix,i='',i+n+o}function p(){var e,t,r,a;if(!E.k)return n(B);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(B);r;)a+=n(B.substr(t,r.index-t)),e=g(E,r),e?(M+=e[1],a+=h(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(B);return a+n(B.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!x[E.sL])return n(B);var t=e?l(E.sL,B,!0,L[E.sL]):f(B,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(L[E.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){k+=null!=E.sL?d():p(),B=""}function v(e){k+=e.cN?h(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(B+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?B+=n:(t.eB&&(B+=n),b(),t.rB||t.eB||(B=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?B+=n:(a.rE||a.eE||(B+=n),b(),a.eE&&(B=n));do E.cN&&(k+=C),E.skip||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return B+=n,n.length||1}var N=R(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,E=i||N,L={},k="";for(w=E;w!==N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var B="",M=0;try{for(var I,j,O=0;;){if(E.t.lastIndex=O,I=E.t.exec(t),!I)break;j=m(t.substr(O,I.index-O),I[0]),O=I.index+j}for(m(t.substr(O)),w=E;w.parent;w=w.parent)w.cN&&(k+=C);return{r:M,value:k,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function f(e,t){t=t||y.languages||E(x);var r={r:0,value:n(e)},a=r;return t.filter(R).forEach(function(n){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function g(e){return y.tabReplace||y.useBR?e.replace(M,function(e,n){return y.useBR&&"\n"===e?"
":y.tabReplace?n.replace(/\t/g,y.tabReplace):void 0}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n,t,r,o,s,p=i(e);a(p)||(y.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,s=n.textContent,r=p?l(p,s,!0):f(s),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),s)),r.value=g(r.value),e.innerHTML=r.value,e.className=h(e.className,p,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function d(e){y=o(y,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");w.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function N(){return E(x)}function R(e){return e=(e||"").toLowerCase(),x[e]||x[L[e]]}var w=[],E=Object.keys,x={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",y={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},I={"&":"&","<":"<",">":">"};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=R,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("clojure",function(e){var t={"builtin-name":"def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},r="a-zA-Z_\\-!.?+*=<>&#'",n="["+r+"]["+r+"0-9/;:]*",a="[-+]?\\d+(\\.\\d+)?",o={b:n,r:0},s={cN:"number",b:a,r:0},i=e.inherit(e.QSM,{i:null}),c=e.C(";","$",{r:0}),d={cN:"literal",b:/\b(true|false|nil)\b/},l={b:"[\\[\\{]",e:"[\\]\\}]"},m={cN:"comment",b:"\\^"+n},p=e.C("\\^\\{","\\}"),u={cN:"symbol",b:"[:]{1,2}"+n},f={b:"\\(",e:"\\)"},h={eW:!0,r:0},y={k:t,l:n,cN:"name",b:n,starts:h},b=[f,i,m,p,c,u,l,s,d,o];return f.c=[e.C("comment",""),y,h],h.c=b,l.c=b,{aliases:["clj"],i:/\S/,c:[f,i,m,p,c,u,l,s,d]}});hljs.registerLanguage("clojure-repl",function(e){return{c:[{cN:"meta",b:/^([\w.-]+|\s*#_)=>/,starts:{e:/$/,sL:"clojure"}}]}}); \ No newline at end of file diff --git a/docs/codox/js/jquery.min.js b/docs/codox/js/jquery.min.js new file mode 100644 index 0000000..73f33fb --- /dev/null +++ b/docs/codox/js/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m="1.11.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f +}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/
","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n("