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.
This commit is contained in:
simon 2016-08-13 17:39:07 +01:00
parent 3c26408a9f
commit 21cdff764f
8 changed files with 267 additions and 71 deletions

View file

@ -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

View file

@ -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]

View file

@ -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,6 +40,7 @@
[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
@ -34,6 +57,7 @@
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
@ -47,6 +71,7 @@
(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
of the lowest of its `neighbours`."
@ -56,6 +81,7 @@
([world cell]
(flood-hollow world cell (get-neighbours world cell))))
(defn flood-hollows
"Flood all local hollows in this `world`. At this stage only floods single
cell hollows."
@ -63,6 +89,7 @@
(map-world world
#(if (is-hollow %1 %2) (flood-hollow %1 %2) %2)))
(def max-altitude 255)
(defn flow-nr
@ -80,6 +107,7 @@
(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,6 +139,7 @@
[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

View file

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

View file

@ -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))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; 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)))

View file

@ -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"

View file

@ -1,4 +1,6 @@
(ns mw-engine.version
(ns ^{:doc "package documentation."
:author "Simon Brooke"}
mw-engine.version
(:gen-class))
(defn get-implementation-version

View file

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