Upversioning whole system to 0.2.0, for flow feature

This commit is contained in:
Simon Brooke 2023-07-10 13:46:42 +01:00
parent 8b3639edd5
commit 4f35557b38
8 changed files with 104 additions and 152 deletions

View file

@ -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"] :dependencies [[org.clojure/clojure "1.11.1"]
[org.clojure/clojurescript "1.11.60" :scope "provided"] [org.clojure/clojurescript "1.11.60" :scope "provided"]
[org.clojure/math.combinatorics "0.2.0"] [org.clojure/math.combinatorics "0.2.0"]

View file

@ -1,4 +1,25 @@
(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"} :author "Simon Brooke"}
mw-engine.core mw-engine.core
(:require [mw-engine.utils :refer [get-int-or-zero map-world]] (:require [mw-engine.utils :refer [get-int-or-zero map-world]]
@ -26,29 +47,6 @@
;;;; Copyright (C) 2014 Simon Brooke ;;;; 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 (defn apply-rule
"Apply a single `rule` to a `cell`. What this is about is that I want to be able, "Apply a single `rule` to a `cell`. What this is about is that I want to be able,
@ -75,7 +73,6 @@
(cond result result (cond result result
:else (apply-rules world cell (rest rules)))))) :else (apply-rules world cell (rest rules))))))
(defn- transform-cell (defn- transform-cell
"Derive a cell from this `cell` of this `world` by applying these `rules`. If an "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" exception is thrown, cache its message on the cell and set it's state to error"

View file

@ -35,18 +35,15 @@
[state] [state]
(subs (str state) 1)) (subs (str state) 1))
(defn format-image-path (defn format-image-path
"Render this `state`, assumed to be a keyword indicating a state in the "Render this `state`, assumed to be a keyword indicating a state in the
world, into a path which should recover the corresponding image file." world, into a path which should recover the corresponding image file."
[state] [state]
(format "%s/%s.png" *image-base* (format-css-class state))) (format "%s/%s.png" *image-base* (format-css-class state)))
(defn format-mouseover [cell] (defn format-mouseover [cell]
(str cell)) (str cell))
(defn render-cell (defn render-cell
"Render this world cell as a Hiccup table cell." "Render this world cell as a Hiccup table cell."
[cell] [cell]
@ -55,13 +52,11 @@
[:a {:href (format "inspect?x=%d&y=%d" (:x cell) (:y 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)}]]])) [:img {:alt (:state cell) :width 32 :height 32 :src (format-image-path state)}]]]))
(defn render-world-row (defn render-world-row
"Render this world `row` as a Hiccup table row." "Render this world `row` as a Hiccup table row."
[row] [row]
(apply vector (cons :tr (map render-cell row)))) (apply vector (cons :tr (map render-cell row))))
(defn render-world-table (defn render-world-table
"Render this `world` as a Hiccup table." "Render this `world` as a Hiccup table."
[world] [world]

View file

@ -1,6 +1,26 @@
(ns mw-engine.flow (ns mw-engine.flow
"Allow flows of values between cells in the world." "Allow flows of values between cells in the world.
(:require [mw-engine.utils :refer [get-cell get-num merge-cell]]
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]])) [taoensso.timbre :refer [info warn]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -25,32 +45,6 @@
;;;; Copyright (C) 2014 Simon Brooke ;;;; 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? (defn coordinate?
"Return `true` if this object `o` is a valid coordinate with respect to "Return `true` if this object `o` is a valid coordinate with respect to
@ -65,12 +59,13 @@
(defn location? (defn location?
"Return `true` if this object `o` is a location as defined above with respect to "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] [o world]
(try (try
(and (map? o) (and (map? o)
(coordinate? (:x o) world) (integer? (:x o))
(coordinate? (:y o) world)) (integer? (:y o))
(in-bounds? world (:x o) (:y o)))
(catch Exception e (catch Exception e
(warn (format "Not a valid location: %s; %s" o (.getMessage e))) (warn (format "Not a valid location: %s; %s" o (.getMessage e)))
false))) false)))

View file

@ -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"} :author "Simon Brooke"}
mw-engine.heightmap mw-engine.heightmap
(:require [mikera.image.core :refer [load-image filter-image]] (:require [mikera.image.core :refer [load-image filter-image]]
@ -28,11 +31,6 @@
;;;; Copyright (C) 2014 Simon Brooke ;;;; 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 (defn tag-property
"Set the value of this `property` of this cell from the corresponding pixel of this `heightmap`. "Set the value of this `property` of this cell from the corresponding pixel of this `heightmap`.

View file

@ -1,5 +1,10 @@
(ns ^{:doc "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"}
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 mw-engine.natural-rules
(:require [mw-engine.utils :refer [get-int get-neighbours get-neighbours-with-state member?]])) (:require [mw-engine.utils :refer [get-int get-neighbours get-neighbours-with-state member?]]))
@ -25,13 +30,6 @@
;;;; Copyright (C) 2014 Simon Brooke ;;;; 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. ;; treeline at arbitrary altitude.
(def treeline 150) (def treeline 150)

View file

@ -47,7 +47,6 @@
[_ cell] [_ cell]
(merge cell {:generation (get-int-or-zero cell :generation)})) (merge cell {:generation (get-int-or-zero cell :generation)}))
(defn in-bounds (defn in-bounds
"True if x, y are in bounds for this world (i.e., there is a cell at x, y) "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?`. else false. *DEPRECATED*: it's a predicate, prefer `in-bounds?`.

View file

@ -1,4 +1,12 @@
(ns ^{:doc "Functions to create and to print two dimensional cellular automata." (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"} :author "Simon Brooke"}
mw-engine.world mw-engine.world
(:require [clojure.string :as string] (:require [clojure.string :as string]
@ -26,51 +34,14 @@
;;;; Copyright (C) 2014 Simon Brooke ;;;; 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.
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro make-cell
(defn- make-cell
"Create a minimal default cell at x, y "Create a minimal default cell at x, y
* `x` the x coordinate at which this cell is created; * `x` the x coordinate at which this cell is created;
* `y` the y coordinate at which this cell is created." * `y` the y coordinate at which this cell is created."
[x y] [x y]
{:x x :y y :state :new}) `{: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))))
(defn make-world (defn make-world
"Make a world width cells from east to west, and height cells from north to "Make a world width cells from east to west, and height cells from north to
@ -79,8 +50,10 @@
* `width` a natural number representing the width of the matrix to be created; * `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." * `height` a natural number representing the height of the matrix to be created."
[width height] [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 (defn truncate-state
"Truncate the print name of the state of this cell to at most limit characters." "Truncate the print name of the state of this cell to at most limit characters."
@ -89,7 +62,6 @@
(cond (> (count (str s)) limit) (subs s 0 limit) (cond (> (count (str s)) limit) (subs s 0 limit)
:else s))) :else s)))
(defn format-cell (defn format-cell
"Return a formatted string summarising the current state of this cell." "Return a formatted string summarising the current state of this cell."
[cell] [cell]
@ -98,13 +70,11 @@
(population cell :deer) (population cell :deer)
(population cell :wolves))) (population cell :wolves)))
(defn- format-world-row (defn- format-world-row
"Format one row in the state of a world for printing." "Format one row in the state of a world for printing."
[row] [row]
(string/join (map format-cell row))) (string/join (map format-cell row)))
(defn print-world (defn print-world
"Print the current state of this world, and return nil. "Print the current state of this world, and return nil.