Upgraded everything. This may be a mistake!

This commit is contained in:
Simon Brooke 2023-07-08 10:21:41 +01:00
parent 67a43279f6
commit f4d4e9b694
6 changed files with 204 additions and 57 deletions

View file

@ -0,0 +1,171 @@
; This is the Calva evaluation results output window.
; TIPS: The keyboard shortcut `ctrl+alt+o o` shows and focuses this window
; when connected to a REPL session.
; Please see https://calva.io/output/ for more info.
; Happy coding! ♥️
; Connecting ...
; Reading port file: file:///Users/simon/workspace/MicroWorld/mw-engine/.nrepl-port ...
; Using host:port localhost:58100 ...
; Hooking up nREPL sessions ...
; Connected session: clj
; TIPS:
; - You can edit the contents here. Use it as a REPL if you like.
; - `alt+enter` evaluates the current top level form.
; - `ctrl+enter` evaluates the current form.
; - `alt+up` and `alt+down` traverse up and down the REPL command history
; when the cursor is after the last contents at the prompt
; - Clojure lines in stack traces are peekable and clickable.
; Evaluating code from settings: 'calva.autoEvaluateCode.onConnect.clj'
nil
cljuser>  ; Use `alt+enter` to evaluate
cljuser> 
nil
; WARNING: abs already refers to: #'clojure.core/abs in namespace: mw-engine.utils, being replaced by: #'mw-engine.utils/abs
cljmw-engine.core> 
nil
cljmw-engine.core> 
#'mw-engine.core/apply-rule
cljmw-engine.core> 
#'mw-engine.core/apply-rules
cljmw-engine.core> 
#'mw-engine.core/transform-cell
cljmw-engine.core> 
#'mw-engine.core/transform-world
cljmw-engine.core> 
#'mw-engine.core/run-world
cljmw-engine.core> 
#'mw-engine.display/*image-base*
cljmw-engine.display> 
; Syntax error compiling at (src/cljc/mw_engine/display.clj:32:1).
; Unable to resolve symbol: defn in this context
cljmw-engine.display> 
; Syntax error compiling at (src/cljc/mw_engine/display.clj:32:1).
; Unable to resolve symbol: defn in this context
cljmw-engine.display> 
nil
cljmw-engine.display> 
#'mw-engine.display/*image-base*
cljmw-engine.display> 
#'mw-engine.display/format-css-class
cljmw-engine.display> 
#'mw-engine.display/format-image-path
cljmw-engine.display> 
#'mw-engine.display/format-mouseover
cljmw-engine.display> 
#'mw-engine.display/render-cell
cljmw-engine.display> 
#'mw-engine.display/render-world-row
cljmw-engine.display> 
#'mw-engine.display/render-world-table
cljmw-engine.display> 
nil
; WARNING: abs already refers to: #'clojure.core/abs in namespace: mw-engine.heightmap, being replaced by: #'mw-engine.utils/abs
cljmw-engine.drainage> 
#'mw-engine.drainage/*sealevel*
cljmw-engine.drainage> 
#'mw-engine.drainage/flow
cljmw-engine.drainage> 
#'mw-engine.drainage/rainfall
cljmw-engine.drainage> 
#'mw-engine.drainage/rain-row
cljmw-engine.drainage> 
#'mw-engine.drainage/rain-world
cljmw-engine.drainage> 
#'mw-engine.drainage/flow-contributors
cljmw-engine.drainage> 
#'mw-engine.drainage/is-hollow
cljmw-engine.drainage> 
#'mw-engine.drainage/flood-hollow
cljmw-engine.drainage> 
#'mw-engine.drainage/flood-hollows
cljmw-engine.drainage> 
#'mw-engine.drainage/max-altitude
cljmw-engine.drainage> 
#'mw-engine.drainage/flow-nr
cljmw-engine.drainage> 
#'mw-engine.drainage/flow
cljmw-engine.drainage> 
#'mw-engine.drainage/flow-world-nr
cljmw-engine.drainage> 
#'mw-engine.drainage/flow-world
cljmw-engine.drainage> 
#'mw-engine.drainage/explore-lake
cljmw-engine.drainage> 
#'mw-engine.drainage/is-lake?
cljmw-engine.drainage> 
#'mw-engine.drainage/find-lakes
cljmw-engine.drainage> 
#'mw-engine.drainage/run-drainage
cljmw-engine.drainage> 
#'mw-engine.drainage/run-drainage
cljmw-engine.drainage> 
nil
cljmw-engine.heightmap> 
#'mw-engine.heightmap/tag-property
cljmw-engine.heightmap> 
#'mw-engine.heightmap/tag-gradient
cljmw-engine.heightmap> 
#'mw-engine.heightmap/tag-gradients
cljmw-engine.heightmap> 
#'mw-engine.heightmap/tag-altitude
cljmw-engine.heightmap> 
#'mw-engine.heightmap/apply-heightmap
cljmw-engine.heightmap> 
nil
cljmw-engine.utils> 
#'mw-engine.utils/member?
cljmw-engine.utils> 
#'mw-engine.utils/get-int-or-zero
cljmw-engine.utils> 
#'mw-engine.utils/in-bounds
cljmw-engine.utils> 
#'mw-engine.utils/in-bounds?
cljmw-engine.utils> 
#'mw-engine.utils/map-world-n-n
cljmw-engine.utils> 
#'mw-engine.utils/map-world-p-p
cljmw-engine.utils> 
#'mw-engine.utils/map-world
cljmw-engine.utils> 
#'mw-engine.utils/get-cell
cljmw-engine.utils> 
#'mw-engine.utils/get-int
cljmw-engine.utils> 
#'mw-engine.utils/population
cljmw-engine.utils> 
#'mw-engine.utils/memo-get-neighbours
cljmw-engine.utils> 
#'mw-engine.utils/get-neighbours
cljmw-engine.utils> 
#'mw-engine.utils/get-neighbours-with-property-value
cljmw-engine.utils> 
#'mw-engine.utils/get-neighbours-with-state
cljmw-engine.utils> 
#'mw-engine.utils/get-least-cell
cljmw-engine.utils> 
#'mw-engine.utils/set-cell-property
cljmw-engine.utils> 
#'mw-engine.utils/set-property
cljmw-engine.utils> 
#'mw-engine.utils/merge-cell
cljmw-engine.utils> 
nil
cljmw-engine.world> 
#'mw-engine.world/make-cell
cljmw-engine.world> 
#'mw-engine.world/make-world-row
cljmw-engine.world> 
#'mw-engine.world/make-world-rows
cljmw-engine.world> 
#'mw-engine.world/make-world
cljmw-engine.world> 
#'mw-engine.world/truncate-state
cljmw-engine.world> 
#'mw-engine.world/format-cell
cljmw-engine.world> 
#'mw-engine.world/format-world-row
cljmw-engine.world> 
#'mw-engine.world/print-world
cljmw-engine.world> 
; nREPL Connection was closed

