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