From 98e43dbe8f5567a44719b6997fee041d67cb7250 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 24 Jul 2014 18:44:04 +0100 Subject: [PATCH] Feature complete for version 1; some refactoring still needed. --- README.md | 1 + resources/public/css/standard.css | 34 +++--- resources/public/docs/mw-engine/uberdoc.html | 25 ++++- resources/public/docs/mw-ui/uberdoc.html | 97 ++++++++++++------ .../img/heightmaps/life_gosperglidergun.png | Bin 198 -> 192 bytes resources/public/rulesets/settlement.txt | 12 ++- resources/templates/docs.html | 34 ++++-- src/mw_ui/render_world.clj | 31 +++--- src/mw_ui/routes/home.clj | 36 ++++++- test/mw_ui/test/rules.clj | 4 +- 10 files changed, 190 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index a8e1f0a..681b778 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ## What this is about +Screenshot MicroWorld is a rule driven cellular automaton. What does that mean? Well, it's a two dimensional world made up of squares called **cells**. The world develops in steps, and at each step, each cell is modified by applying the rules. diff --git a/resources/public/css/standard.css b/resources/public/css/standard.css index 1a8c13d..db66b45 100644 --- a/resources/public/css/standard.css +++ b/resources/public/css/standard.css @@ -72,9 +72,10 @@ li.nav-item a:active { background: gray; color: white; } } .widget { - margin: 0; - padding: 0.25em 1em; - border: thin solid white; + background-color: silver; + border: thin solid white; + margin-top: 0; + margin-bottom: 0; } .world { @@ -86,8 +87,6 @@ div.error { } form { - width: 100%; - background-color: silver; border: thin solid silver; } @@ -100,7 +99,7 @@ h1, h2, h3, h4, h5 { color: white; } -p, pre, ul, ol, dl, menu, h1, h2, h3, h4, h5 { +div.content, form, p, pre, ul, ol, dl, menu, h1, h2, h3, h4, h5 { padding: 0.25em 10%; } @@ -118,15 +117,8 @@ input.required:after { } label { - min-width: 35%; -} - -label, input { - padding: 0.25em 1em; - margin: 0 0.5em; -} - -label { + width: 30em; + min-width: 20em; border-right: thin solid gray; } @@ -138,9 +130,19 @@ menu li::before { content: "|| "; } -div.world table, div.world tr td { +div.world table, div.world table tr td { padding: 0; margin: 0; + border-collapse: collapse; border: none; } +table.music-ruled tr:nth-child(odd) { + background-color: silver; +} + +th, td { + text-align: left; + padding: 0 0.25em; +} + diff --git a/resources/public/docs/mw-engine/uberdoc.html b/resources/public/docs/mw-engine/uberdoc.html index 08d1adb..a604419 100644 --- a/resources/public/docs/mw-engine/uberdoc.html +++ b/resources/public/docs/mw-engine/uberdoc.html @@ -3441,7 +3441,30 @@ important.

([world cell depth state] (get-neighbours-with-state world (:x cell) (:y cell) depth state)) ([world cell state] - (get-neighbours-with-state world cell 1 state))) 

mw-engine.world

toc

Functions to create and to print two dimensional cellular automata. Nothing in this + (get-neighbours-with-state world cell 1 state)))

If this cells x and y properties are equal to these x and y values, + return a cell like this cell but with the value of this property set to + this value. Otherwise, just return this cell.

+
(defn- set-cell-property
+  [cell x y property value]
+  (cond 
+    (and (= x (:x cell)) (= y (:y cell)))
+    (merge cell {property value})
+    true
+    cell))

Return a world like this world but with the value of exactly one property + of one cell changed to this value

+
(defn set-property
+  ([world cell property value]
+    (set-property world (:x cell) (:y cell) property value))
+  ([world x y property value]
+    (apply 
+      vector ;; we want a vector of vectors, not a list of lists, for efficiency
+      (map
+        (fn [row]
+          (apply
+            vector
+            (map #(set-cell-property % x y property value)
+                 row)))
+        world))))
 

