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)