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"]
|
: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"]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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?`.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue