001 (ns ^{:doc "Functions to transform a world and run rules.
002
003 Every rule is a function of two arguments, a cell and a world. If the rule
004 fires, it returns a new cell, which should have the same values for `:x` and
005 `:y` as the old cell. Anything else can be modified.
006
007 While any function of two arguments can be used as a rule, a special high
008 level rule language is provided by the `mw-parser` package, which compiles
009 rules expressed in a subset of English rules into suitable functions.
010
011 A cell is a map containing at least values for the keys :x, :y, and :state;
012 a transformation should not alter the values of :x or :y, and should not
013 return a cell without a keyword as the value of :state. Anything else is
014 legal.
015
016 A world is a two dimensional matrix (sequence of sequences) of cells, such
017 that every cell's `:x` and `:y` properties reflect its place in the matrix.
018 See `world.clj`.
019
020 Each time the world is transformed (see `transform-world`), for each cell,
021 rules are applied in turn until one matches. Once one rule has matched no
022 further rules can be applied to that cell."
023 :author "Simon Brooke"}
024 mw-engine.core
025 (:require [mw-engine.utils :refer [get-int-or-zero map-world]]
026 [taoensso.timbre :as l]))
027
028 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
029 ;;;;
030 ;;;; mw-engine: the state/transition engine of MicroWorld.
031 ;;;;
032 ;;;; This program is free software; you can redistribute it and/or
033 ;;;; modify it under the terms of the GNU General Public License
034 ;;;; as published by the Free Software Foundation; either version 2
035 ;;;; of the License, or (at your option) any later version.
036 ;;;;
037 ;;;; This program is distributed in the hope that it will be useful,
038 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
039 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
040 ;;;; GNU General Public License for more details.
041 ;;;;
042 ;;;; You should have received a copy of the GNU General Public License
043 ;;;; along with this program; if not, write to the Free Software
044 ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
045 ;;;; USA.
046 ;;;;
047 ;;;; Copyright (C) 2014 Simon Brooke
048 ;;;;
049 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
050
051 (defn apply-rule
052 "Apply a single `rule` to a `cell`. What this is about is that I want to be able,
053 for debugging purposes, to tag a cell with the rule text of the rule which
054 fired (and especially so when an exception is thrown. So a rule may be either
055 an ifn, or a list (ifn source-text). This function deals with despatching
056 on those two possibilities. `world` is also passed in in order to be able
057 to access neighbours."
058 ([world cell rule]
059 (cond
060 (ifn? rule) (apply-rule world cell rule nil)
061 (seq? rule) (let [[afn src] rule] (apply-rule world cell afn src))))
062 ([world cell rule source]
063 (let [result (apply rule (list cell world))]
064 (cond
065 (and result source) (merge result {:rule source})
066 :else result))))
067
068 (defn- apply-rules
069 "Derive a cell from this `cell` of this `world` by applying these `rules`."
070 [world cell rules]
071 (cond (empty? rules) cell
072 :else (let [result (apply-rule world cell (first rules))]
073 (cond result result
074 :else (apply-rules world cell (rest rules))))))
075
076 (defn- transform-cell
077 "Derive a cell from this `cell` of this `world` by applying these `rules`. If an
078 exception is thrown, cache its message on the cell and set it's state to error"
079 [world cell rules]
080 (try
081 (merge
082 (apply-rules world cell rules)
083 {:generation (+ (get-int-or-zero cell :generation) 1)})
084 (catch Exception e
085 (merge cell {:error
086 (format "%s at generation %d when in state %s"
087 (.getMessage e)
088 (:generation cell)
089 (:state cell))
090 :stacktrace (map #(.toString %) (.getStackTrace e))
091 :state :error}))))
092
093 (defn transform-world
094 "Return a world derived from this `world` by applying these `rules` to each cell."
095 ([world rules]
096 (map-world world transform-cell (list rules))))
097
098 (defn run-world
099 "Run this world with these rules for this number of generations.
100
101 * `world` a world as discussed above;
102 * `init-rules` a sequence of rules as defined above, to be run once to initialise the world;
103 * `rules` a sequence of rules as defined above, to be run iteratively for each generation;
104 * `generations` an (integer) number of generations.
105
106 Return the final generation of the world."
107 [world init-rules rules generations]
108 (reduce (fn [world iteration]
109 (l/info "Running iteration " iteration)
110 (transform-world world rules))
111 (transform-world world init-rules)
112 (range generations)))
113
114
115