001  (ns ^{:doc "Functions to create and to print two dimensional cellular automata.
002              
003              Nothing in this namespace should determine what states are possible within
004              the automaton, except for the initial state, :new.
005  
006              A cell is a map containing at least values for the keys `:x`, `:y`, and `:state`.
007  
008              A world is a two dimensional matrix (sequence of sequences) of cells, such
009              that every cell's `:x` and `:y` properties reflect its place in the matrix."
010        :author "Simon Brooke"}
011   mw-engine.world
012    (:require [clojure.string :as string]
013              [mw-engine.utils :refer [population]]))
014  
015  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
016  ;;;;
017  ;;;; mw-engine: the state/transition engine of MicroWorld.
018  ;;;;
019  ;;;; This program is free software; you can redistribute it and/or
020  ;;;; modify it under the terms of the GNU General Public License
021  ;;;; as published by the Free Software Foundation; either version 2
022  ;;;; of the License, or (at your option) any later version.
023  ;;;;
024  ;;;; This program is distributed in the hope that it will be useful,
025  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
026  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
027  ;;;; GNU General Public License for more details.
028  ;;;;
029  ;;;; You should have received a copy of the GNU General Public License
030  ;;;; along with this program; if not, write to the Free Software
031  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
032  ;;;; USA.
033  ;;;;
034  ;;;; Copyright (C) 2014 Simon Brooke
035  ;;;;
036  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
037  
038  (defn cell?
039    "Return `true` if `obj` is a cell, as understood by MicroWorld, else `false`."
040    [obj]
041    (and (map? obj) ;; it's a map...
042         ;; TODO: it's worth checking (and this does not) that cells have the
043         ;; right co-ordinates!
044         (pos-int? (:x obj)) ;; with an x co-ordinate...
045         (pos-int? (:y obj)) ;; and a y co-ordinate...
046         (keyword? (:state obj)))) ;; and a state which is a keyword.
047  
048  (defn world?
049    "Return `true` if `obj` is a world, as understood by MicroWorld, else `false`."
050    [obj]
051    (and (coll? obj) ;; it's a collection...
052         (every? coll? obj) ;; of collections...
053         (= 1 (count (set (map count obj)))) ;; all of which are the same length...
054         (every? cell? (flatten obj)))) ;; and every element of each of those is a cell.
055  
056  (defmacro make-cell
057    "Create a minimal default cell at x, y
058  
059    * `x` the x coordinate at which this cell is created;
060    * `y` the y coordinate at which this cell is created."
061    [x y]
062    `{:x ~x :y ~y :state :new})
063  
064  (defn make-world
065    "Make a world width cells from east to west, and height cells from north to
066     south.
067  
068    * `width` a natural number representing the width of the matrix to be created;
069    * `height` a natural number representing the height of the matrix to be created."
070    [width height]
071    (apply vector
072           (map (fn [h]
073                  (apply vector (map #(make-cell % h) (range width))))
074                (range height))))
075  
076  (defn truncate-state
077    "Truncate the print name of the state of this cell to at most limit characters."
078    [cell limit]
079    (let [s (:state cell)]
080      (try
081        (cond (> (count (str s)) limit) (subs (name s) 0 limit)
082            :else s)
083        (catch Exception any 
084          (throw (ex-info (.getMessage any) 
085                          {:cell cell
086                           :limit limit
087                           :exception-class (.getClass any)}))))))
088  
089  (defn format-cell
090    "Return a formatted string summarising the current state of this cell."
091    [cell]
092    (format "%10s"
093            (truncate-state cell 10)))
094  
095  (defn- format-world-row
096    "Format one row in the state of a world for printing."
097    [row]
098    (string/join (map format-cell row)))
099  
100  (defn print-world
101    "Print the current state of this world, and return nil.
102  
103    * `world` a world as defined above."
104    [world]
105    (println)
106    (dorun
107     (map
108      #(println
109        (format-world-row %))
110      world))
111    nil)