mw-engine.world

toc

Functions to create and to print two dimensional cellular automata. Nothing in this file should determine what states are possible within the automaton, except for the initial state, :new.

diff --git a/resources/public/docs/mw-ui/uberdoc.html b/resources/public/docs/mw-ui/uberdoc.html index 4057037..71f8ea5 100644 --- a/resources/public/docs/mw-ui/uberdoc.html +++ b/resources/public/docs/mw-ui/uberdoc.html @@ -3126,6 +3126,7 @@ net.brehaut.ClojureTools = (function (SH) { [mw-engine.natural-rules :as rules] [mw-parser.bulk :as compiler] [hiccup.core :refer [html]] + [noir.io :as io] [noir.session :as session]))
(defn format-css-class [statekey]
   "Format this statekey, assumed to be a keyword indicating a state in the
@@ -3141,7 +3142,7 @@ net.brehaut.ClojureTools = (function (SH) {
   [cell]
   (let [state (:state cell)]
     [:td {:class (format-css-class state) :title (format-mouseover cell)}
-     [:a {:href (format "inspect?x=%d&y=%d" (:x cell) (:y cell))}       
+     [:a {:href (format "inspect?x=%d&y=%d" (:x cell) (:y cell))}       
       [:img {:alt (:state cell) :src (format-image-path state)}]]]))

Render this world row as a Hiccup table row.

(defn render-world-row
   [row]
@@ -3151,11 +3152,13 @@ net.brehaut.ClojureTools = (function (SH) {
   (let [world (or (session/get :world)
                   (engine/transform-world
                    (heightmap/apply-heightmap
-                     "resources/public/img/heightmaps/small_hill.png"
+                     (io/get-resource "/img/heightmaps/small_hill.png")
                      ;; "resources/public/img/heightmaps/great_britain_and_ireland_small.png")
                    rules/init-rules))
         rules (or (session/get :rules) 
-                  (do (session/put! :rules (compiler/compile-file "resources/rulesets/basic.txt"))
+                  (do (session/put! :rules 
+                                    (compiler/compile-file 
+                                      (io/get-resource "/rulesets/basic.txt")))
                     (session/get :rules)))
         generation (+ (or (session/get :generation) 0) 1)
         w2 (engine/transform-world world rules)
@@ -3166,22 +3169,17 @@ net.brehaut.ClojureTools = (function (SH) {
       (apply vector
                  (cons :table
                        (map render-world-row w2)))
-      [:p (str "Generation " generation)]]))

Render the world implied by the session as a complete HTML page.

-
(defn render-world
-  []
-  (html
-   [:html
-    [:head
-     [:title "MicroWorld demo"]
-     [:link {:media "only screen and (max-device-width: 480px)" :href "css/phone.css" :type  "text/css" :rel "stylesheet"}]
-     [:link {:media "only screen and (min-device-width: 481px) and (max-device-width: 1024px)" :href "css/tablet.css" :type "text/css" :rel "stylesheet"}]
-     [:link {:media "screen and (min-device-width: 1025px)" :href "css/standard.css" :type "text/css" :rel "stylesheet"}]
-     [:link {:media "print" :href "css/print.css" :type "text/css" :rel "stylesheet"}]
-     [:link {:href "css/states.css" :type "text/css" :rel "stylesheet"}]
-     [:meta {:http-equiv "refresh" :content "5"}]]
-    [:body
-     (render-world-table)
-     ]]))
 

mw-ui.repl

toc
+ [:p (str "Generation " generation)]])) +
(defn render-inspector
+  [cell table]
+  [:table {:class "music-ruled"}
+   [:tr 
+    [:td {:colspan 2 :style "text-align: center;"} 
+     [:img {:src (str "img/tiles/" (name (:state cell)) ".png")
+            :width 64
+            :height 64}]]]
+   [:tr [:th "Key"][:th "Value"]]
+   (map #(vector :tr (vector :th %)(vector :td (cell %))) (keys cell))])
 

mw-ui.repl

toc
(ns mw-ui.repl
   (:use mw-ui.handler
         ring.server.standalone
@@ -3214,16 +3212,40 @@ net.brehaut.ClojureTools = (function (SH) {
 
(ns mw-ui.routes.home
   (:use clojure.walk
         compojure.core
+        [mw-engine.utils :as engine-utils]
         [mw-ui.routes.rules :as rules]
         [mw-ui.routes.params :as params])
   (:require [hiccup.core :refer [html]]
             [mw-ui.layout :as layout]
             [mw-ui.util :as util]
             [mw-ui.render-world :as world]
-            [noir.session :as session]))
+ [noir.session :as session] + [ring.util.response :as response]))
(defn home-page []
   (layout/render "trusted-content.html" {:title "Welcome to MicroWorld" 
                               :content (util/md->html "/md/mw-ui.md")}))
+
(defn inspect-page [request]
+  (let [params (keywordize-keys (:params request))
+        xs (:x params)
+        ys (:y params)
+        x (if (not (empty? xs)) (read-string xs) 0)
+        y (if (not (empty? ys)) (read-string ys) 0)
+        world (session/get :world)
+        cell (engine-utils/get-cell world x y)
+        state (:state params)]
+    (cond state
+      (do
+        (session/put! :world (engine-utils/set-property world cell :state (keyword state)))
+        (response/redirect "world"))
+      true
+      (layout/render "inspector.html"
+                     {:title (format "Inspect cell at %d, %d" x y)
+                      :content (html (world/render-inspector cell world))
+                      :cell cell
+                      :x (:x cell)
+                      :y (:y cell)
+                      :states (util/list-resources 
+                                "/img/tiles" #"([0-9a-z-_]+).png")}))))
(defn world-page []
   (layout/render "trusted-content.html" 
                  {:title "Watch your world grow" 
@@ -3250,8 +3272,8 @@ net.brehaut.ClojureTools = (function (SH) {
 
(defn docs-page []
   (layout/render "docs.html" {:title "Documentation"
                               :parser (util/md->html "/md/mw-parser.md" )
-                              :states (util/list-resources "resources/public/img/tiles" #"([0-9a-z-_]+).png")
-                              :lessons (util/list-resources "resources/public/md/lesson-plans"  #"([0-9a-z-_]+).md")
+                              :states (util/list-resources "/img/tiles" #"([0-9a-z-_]+).png")
+                              :lessons (util/list-resources "/md/lesson-plans"  #"([0-9a-z-_]+).md")
                               :components ["mw-engine" "mw-parser" "mw-ui"]}))
(defroutes home-routes
   (GET "/" [] (home-page))
@@ -3262,9 +3284,12 @@ net.brehaut.ClojureTools = (function (SH) {
   (GET "/md" request (md-page request))
   (POST "/params" request (params/params-page request))
   (GET "/rules" request (rules/rules-page request))
-  (POST "/rules" request (rules/rules-page request)))
  + (POST "/rules" request (rules/rules-page request)) + (GET "/inspect" request (inspect-page request)) + (POST "/inspect" request (inspect-page request)))
 

mw-ui.routes.params

toc
(ns mw-ui.routes.params
   (:use clojure.walk
+        clojure.java.io
         compojure.core)
   (:require [hiccup.core :refer [html]]
             [mw-engine.heightmap :as heightmap]
@@ -3272,12 +3297,13 @@ net.brehaut.ClojureTools = (function (SH) {
             [mw-ui.layout :as layout]
             [mw-ui.util :as util]
             [mw-ui.render-world :as world]
+            [noir.io :as io]
             [noir.session :as session]))
(defn- send-params []
   {:title "Choose your world"
-   :heightmaps (util/list-resources "resources/public/img/heightmaps" #"([0-9a-z-_]+).png")
+   :heightmaps (util/list-resources "/img/heightmaps" #"([0-9a-z-_]+).png")
    :pause (or (session/get :pause) 5)
-   :rulesets (util/list-resources "resources/rulesets" #"([0-9a-z-_]+).txt")
+   :rulesets (util/list-resources "/rulesets" #"([0-9a-z-_]+).txt")
    })

Handler for params request. If no request passed, show empty params form. If request is passed, put parameters from request into session and show the world page.

@@ -3290,15 +3316,15 @@ net.brehaut.ClojureTools = (function (SH) { map (:heightmap params) pause (:pause params) rulefile (:ruleset params) - rulepath (str "resources/rulesets/" rulefile ".txt")] + rulepath (str "/rulesets/" rulefile ".txt")] (if (not (= map "")) (session/put! :world (heightmap/apply-heightmap - (str "resources/public/img/heightmaps/" map ".png")))) + (io/get-resource (str "/img/heightmaps/" map ".png"))))) (if (not (= rulefile "")) (do - (session/put! :rule-text (slurp rulepath)) - (session/put! :rules (compiler/compile-file rulepath)))) + (session/put! :rule-text (io/slurp-resource rulepath)) + (session/put! :rules (compiler/compile-file (io/get-resource rulepath))))) (if (not (= pause "")) (session/put! :pause pause)) (layout/render "params.html" @@ -3323,6 +3349,7 @@ net.brehaut.ClojureTools = (function (SH) { [mw-ui.layout :as layout] [mw-ui.util :as util] [mw-ui.render-world :as world] + [noir.io :as io] [noir.session :as session]))
(defn process-rules-request
   [request]
@@ -3337,7 +3364,7 @@ net.brehaut.ClojureTools = (function (SH) {
                            " rules")           })
           true {:rule-text (or 
                              (session/get :rule-text) 
-                             (slurp "resources/rulesets/basic.txt"))
+                             (io/slurp-resource "/rulesets/basic.txt"))
                 :message "No rules found in request; loading defaults"})
         (catch Exception e 
           {:rule-text src
@@ -3362,6 +3389,7 @@ net.brehaut.ClojureTools = (function (SH) {
     (rules-page nil)))
 

mw-ui.util

toc
(ns mw-ui.util
   (:require [noir.io :as io]
+            [noir.session :as session]
             [markdown.core :as md]))

reads a markdown file from public/md and returns an HTML string

(defn md->html
   [filename]
@@ -3370,8 +3398,11 @@ net.brehaut.ClojureTools = (function (SH) {
     (md/md-to-html-string)))
(defn list-resources [directory pattern]
   "List resource files matching `pattern` in `directory`."
-  (sort
-    (filter #(not (nil? %)) 
+  (let 
+    [path (str (io/resource-path) directory)]
+    (session/put! :list-resources-path path)
+    (sort
+      (filter #(not (nil? %)) 
             (map #(first (rest (re-matches pattern (.getName %))))
-                 (file-seq (clojure.java.io/file directory))))))
  \ No newline at end of file diff --git a/resources/public/img/heightmaps/life_gosperglidergun.png b/resources/public/img/heightmaps/life_gosperglidergun.png index c48b7414573bae67d62b5ced79a5b23fc89c8455..c11c56c198272c41df808c8ff2dd9668f6e1073d 100644 GIT binary patch delta 135 zcmX@ccz{u{Gr-TCmrII^fq{Y7)59eQNE?7K6El$1$`KTqsOS_c!Kr2W=Mpza!q(Hp zF+}5hGKVV@kARkFr*PYY6sE+C8zCI&uRIH!eNrT@KqKp|&O z7sn8b)5#pJ%sc{GqP_||p$ftr&WD#Nu`Kg*6+6Mh%k6vC#YjX$U|MGuTL9}U-QeYm lb}9>ZA9iB}LAG87hW1}!GZJm{ZUc>D@O1TaS?83{1OOq7B*_2( diff --git a/resources/public/rulesets/settlement.txt b/resources/public/rulesets/settlement.txt index 6d66ed0..42d348a 100644 --- a/resources/public/rulesets/settlement.txt +++ b/resources/public/rulesets/settlement.txt @@ -15,7 +15,10 @@ if state is in grassland or heath and more than 2 neighbours are pasture then st if state is pasture and more than 3 neighbours are pasture and fewer than 1 neighbours are camp and fewer than 1 neighbours within 2 are house then state should be camp ;; the idea of agriculture spreads -if state is in grassland or heath and some neighbours are pasture and some neighbours within 3 are house then state should be pasture +if state is in grassland or heath and some neighbours within 3 are house then state should be pasture + +;; nomads don't move on while the have crops growing. That would be silly! +if state is camp and some neighbours are ploughland then state should be camp ;; nomads move on if state is camp then 1 chance in 5 state should be waste @@ -82,8 +85,8 @@ if state is forest and fertility is more than 5 and altitude is less than 70 the if state is climax then 1 chance in 500 state should be fire -;; Climax forest neighbouring fires is likely to catch fire. So are buildings. -if state is in climax or camp or house or inn and some neighbours are fire then 1 chance in 3 state should be fire +;; Forest neighbouring fires is likely to catch fire. So are buildings. +if state is in forest or climax or camp or house or inn and some neighbours are fire then 1 chance in 3 state should be fire ;; Climax forest near to settlement may be cleared for timber if state is in climax and more than 3 neighbours within 2 are house then state should be scrub @@ -122,5 +125,4 @@ if state is new and altitude is less than 10 then state should be water if state is new and altitude is more than 200 then state should be snow ;; otherwise, we have grassland. -if state is new then state should be grassland - +if state is new then state should be grassland \ No newline at end of file diff --git a/resources/templates/docs.html b/resources/templates/docs.html index 9c999c6..82116dc 100644 --- a/resources/templates/docs.html +++ b/resources/templates/docs.html @@ -93,12 +93,15 @@ user interface is a web browser. It would be possible to arrange a classroom with one copy of MicroWorld on a single server, and each child's machine running MicroWorld from that single server.

-

However, performance isn't very good, and unless you have an unusually - powerful server you may find that when a full class of pupils are running - MicroWorld from a single server performance may be frustratingly poor. - Check your performance before introducing a class to it, and if in doubt, +

However, many users accessing the same server at the same time may + lead to poor performance. + Check performance on your system before introducing a class to it, and if in doubt, running a separate copy on each machine used by children may well be more satisfactory.

+

If many users are using the same shared machine, you should make sure + that they don't use the 'original' or 'med' versions of the Great Britain + and Ireland map - both of these are big, and performance will inevitably + be poor.

Subject areas

One of my main objectives in writing MicroWorld was to create a system @@ -148,15 +151,34 @@ class projects. Writing rules will enable discussion of why castles, or mills, are positioned where they are, and what the social consequences of these developments are.

+

You might also want to explore the conditions for the spread of epidemic + disease - such as the black death - I've provided a state for this, + but no rules.

IT/Informatics

-

Obviously, any of the rulesets but particularly the gameoflife +

Obviously, any of the rulesets but particularly the life ruleset are good introduction points to informatics lessons. The rule language is sufficiently simple that introducing children to writing their own rules can begin almost as soon as basic literacy is established.

In more advanced IT lessons, at the upper end of primary school or in secondary schools, I would encourage you to explore modifying the engine - itself in your classes.

+ itself in your classes. Also, it would be interesting to write an + export program which would export MicroWorld maps to Minecraft, or + render them as explorable three dimensional spaces using + JMonkeyEngine + or NightMod.

+

Art and design

+

A number of projects, progressively more ambitious, are possible in + art and design.

+
    +
  1. The tiles I've drawn are pretty basic - again, that's + intentional, children can easily produce better ones. They don't have to + be 32x32, but they do all have to be the same size.
  2. +
  3. The stylesheets providing the overall look and feel of the system + could be modified
  4. +
  5. If you have an IT project to render a 3d world, you will need 3d + models of each of the states, instead of tiles. That might be a job for + Blender.

Lesson plans