Upversioning whole system to 0.2.0, for flow feature
This commit is contained in:
parent
8b3639edd5
commit
4f35557b38
|
@ -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"]
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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?`.
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue