Trying to address bit-rot
This commit is contained in:
parent
2f2463da0e
commit
2cb3a6af6f
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -8,3 +8,11 @@ pom.xml
|
|||
.lein-failures
|
||||
|
||||
eastwood.txt
|
||||
|
||||
.clj-kondo/
|
||||
.lsp/
|
||||
.project
|
||||
.settings/
|
||||
.nrepl-port
|
||||
.classpath
|
||||
|
||||
|
|
1003
docs/uberdoc.html
1003
docs/uberdoc.html
File diff suppressed because one or more lines are too long
35
project.clj
35
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/"
|
||||
)
|
||||
|
|
134
src/cljc/mw_engine/core.clj
Normal file
134
src/cljc/mw_engine/core.clj
Normal file
|
@ -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)))
|
||||
|
||||
|
||||
|
|
@ -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))))
|
|
@ -62,16 +62,27 @@
|
|||
|
||||
|
||||
(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]
|
||||
|
@ -126,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]
|
||||
(cond (in-bounds world x y)
|
||||
(when (in-bounds world x y)
|
||||
(nth (nth world y) x)))
|
||||
|
||||
|
||||
|
@ -136,11 +147,11 @@
|
|||
* `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
|
|
@ -1,7 +1,7 @@
|
|||
(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]]
|
||||
(:require [clojure.string :as string]
|
||||
[mw-engine.utils :refer [population]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
Loading…
Reference in a new issue