git @ Cat's Eye Technologies noit-o-mnain-worb / master impl / worb.js / src / worb.js
master

Tree @master (Download .tar.gz)

worb.js @masterraw · history · blame

/*
 * worb.js -- An implementation of noit o' mnain worb, using yoob.js
 * requires the following classes to be sourced before this file:
 * yoob.Controller
 * yoob.Playfield
 */

/*
 * Copyright (c)2013-2015, Chris Pressey, Cat's Eye Technologies.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 *   Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 *   Neither the name of Cat's Eye Technologies nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

Bobule = function() {
    this.pressure = 1;
    this.serial = 0;

    this.move = function(serial, pf, x, y) {
        if (this.serial > serial)
            return;

        this.pressure++;

        var newX = x + (Math.floor(Math.random() * 3) - 1);
        var newY = y + (Math.floor(Math.random() * 3) - 1);

        var e = pf.get(newX, newY);
        if (e instanceof Bobule || e instanceof Wall) {
            return;
        }
        if (e instanceof Diode) {
            var dx = e.dx;
            var dy = e.dy;
            if (dx === -1 && newX > x) return;
            if (dx === 1 && newX < x) return;
            if (dy === -1 && newY > y) return;
            if (dy === 1 && newY < y) return;
        }
        pf.put(x, y, undefined);
        this.pressure = 1;
        pf.put(newX, newY, this);
        this.serial = serial + 1;
    };

    this.draw = function(ctx, px, py, x, y, w, h) {
        ctx.fillStyle = "rgba(0,0," + (255-(this.pressure * 25)) + ",1.0)";
        ctx.fillRect(x, y, w, h);
    };
};

Wall = function() {
    this.draw = function(ctx, px, py, x, y, w, h) {
        ctx.fillStyle = "brown";
        ctx.fillRect(x, y, w, h);
    };
};

Diode = function() {
    this.init = function(dx, dy) {
        this.dx = dx;
        this.dy = dy;
    };

    this.draw = function(ctx, px, py, x, y, w, h) {
        ctx.fillStyle = "yellow";
        ctx.fillRect(x, y, w, h);
        ctx.beginPath();
        ctx.fillStyle = "orange";
        if (this.dx === 0 && this.dy === -1) {
            ctx.lineTo(x + (w*0.25), y + (h*0.75));
            ctx.lineTo(x + (w/2), y + (h*0.25));
            ctx.lineTo(x + (w*0.75), y + (h*0.75));
        } else if (this.dx === 0 && this.dy === 1) {
            ctx.lineTo(x + (w*0.25), y + (h*0.25));
            ctx.lineTo(x + (w/2), y + (h*0.75));
            ctx.lineTo(x + (w*0.75), y + (h*0.25));
        } else if (this.dx === 1 && this.dy === 0) {
            ctx.lineTo(x + (w*0.25), y + (h*0.25));
            ctx.lineTo(x + (w*0.75), y + (h*0.50));
            ctx.lineTo(x + (w*0.25), y + (h*0.75));
        } else if (this.dx === -1 && this.dy === 0) {
            ctx.lineTo(x + (w*0.75), y + (h*0.25));
            ctx.lineTo(x + (w*0.25), y + (h*0.50));
            ctx.lineTo(x + (w*0.75), y + (h*0.75));
        }
        ctx.closePath();
        ctx.fill();
    };
};

Source = function() {
    this.draw = function(ctx, px, py, x, y, w, h) {
        ctx.fillStyle = "green";
        ctx.fillRect(x, y, w, h);
    };
};

Sink = function() {
    this.draw = function(ctx, px, py, x, y, w, h) {
        ctx.fillStyle = "red";
        ctx.fillRect(x, y, w, h);
    };
};

Load = function() {
    this.draw = function(ctx, px, py, x, y, w, h) {
        ctx.fillStyle = "cyan";
        ctx.fillRect(x, y, w, h);
    };
};

/*
 * Adapter on top of yoob.Playfield
 */
