Commits

catseye  committed ba407ed

Stick almost all of yoob/playfield.js inline in the JS backend.

  • Participants
  • Parent commits eccd694

Comments (0)

Files changed (1)

File src/alpaca/backends/javascript.py

 )
 
 
+# We'll just stick (almost) the entire yoob/playfield.js file inline here
+# see https://github.com/catseye/yoob.js for where this came from (v0.2)
+YOOB_PLAYFIELD_JS = r"""
+var yoob = {};
+if (typeof window !== 'undefined') {
+    if (!window.yoob) window.yoob = {};
+    yoob = window.yoob;
+}
+
+/*
+ * A two-dimensional Cartesian grid of values.
+ */
+yoob.Playfield = function() {
+    this._store = {};
+    this.minX = undefined;
+    this.minY = undefined;
+    this.maxX = undefined;
+    this.maxY = undefined;
+    this._default = undefined;
+
+    /*
+     * Set the default value for this Playfield.  This
+     * value is returned by get() for any cell that was
+     * never written to, or had `undefined` put() into it.
+     */
+    this.setDefault = function(v) {
+        this._default = v;
+    };
+
+    /*
+     * Obtain the value at (x, y).  The default value will
+     * be returned if the cell was never written to.
+     */
+    this.get = function(x, y) {
+        var v = this._store[x+','+y];
+        if (v === undefined) return this._default;
+        return v;
+    };
+
+    /*
+     * Write a new value into (x, y).  Note that writing
+     * `undefined` into a cell has the semantics of deleting
+     * the value at that cell; a subsequent get() for that
+     * location will return this Playfield's default value.
+     */
+    this.put = function(x, y, value) {
+        var key = x+','+y;
+        if (value === undefined || value === this._default) {
+            delete this._store[key];
+            return;
+        }
+        if (this.minX === undefined || x < this.minX) this.minX = x;
+        if (this.maxX === undefined || x > this.maxX) this.maxX = x;
+        if (this.minY === undefined || y < this.minY) this.minY = y;
+        if (this.maxY === undefined || y > this.maxY) this.maxY = y;
+        this._store[key] = value;
+    };
+
+    /*
+     * Like put(), but does not update the playfield bounds.  Do
+     * this if you must do a batch of put()s in a more efficient
+     * manner; after doing so, call recalculateBounds().
+     */
+    this.putDirty = function(x, y, value) {
+        var key = x+','+y;
+        if (value === undefined || value === this._default) {
+            delete this._store[key];
+            return;
+        }
+        this._store[key] = value;
+    };
+
+    /*
+     * Recalculate the bounds (min/max X/Y) which are tracked
+     * internally to support methods like foreach().  This is
+     * not needed *unless* you've used putDirty() at some point.
+     * (In which case, call this immediately after your batch
+     * of putDirty()s.)
+     */
+    this.recalculateBounds = function() {
+        this.minX = undefined;
+        this.minY = undefined;
+        this.maxX = undefined;
+        this.maxX = undefined;
+
+        for (var cell in this._store) {
+            var pos = cell.split(',');
+            var x = parseInt(pos[0], 10);
+            var y = parseInt(pos[1], 10);
+            if (this.minX === undefined || x < this.minX) this.minX = x;
+            if (this.maxX === undefined || x > this.maxX) this.maxX = x;
+            if (this.minY === undefined || y < this.minY) this.minY = y;
+            if (this.maxY === undefined || y > this.maxY) this.maxY = y;
+        }
+    };
+
+    /*
+     * Clear the contents of this Playfield.
+     */
+    this.clear = function() {
+        this._store = {};
+        this.minX = undefined;
+        this.minY = undefined;
+        this.maxX = undefined;
+        this.maxX = undefined;
+    };
+
+    /*
+     * Load a string into this Playfield.
+     * The string may be multiline, with newline (ASCII 10)
+     * characters delimiting lines.  ASCII 13 is ignored.
+     *
+     * If transformer is given, it should be a one-argument
+     * function which accepts a character and returns the
+     * object you wish to write into the playfield upon reading
+     * that character.
+     */
+    this.load = function(x, y, string, transformer) {
+        var lx = x;
+        var ly = y;
+        if (transformer === undefined) {
+            transformer = function(c) {
+                if (c === ' ') {
+                    return undefined;
+                } else {
+                    return c;
+                }
+            }
+        }
+        for (var i = 0; i < string.length; i++) {
+            var c = string.charAt(i);
+            if (c === '\n') {
+                lx = x;
+                ly++;
+            } else if (c === '\r') {
+            } else {
+                this.putDirty(lx, ly, transformer(c));
+                lx++;
+            }
+        }
+        this.recalculateBounds();
+    };
+
+    /*
+     * Convert this Playfield to a multi-line string.  Each row
+     * is a line, delimited with a newline (ASCII 10).
+     *
+     * If transformer is given, it should be a one-argument
+     * function which accepts a playfield element and returns a
+     * character (or string) you wish to place in the resulting
+     * string for that element.
+     */
+    this.dump = function(transformer) {
+        var text = "";
+        if (transformer === undefined) {
+            transformer = function(c) { return c; }
+        }
+        for (var y = this.minY; y <= this.maxY; y++) {
+            var row = "";
+            for (var x = this.minX; x <= this.maxX; x++) {
+                row += transformer(this.get(x, y));
+            }
+            text += row + "\n";
+        }
+        return text;
+    };
+
+    /*
+     * Iterate over every defined cell in the Playfield.
+     * fun is a callback which takes three parameters:
+     * x, y, and value.  If this callback returns a value,
+     * it is written into the Playfield at that position.
+     * This function ensures a particular order.
+     */
+    this.foreach = function(fun) {
+        for (var y = this.minY; y <= this.maxY; y++) {
+            for (var x = this.minX; x <= this.maxX; x++) {
+                var key = x+','+y;
+                var value = this._store[key];
+                if (value === undefined)
+                    continue;
+                var result = fun(x, y, value);
+                if (result !== undefined) {
+                    if (result === ' ') {
+                        result = undefined;
+                    }
+                    this.put(x, y, result);
+                }
+            }
+        }
+    };
+
+    /*
+     * Analogous to (monoid) map in functional languages,
+     * iterate over this Playfield, transform each value using
+     * a supplied function, and write the transformed value into
+     * a destination Playfield.
+     *
+     * Supplied function should take a Playfield (this Playfield),
+     * x, and y, and return a value.
+     *
+     * The map source may extend beyond the internal bounds of
+     * the Playfield, by giving the min/max Dx/Dy arguments
+     * (which work like margin offsets.)
+     *
+     * Useful for evolving a cellular automaton playfield.  In this
+     * case, min/max Dx/Dy should be computed from the neighbourhood.
+     */
+    this.map = function(destPf, fun, minDx, minDy, maxDx, maxDy) {
+        if (minDx === undefined) minDx = 0;
+        if (minDy === undefined) minDy = 0;
+        if (maxDx === undefined) maxDx = 0;
+        if (maxDy === undefined) maxDy = 0;
+        for (var y = this.minY + minDy; y <= this.maxY + maxDy; y++) {
+            for (var x = this.minX + minDx; x <= this.maxX + maxDx; x++) {
+                destPf.putDirty(x, y, fun(pf, x, y));
+            }
+        }
+        destPf.recalculateBounds();
+    };
+
+    this.getExtentX = function() {
+        if (this.maxX === undefined || this.minX === undefined) {
+            return 0;
+        } else {
+            return this.maxX - this.minX + 1;
+        }
+    };
+
+    this.getExtentY = function() {
+        if (this.maxY === undefined || this.minY === undefined) {
+            return 0;
+        } else {
+            return this.maxY - this.minY + 1;
+        }
+    };
+};
+"""
+
 class Compiler(object):
     def __init__(self, alpaca, file):
         """alpaca is an ALPACA description in AST form.  file is a file-like
  * EDIT AT YOUR OWN RISK!
  */
 
-Playfield = function() {
-    this._store = {};
-    this.min_x = undefined;
-    this.min_y = undefined;
-    this.max_x = undefined;
-    this.max_y = undefined;
-    this.default = undefined;
-
-    this.get = function(x, y) {
-        var state = this._store[x+','+y];
-        return state === undefined ? this.default : state;
-    };
-
-    this.put = function(x, y, value) {
-        if (value === this.default) {
-            delete this._store[x+','+y];
-        } else {
-            this._store[x+','+y] = value;
-        }
-    };
-
-    this.recalculate_limits = function() {
-        this.min_x = undefined;
-        this.min_y = undefined;
-        this.max_x = undefined;
-        this.max_y = undefined;
-
-        for (var cell in this._store) {
-            var pos = cell.split(',');
-            var x = parseInt(pos[0], 10);
-            var y = parseInt(pos[1], 10);
-            if (this.min_x === undefined || this.min_x > x)
-                this.min_x = x;
-            if (this.max_x === undefined || this.max_x < x)
-                this.max_x = x;
-            if (this.min_y === undefined || this.min_y > y)
-                this.min_y = y;
-            if (this.max_y === undefined || this.max_y < y)
-                this.max_y = y;
-        }
-    };
-};
-
+""")
+        self.file.write(YOOB_PLAYFIELD_JS)
+        self.file.write("""
 function in_nbhd_pred(pf, x, y, pred, nbhd) {
   var count = 0;
   for (var i = 0; i < nbhd.length; i++) {
 }
 
 function evolve_playfield(pf, new_pf) {
-  for (var y = pf.min_y - %d; y <= pf.max_y - %d; y++) {
-    for (var x = pf.min_x - %d; x <= pf.max_x - %d; x++) {
-      new_pf.put(x, y, evalState(pf, x, y));
-    }
-  }
-  new_pf.recalculate_limits();
+  pf.map(new_pf, evalState, %d, %d, %d, %d);
 }
-""" % (bb.max_dy, bb.min_dy, bb.max_dx, bb.min_dx))
+""" % (-1 * bb.max_dx, -1 * bb.max_dy, -1 * bb.min_dx, -1 * bb.min_dy,))
         class_map = get_class_map(self.alpaca)
         for (class_id, state_set) in class_map.iteritems():
             self.file.write("function is_%s(st) {\n" % class_id)
         pf = get_defined_playfield(self.alpaca)
         if pf is not None:
             self.file.write("""
