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.flow :refer [flow-world]]
026              [mw-engine.utils :refer [add-history-event get-int-or-zero map-world rule-type]]
027              [taoensso.timbre :as l]))
028  
029  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
030  ;;;;
031  ;;;; mw-engine: the state/transition engine of MicroWorld.
032  ;;;;
033  ;;;; This program is free software; you can redistribute it and/or
034  ;;;; modify it under the terms of the GNU General Public License
035  ;;;; as published by the Free Software Foundation; either version 2
036  ;;;; of the License, or (at your option) any later version.
037  ;;;;
038  ;;;; This program is distributed in the hope that it will be useful,
039  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
040  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
041  ;;;; GNU General Public License for more details.
042  ;;;;
043  ;;;; You should have received a copy of the GNU General Public License
044  ;;;; along with this program; if not, write to the Free Software
045  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
046  ;;;; USA.
047  ;;;;
048  ;;;; Copyright (C) 2014 Simon Brooke
049  ;;;;
050  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
051  
052  (def ^:dynamic *with-history*
053    "I suspect that caching history on the cells is greatly worsening the
054     memory problems. Make it optional, but by default false."
055    false)
056  
057  (defn apply-rule
058    "Apply a single `rule` to a `cell`. What this is about is that I want to be able,
059     for debugging purposes, to tag a cell with the rule text of the rule which
060     fired (and especially so when an exception is thrown). "
061    ;; as of version 0-3-0, metadata for rules is now passed around on the metadata
062    ;; of the rule function itself. Yes, I know, this is obvious; but I'll confess
063    ;; I didn't think of it before.
064    [world cell rule]
065    (let [result (try
066                   (apply rule (list cell world))
067                   (catch Exception e
068                     (l/warn e
069                             (format
070                              "Error in `apply-rule`: `%s` (%s) while executing rule `%s` on cell `%s`"
071                              e
072                              (.getMessage e)
073                              (-> rule meta :lisp)
074                              cell))))]
075      (if *with-history*
076        (add-history-event result rule)
077        result)))
078  
079  (defn- apply-rules
080    "Derive a cell from this `cell` of this `world` by applying these `rules`."
081    [world cell rules]
082    (or
083     (first
084      (remove
085       nil?
086       (try
087         (map #(apply-rule world cell %) rules)
088         (catch Exception e
089           (l/warn e
090                   (format
091                    "Error in `apply-rules`: `%s` (%s) while executing rules on cell `%s`"
092                    (-> e .getClass .getName)
093                    (.getMessage e)
094                    cell))))))
095     cell))
096  
097  (defn- transform-cell
098    "Derive a cell from this `cell` of this `world` by applying these `rules`. If an
099     exception is thrown, cache its message on the cell and set it's state to error"
100    [world cell rules]
101    (try
102      (merge
103       (apply-rules world cell rules)
104       {:generation (+ (get-int-or-zero cell :generation) 1)})
105      (catch Exception e
106        (let [narrative (format "Error in `transform-cell`: `%s` (%s) at generation %d when in state %s;"
107                                (-> e .getClass .getName)
108                                (.getMessage e)
109                                (:generation cell)
110                                (:state cell))]
111          (l/warn e narrative)
112          cell))))
113  
114  (defn transform-world
115    "Return a world derived from this `world` by applying the production rules 
116    found among these `rules` to each cell."
117    [world rules]
118    (map-world world transform-cell
119               ;; Yes, that `list` is there for a reason! 
120               (list
121                (filter
122                 #(= :production (rule-type %))
123                 rules))))
124  
125  (defn run-world
126    "Run this world with these rules for this number of generations.
127  
128     * `world` a world as discussed above;
129     * `init-rules` a sequence of rules as defined above, to be run once to initialise the world;
130     * `rules` a sequence of rules as defined above, to be run iteratively for each generation;
131     * `generations` an (integer) number of generations.
132     
133     **NOTE THAT** all rules **must** be tagged with `rule-type` metadata, or thet **will not**
134     be executed.
135  
136     Return the final generation of the world."
137    ([world rules generations]
138     (run-world world rules rules (dec generations)))
139    ([world init-rules rules generations]
140     (reduce (fn [world iteration]
141               (l/info "Running iteration " iteration)
142               (let [w' (transform-world world rules)]
143                 (flow-world w' rules)))
144             (transform-world world init-rules)
145             (range generations))))