WorbPlayfield = function() {
    this.init = function(cfg) {
        this.worldPf = new yoob.Playfield().init({});
        this.bobulePf = new yoob.Playfield().init({});
        this.cursors = [];
        return this;
    };

    this.get = function(x, y) {
        var e = this.bobulePf.get(x, y);
        if (e === undefined) {
            e = this.worldPf.get(x, y);
        }
        return e;
    };

    this.put = function(x, y, value) {
        if (value === undefined) {
            this.bobulePf.put(x, y, undefined);
        } else if (value instanceof Bobule) {
            var e = this.bobulePf.get(x, y);
            if (e === undefined) {
                this.bobulePf.put(x, y, value);
            } else {
                alert("Error! Can't overwrite bobule");
            }
        } else {
            this.worldPf.put(x, y, value);
        }
    };

    /*
     * Clear the contents of this Playfield.
     */
    this.clear = function() {
        this.bobulePf.clear();
        this.worldPf.clear();
    };
          
    /*
     * Load a string into the playfield.
     * The string may be multiline, with newline (ASCII 10)
     * characters delimiting lines.  ASCII 13 is ignored.
     */
    this.load = function(x, y, string) {
        var lx = x;
        var ly = y;
        for (var i = 0; i < string.length; i++) {
            var c = string.charAt(i);
            if (c === '\n') {
                lx = x;
                ly++;
            } else if (c === ' ') {
                this.put(lx, ly, undefined);
                lx++;
            } else if (c === '\r') {
            } else {
                if (c === '.') {
                    this.put(lx, ly, new Bobule());
                } else if (c === '#') {
                    this.put(lx, ly, new Wall());
                } else if (c === '+') {
                    this.put(lx, ly, new Source());
                } else if (c === '-') {
                    this.put(lx, ly, new Sink());
                } else if (c === '!') {
                    this.put(lx, ly, new Load());
                } else if (c === '>') {
                    var g = new Diode();
                    g.init(1, 0);
                    this.put(lx, ly, g);
                } else if (c === '<') {
                    var g = new Diode();
                    g.init(-1, 0);
                    this.put(lx, ly, g);
                } else if (c === '^') {
                    var g = new Diode();
                    g.init(0, -1);
                    this.put(lx, ly, g);
                } else if (c === 'v') {
                    var g = new Diode();
                    g.init(0, 1);
                    this.put(lx, ly, g);
                }
                lx++;
            }
        }
    };

    /*
     * Iterate over every defined cell in the Playfield.
     * fun is a callback which takes three parameters:
     * x, y, value.
     * This function ensures a particular order.
     */
    this.foreach = function(fun) {
        var lowerY = this.getLowerY();
        var upperY = this.getUpperY();
        var lowerX = this.getLowerX();
        var upperX = this.getUpperX();
        for (var y = lowerY; y <= upperY; y++) {
            for (var x = lowerX; x <= upperX; x++) {
                var value = this.get(x, y);
                if (value === undefined)
                    continue;
                fun(x, y, value);
            }
        }
    };

    this.foreachBobule = function(fun) {
        this.bobulePf.foreach(fun);
    };

    this.foreachWorld = function(fun) {
        this.worldPf.foreach(fun);
    };

    /*
     * Draws the Playfield in a drawing context.
     * cellWidth and cellHeight are canvas units of measure for each cell.
     */
    this.drawContext = function(ctx, offsetX, offsetY, cellWidth, cellHeight) {
        this.foreach(function (x, y, elem) {
            elem.draw(ctx, offsetX + x * cellWidth, offsetY + y * cellHeight,
                           cellWidth, cellHeight);
        });
    };

    this.getLowerX = function() {
        return this.worldPf.getLowerX();
    };

    this.getUpperX = function() {
        return this.worldPf.getUpperX();
    };

    this.getLowerY = function() {
        return this.worldPf.getLowerY();
    };

    this.getUpperY = function() {
        return this.worldPf.getUpperY();
    };

    this.getCursoredExtentX = function() {
        return this.worldPf.getCursoredExtentX();
    };

    this.getCursoredExtentY = function() {
        return this.worldPf.getCursoredExtentY();
    };
};

var proto = new yoob.Controller();
WorbController = function() {
    this.init = function(cfg) {
        proto.init.apply(this, [cfg]);
        this.pf = new WorbPlayfield().init({});
        cfg.view.setPlayfield(this.pf);
        this.view = cfg.view;
        this.serial = 0;
        return this;
    };

    this.step = function() {
        var underLoad = false;
        var pf = this.pf;
        var serial = this.serial;
        pf.foreachBobule(function(x, y, elem) {
            elem.move(serial, pf, x, y);
        });
        pf.foreachWorld(function(x, y, elem) {
            var b;
            if (elem instanceof Sink) {
                b = pf.get(x, y);
                if (b instanceof Bobule && Math.random() <= 0.10) {
                    pf.put(x, y, undefined);
                }
            } else if (elem instanceof Source) {
                b = pf.get(x, y);
                if (!(b instanceof Bobule) && Math.random() <= 0.10) {
                    pf.put(x, y, new Bobule());
                }
            } else if (elem instanceof Load) {
                b = pf.get(x, y);
                if (b instanceof Bobule) {
                    underLoad = true;
                }
            }
        });
        if (this.onstep !== undefined) {
            this.onstep(underLoad);
        }
        this.view.draw();
        this.serial++;
    };

    this.reset = function(text) {
        this.pf.clear();
        this.pf.load(0, 0, text);
        this.view.draw();
    };
};
WorbController.prototype = proto;