View file

@ -1,10 +1,10 @@
(defproject mw-engine "0.1.6-SNAPSHOT" (defproject mw-engine "0.1.6-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.3"] :dependencies [[org.clojure/clojure "1.11.1"]
[org.clojure/clojurescript "1.10.896" :scope "provided"] [org.clojure/clojurescript "1.11.60" :scope "provided"]
[org.clojure/math.combinatorics "0.1.6"] [org.clojure/math.combinatorics "0.2.0"]
[org.clojure/tools.trace "0.7.11"] [org.clojure/tools.trace "0.7.11"]
[org.clojure/tools.namespace "1.1.1"] [org.clojure/tools.namespace "1.4.4"]
[com.taoensso/timbre "5.1.2"] [com.taoensso/timbre "6.2.1"]
[fivetonine/collage "0.3.0"] [fivetonine/collage "0.3.0"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[net.mikera/imagez "0.12.0"]] [net.mikera/imagez "0.12.0"]]

View file

@ -1,9 +1,9 @@
(ns ^{:doc "Functions to apply a heightmap to a world." (ns ^{:doc "Functions to apply a heightmap to a world."
:author "Simon Brooke"} :author "Simon Brooke"}
mw-engine.heightmap mw-engine.heightmap
(:require [mikera.image.core :refer [load-image filter-image get-pixels]] (:require [mikera.image.core :refer [load-image filter-image]]
[mikera.image.filters :as filters] [mikera.image.filters :as filters]
[mw-engine.utils :refer [abs get-int get-neighbours map-world]] [mw-engine.utils :refer [get-int get-neighbours map-world]]
[mw-engine.world :refer [make-world]])) [mw-engine.world :refer [make-world]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -34,7 +34,6 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(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`.
If the heightmap you supply is smaller than the world, this will break. If the heightmap you supply is smaller than the world, this will break.
@ -45,7 +44,7 @@
* `property` the property (normally a keyword) whose value will be set on the cell. * `property` the property (normally a keyword) whose value will be set on the cell.
* `heightmap` an (ideally) greyscale image, whose x and y dimensions should * `heightmap` an (ideally) greyscale image, whose x and y dimensions should
exceed those of the world of which the `cell` forms part." exceed those of the world of which the `cell` forms part."
([world cell property heightmap] ([_ cell property heightmap]
(tag-property cell property heightmap)) (tag-property cell property heightmap))
([cell property heightmap] ([cell property heightmap]
(merge cell (merge cell
@ -58,7 +57,6 @@
(get-int cell :x) (get-int cell :x)
(get-int cell :y)) 256))))}))) (get-int cell :y)) 256))))})))
(defn tag-gradient (defn tag-gradient
"Set the `gradient` property of this `cell` of this `world` to the difference in "Set the `gradient` property of this `cell` of this `world` to the difference in
altitude between its highest and lowest neghbours." altitude between its highest and lowest neghbours."
@ -71,14 +69,12 @@
gradient (- highest lowest)] gradient (- highest lowest)]
(merge cell {:gradient gradient}))) (merge cell {:gradient gradient})))
(defn tag-gradients (defn tag-gradients
"Set the `gradient` property of each cell in this `world` to the difference in "Set the `gradient` property of each cell in this `world` to the difference in
altitude between its highest and lowest neghbours." altitude between its highest and lowest neghbours."
[world] [world]
(map-world world tag-gradient)) (map-world world tag-gradient))
(defn tag-altitude (defn tag-altitude
"Set the altitude of this cell from the corresponding pixel of this heightmap. "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. If the heightmap you supply is smaller than the world, this will break.
@ -88,12 +84,11 @@
* `cell` a cell, as discussed in world.clj, q.v. Alternatively, a map; * `cell` a cell, as discussed in world.clj, q.v. Alternatively, a map;
* `heightmap` an (ideally) greyscale image, whose x and y dimensions should * `heightmap` an (ideally) greyscale image, whose x and y dimensions should
exceed those of the world of which the `cell` forms part." exceed those of the world of which the `cell` forms part."
([world cell heightmap] ([_ cell heightmap]
(tag-property cell :altitude heightmap)) (tag-property cell :altitude heightmap))
([cell heightmap] ([cell heightmap]
(tag-property cell :altitude heightmap))) (tag-property cell :altitude heightmap)))
(defn apply-heightmap (defn apply-heightmap
"Apply the image file loaded from this path to this world, and return a world whose "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 altitudes are modified (added to) by the altitudes in the heightmap. It is assumed that
@ -105,21 +100,20 @@
* `imagepath` a file path or URL which indicates an (ideally greyscale) image file." * `imagepath` a file path or URL which indicates an (ideally greyscale) image file."
([world imagepath] ([world imagepath]
(let [heightmap (filter-image (let [heightmap (filter-image
(filters/grayscale) (load-image imagepath)
(load-image imagepath))] (filters/grayscale))]
(map-world (map-world
(map-world world tag-altitude (list heightmap)) (map-world world tag-altitude (list heightmap))
tag-gradient))) tag-gradient)))
([imagepath] ([imagepath]
(let [heightmap (filter-image (let [heightmap (filter-image
(filters/grayscale) (load-image imagepath)
(load-image imagepath)) (filters/grayscale))
world (make-world (.getWidth heightmap) (.getHeight heightmap))] world (make-world (.getWidth heightmap) (.getHeight heightmap))]
(map-world (map-world
(map-world world tag-altitude (list heightmap)) (map-world world tag-altitude (list heightmap))
tag-gradient)))) tag-gradient))))
(defn apply-valuemap (defn apply-valuemap
"Generalised from apply-heightmap, set an arbitrary property on each cell "Generalised from apply-heightmap, set an arbitrary property on each cell
of this `world` from the values in this (ideally greyscale) heightmap. of this `world` from the values in this (ideally greyscale) heightmap.
@ -130,6 +124,6 @@
intensity of the corresponding cell of the image." intensity of the corresponding cell of the image."
[world imagepath property] [world imagepath property]
(let [heightmap (filter-image (let [heightmap (filter-image
(filters/grayscale) (load-image imagepath)
(load-image imagepath))] (filters/grayscale))]
(map-world world tag-property (list property heightmap)))) (map-world world tag-property (list property heightmap))))

View file

@ -33,46 +33,41 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; treeline at arbitrary altitude. ;; treeline at arbitrary altitude.
(def treeline 150) (def treeline 150)
;; waterline also at arbitrary altitude. ;; waterline also at arbitrary altitude.
(def waterline 10) (def waterline 10)
;; and finally snowline is also arbitrary. ;; and finally snowline is also arbitrary.
(def snowline 200) (def snowline 200)
;; Rare chance of lightning strikes ;; Rare chance of lightning strikes
(def lightning-probability 500) (def lightning-probability 500)
;; rules describing vegetation ;; rules describing vegetation
(def vegetation-rules (def vegetation-rules
(list (list
;; Randomly, birds plant tree seeds into grassland. ;; Randomly, birds plant tree seeds into grassland.
(fn [cell world] (cond (and (= (:state cell) :grassland)(< (rand 10) 1))(merge cell {:state :heath}))) (fn [cell _] (cond (and (= (:state cell) :grassland)(< (rand 10) 1))(merge cell {:state :heath})))
;; heath below the treeline grows gradually into forest, providing browsing pressure is not to high ;; heath below the treeline grows gradually into forest, providing browsing pressure is not to high
(fn [cell world] (fn [cell _]
(cond (and (cond (and
(= (:state cell) :heath) (= (:state cell) :heath)
;; browsing limit really ought to vary with soil fertility, but... ;; browsing limit really ought to vary with soil fertility, but...
(< (+ (get-int cell :deer)(get-int cell :sheep)) 6) (< (+ (get-int cell :deer)(get-int cell :sheep)) 6)
(< (get-int cell :altitude) treeline)) (< (get-int cell :altitude) treeline))
(merge cell {:state :scrub}))) (merge cell {:state :scrub})))
(fn [cell world] (cond (= (:state cell) :scrub) (merge cell {:state :forest}))) (fn [cell _] (cond (= (:state cell) :scrub) (merge cell {:state :forest})))
;; Forest on fertile land grows to climax ;; Forest on fertile land grows to climax
(fn [cell world] (fn [cell _]
(cond (cond
(and (and
(= (:state cell) :forest) (= (:state cell) :forest)
(> (get-int cell :fertility) 10)) (> (get-int cell :fertility) 10))
(merge cell {:state :climax}))) (merge cell {:state :climax})))
;; Climax forest occasionally catches fire (e.g. lightning strikes) ;; Climax forest occasionally catches fire (e.g. lightning strikes)
(fn [cell world] (cond (and (= (:state cell) :climax)(< (rand lightning-probability) 1)) (merge cell {:state :fire}))) (fn [cell _] (cond (and (= (:state cell) :climax)(< (rand lightning-probability) 1)) (merge cell {:state :fire})))
;; Climax forest neighbouring fires is likely to catch fire ;; Climax forest neighbouring fires is likely to catch fire
(fn [cell world] (fn [cell world]
(cond (cond
@ -81,7 +76,7 @@
(not (empty? (get-neighbours-with-state world (:x cell) (:y cell) 1 :fire)))) (not (empty? (get-neighbours-with-state world (:x cell) (:y cell) 1 :fire))))
(merge cell {:state :fire}))) (merge cell {:state :fire})))
;; After fire we get waste ;; After fire we get waste
(fn [cell world] (cond (= (:state cell) :fire) (merge cell {:state :waste}))) (fn [cell _] (cond (= (:state cell) :fire) (merge cell {:state :waste})))
;; And after waste we get pioneer species; if there's a woodland seed ;; And after waste we get pioneer species; if there's a woodland seed
;; source, it's going to be heath, otherwise grassland. ;; source, it's going to be heath, otherwise grassland.
(fn [cell world] (fn [cell world]
@ -95,11 +90,11 @@
(get-neighbours-with-state world (:x cell) (:y cell) 1 :forest) (get-neighbours-with-state world (:x cell) (:y cell) 1 :forest)
(get-neighbours-with-state world (:x cell) (:y cell) 1 :climax)))))) (get-neighbours-with-state world (:x cell) (:y cell) 1 :climax))))))
(merge cell {:state :heath}))) (merge cell {:state :heath})))
(fn [cell world] (fn [cell _]
(cond (= (:state cell) :waste) (cond (= (:state cell) :waste)
(merge cell {:state :grassland}))) (merge cell {:state :grassland})))
;; Forest increases soil fertility ;; Forest increases soil fertility
(fn [cell world] (fn [cell _]
(cond (member? (:state cell) '(:forest :climax)) (cond (member? (:state cell) '(:forest :climax))
(merge cell {:fertility (+ (get-int cell :fertility) 1)}))))) (merge cell {:fertility (+ (get-int cell :fertility) 1)})))))
@ -109,7 +104,7 @@
(list (list
;; if there are too many deer for the fertility of the area to sustain, ;; if there are too many deer for the fertility of the area to sustain,
;; some die or move on. ;; some die or move on.
(fn [cell world] (fn [cell _]
(cond (> (get-int cell :deer) (get-int cell :fertility)) (cond (> (get-int cell :deer) (get-int cell :fertility))
(merge cell {:deer (get-int cell :fertility)}))) (merge cell {:deer (get-int cell :fertility)})))
;; deer arrive occasionally at the edge of the map. ;; deer arrive occasionally at the edge of the map.
@ -128,7 +123,7 @@
(>= n 2)) (>= n 2))
(merge cell {:deer (int (/ n 2))})))) (merge cell {:deer (int (/ n 2))}))))
;; deer breed. ;; deer breed.
(fn [cell world] (fn [cell _]
(cond (cond
(>= (get-int cell :deer) 2) (>= (get-int cell :deer) 2)
(merge cell {:deer (int (* (:deer cell) 2))}))))) (merge cell {:deer (int (* (:deer cell) 2))})))))
@ -137,7 +132,7 @@
(def predator-rules (def predator-rules
(list (list
;; wolves eat deer ;; wolves eat deer
(fn [cell world] (fn [cell _]
(cond (cond
(>= (get-int cell :wolves) 1) (>= (get-int cell :wolves) 1)
(merge cell {:deer (max 0 (- (get-int cell :deer) (get-int cell :wolves)))}))) (merge cell {:deer (max 0 (- (get-int cell :deer) (get-int cell :wolves)))})))
@ -146,7 +141,7 @@
;; (cond (> (get-int cell :wolves) 8) (merge cell {:wolves 8}))) ;; (cond (> (get-int cell :wolves) 8) (merge cell {:wolves 8})))
;; if there are not enough deer to sustain the get-int of wolves, ;; if there are not enough deer to sustain the get-int of wolves,
;; some wolves die or move on. (doesn't seem to be working?) ;; some wolves die or move on. (doesn't seem to be working?)
(fn [cell world] (fn [cell _]
(cond (> (get-int cell :wolves) (get-int cell :deer)) (cond (> (get-int cell :wolves) (get-int cell :deer))
(merge cell {:wolves 0}))) (merge cell {:wolves 0})))
;; wolves arrive occasionally at the edge of the map. ;; wolves arrive occasionally at the edge of the map.
@ -165,7 +160,7 @@
(>= n 2)) (>= n 2))
(merge cell {:wolves 2})))) (merge cell {:wolves 2}))))
;; wolves breed. ;; wolves breed.
(fn [cell world] (fn [cell _]
(cond (cond
(>= (get-int cell :wolves) 2) (>= (get-int cell :wolves) 2)
(merge cell {:wolves (int (* (:wolves cell) 2))}))))) (merge cell {:wolves (int (* (:wolves cell) 2))})))))
@ -175,13 +170,13 @@
(def init-rules (def init-rules
(list (list
;; below the waterline, we have water. ;; below the waterline, we have water.
(fn [cell world] (fn [cell _]
(cond (and (= (:state cell) :new) (< (get-int cell :altitude) waterline)) (merge cell {:state :water}))) (cond (and (= (:state cell) :new) (< (get-int cell :altitude) waterline)) (merge cell {:state :water})))
;; above the snowline, we have snow. ;; above the snowline, we have snow.
(fn [cell world] (fn [cell _]
(cond (and (= (:state cell) :new) (> (get-int cell :altitude) snowline)) (merge cell {:state :snow}))) (cond (and (= (:state cell) :new) (> (get-int cell :altitude) snowline)) (merge cell {:state :snow})))
;; in between, we have a wasteland. ;; in between, we have a wasteland.
(fn [cell world] (cond (= (:state cell) :new) (merge cell {:state :grassland}))))) (fn [cell _] (cond (= (:state cell) :new) (merge cell {:state :grassland})))))
(def natural-rules (flatten (def natural-rules (flatten

View file

@ -28,22 +28,10 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(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,
and negative integers onto natural numbers. Also maps negative real numbers
onto positive real numbers.
* `n` a number, on the set of real numbers."
[n]
(if (neg? n) (- 0 n) n))
(defn member? (defn member?
"True if elt is a member of col." "True if elt is a member of col."
[elt col] (some #(= elt %) col)) [elt col] (some #(= elt %) col))
(defn get-int-or-zero (defn get-int-or-zero
"Return the value of this `property` from this `map` if it is a integer; "Return the value of this `property` from this `map` if it is a integer;
otherwise return zero." otherwise return zero."
@ -51,13 +39,12 @@
(let [value (map property)] (let [value (map property)]
(if (integer? value) value 0))) (if (integer? value) value 0)))
(defn init-generation (defn init-generation
"Return a cell like this `cell`, but having a value for :generation, zero if "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 the cell passed had no integer value for generation, otherwise the value
taken from the cell passed. The `world` argument is present only for taken from the cell passed. The `world` argument is present only for
consistency with the rule engine and is ignored." consistency with the rule engine and is ignored."
[world cell] [_ cell]
(merge cell {:generation (get-int-or-zero cell :generation)})) (merge cell {:generation (get-int-or-zero cell :generation)}))