-pf = new Playfield();
-pf.default = '%s';
+pf = new yoob.Playfield();
+pf.setDefault('%s');
 """ % pf.default)
             for (x, y, c) in pf.iteritems():
-                self.file.write("pf.put(%d, %d, '%s');\n" % (x, y, c))
-            self.file.write("pf.recalculate_limits();\n")
+                self.file.write("pf.putDirty(%d, %d, '%s');\n" % (x, y, c))
+            self.file.write("pf.recalculateBounds();\n")
+            # TODO: use pf.dump()
             self.file.write("""
 function dump_playfield(pf) {
-  for (var y = pf.min_y; y <= pf.max_y; y++) {
+  for (var y = pf.minY; y <= pf.maxY; y++) {
     var line = '';
-    for (var x = pf.min_x; x <= pf.max_x; x++) {
+    for (var x = pf.minX; x <= pf.maxX; x++) {
       s = pf.get(x, y);
 """)
             for (state_id, char) in pf.state_to_repr.iteritems():
 }  
 """)
             self.file.write("""
-new_pf = new Playfield();
-new_pf.default = '%s';
+new_pf = new yoob.Playfield();
+new_pf.setDefault('%s');
 evolve_playfield(pf, new_pf);
 console.log('-----');
 dump_playfield(new_pf);