View Source Document

the-new-gamerly-realism.js

if (window.yoob === undefined) yoob = {};

function launch(scriptRoot, containerId) {
    var container = document.getElementById(containerId);

    var canvas = document.createElement('canvas');
    canvas.width = 400;
    canvas.height = canvas.width;
    container.appendChild(canvas);

    var game = /* The */ new GamerlyRealism();
    game.init(canvas);
    game.reset();
    canvas.onclick = function() {
        if (game.lives === 0) {
            game.reset();
        }
    };
}

yoob.Sprite = function() {

    /*
     * x and y always represent the CENTRE of the Sprite().
     * Chainable.
     */
    this.init = function(cfg) {
        this.x = cfg.x;
        this.y = cfg.y;
        this.width = cfg.width;
        this.height = cfg.height;
        this.dx = cfg.dx || 0;
        this.dy = cfg.dy || 0;
        this.isDraggable = cfg.isDraggable || false;
        this.isClickable = cfg.isClickable || false;
        this.fillStyle = cfg.fillStyle || "green";
        this.visible = (cfg.visible === undefined ? true : (!!cfg.visible));
        this._isBeingDragged = false;
        return this;
    };

    this.getX = function() {
        return this.x;
    };

    this.getLeftX = function() {
        return this.x - this.width / 2;
    };

    this.getRightX = function() {
        return this.x + this.width / 2;
    };

    this.getY = function() {
        return this.y;
    };

    this.getTopY = function() {
        return this.y - this.height / 2;
    };

    this.getBottomY = function() {
        return this.y + this.height / 2;
    };

    this.getWidth = function() {
        return this.width;
    };

    this.getHeight = function() {
        return this.height;
    };

    this.isBeingDragged = function() {
        return this._isBeingDragged;
    };

    /*
     * Chainable.
     */
    this.setPosition = function(x, y) {
        this.x = x;
        this.y = y;
        return this;
    };

    /*
     * Chainable.
     */
    this.setDimensions = function(width, height) {
        this.width = width;
        this.height = height;
        return this;
    };

    /*
     * Chainable.
     */
    this.setVelocity = function(dx, dy) {
        this.dx = dx;
        this.dy = dy;
        return this;
    };

    /*
     * Chainable.
     */
    this.setDestination = function(x, y, ticks) {
        this.destX = x;
        this.destY = y;
        this.setVelocity((this.destX - this.getX()) / ticks, (this.destY - this.getY()) / ticks);
        this.destCounter = ticks;
        return this;
    };

    this.move = function(x, y) {
        this.x += this.dx;
        this.y += this.dy;
        this.onmove();
        if (this.destCounter !== undefined) {
            this.destCounter--;
            if (this.destCounter <= 0) {
                this.destCounter = undefined;
                this.setPosition(this.destX, this.destY).setVelocity(0, 0);
                this.onreachdestination();
            }
        }
    };

    // override this if your shape is not a rectangle
    this.containsPoint = function(x, y) {
        return (x >= this.getLeftX() && x <= this.getRightX() &&
                y >= this.getTopY() && y <= this.getBottomY());
    };

    // you may need to override this in a sophisticated way if you
    // expect it to detect sprites of different shapes intersecting
    // assumes given sprite is LARGER THAN or EQUAL IN SIZE TO this.
    this.intersects = function(sprite) {
        var x1 = this.getLeftX();
        var x2 = this.getRightX();
        var y1 = this.getTopY();
        var y2 = this.getBottomY();
        return (sprite.containsPoint(x1, y1) || sprite.containsPoint(x2, y1) ||
                sprite.containsPoint(x1, y2) || sprite.containsPoint(x2, y2));
    };

    // you will probably want to override this
    // if you do, it's up to you to honour this.visible.
    this.draw = function(ctx) {
        if (!this.visible) return;
        ctx.fillStyle = this.fillStyle || "green";
        ctx.fillRect(this.getLeftX(), this.getTopY(), this.getWidth(), this.getHeight());
    };

    // event handlers.  override to detect these events.
    this.ongrab = function() {
    };
    this.ondrag = function() {
    };
    this.ondrop = function() {
    };
    this.onclick = function() {
    };
    this.onmove = function() {
    };
    this.onreachdestination = function() {
    };

};

/*
 * This still has a few shortcomings at the moment.
 */
yoob.SpriteManager = function() {
    /*
     * Attach this SpriteManager to a canvas.
     */
    this.init = function(cfg) {
        this.canvasX = undefined;
        this.canvasY = undefined;
        this.offsetX = undefined;
        this.offsetY = undefined;
        this.dragging = undefined;
        this.sprites = [];

        this.canvas = cfg.canvas;

        return this;
    };

    this.move = function() {
        this.foreach(function(sprite) {
            sprite.move();
        });
    };

    this.draw = function(ctx) {
        if (ctx === undefined) {
            ctx = this.canvas.getContext('2d');
        }
        this.foreach(function(sprite) {
            sprite.draw(ctx);
        });
    };

    this.addSprite = function(sprite) {
        this.sprites.push(sprite);
    };

    this.removeSprite = function(sprite) {
        var index = undefined;
        for (var i = 0; i < this.sprites.length; i++) {
            if (this.sprites[i] === sprite) {
                index = i;
                break;
            }
        }
        if (index !== undefined) {
            this.sprites.splice(index, 1);
        }
    };

    this.clearSprites = function() {
        this.sprites = [];
    };

    this.moveToFront = function(sprite) {
        this.removeSprite(sprite);
        this.sprites.push(sprite);
    };

    this.moveToBack = function(sprite) {
        this.removeSprite(sprite);
        this.sprites.unshift(sprite);
    };

    this.getSpriteAt = function(x, y) {
        for (var i = this.sprites.length-1; i >= 0; i--) {
            var sprite = this.sprites[i];
            if (sprite.containsPoint(x, y)) {
                return sprite;
            }
        }
        return undefined;
    };

    this.foreach = function(fun) {
        for (var i = this.sprites.length-1; i >= 0; i--) {
            var sprite = this.sprites[i];
            var result = fun(sprite);
            if (result === 'remove') {
                this.removeSprite(sprite);
            }
            if (result === 'return') {
                return sprite;
            }
        }
    };

};

yoob.Joystick = function() {
    this.init = function() {
        this.dx = 0;
        this.dy = 0;
        this.leftPressed = false;
        this.rightPressed = false;
        this.upPressed = false;
        this.downPressed = false;
        this.buttonPressed = false;
        this.keyMap = {
            17: this.fire,
            37: this.left,
            38: this.up,
            39: this.right,
            40: this.down
        };
        this.onchange = undefined;
        return this;
    };

    this.fire = function(obj, pressed) {
        if (obj.buttonPressed === pressed) return;
        obj.buttonPressed = pressed;
        if (obj.onchange !== undefined) obj.onchange();
    };

    this.left = function(obj, pressed) {
        if (obj.leftPressed === pressed) return;
        obj.leftPressed = pressed;
        obj.dx = (obj.leftPressed ? -1 : 0) + (obj.rightPressed ? 1 : 0);
        if (obj.onchange !== undefined) obj.onchange();
    };

    this.up = function(obj, pressed) {
        if (obj.upPressed === pressed) return;
        obj.upPressed = pressed;
        obj.dy = (obj.upPressed ? -1 : 0) + (obj.downPressed ? 1 : 0);
        if (obj.onchange !== undefined) obj.onchange();
    };

    this.right = function(obj, pressed) {
        if (obj.rightPressed === pressed) return;
        obj.rightPressed = pressed;
        obj.dx = (obj.leftPressed ? -1 : 0) + (obj.rightPressed ? 1 : 0);
        if (obj.onchange !== undefined) obj.onchange();
    };

    this.down = function(obj, pressed) {
        if (obj.downPressed === pressed) return;
        obj.downPressed = pressed;
        obj.dy = (obj.upPressed ? -1 : 0) + (obj.downPressed ? 1 : 0);
        if (obj.onchange !== undefined) obj.onchange();
    };

    this.attach = function(element) {
        var $this = this;
        element.addEventListener('keydown', function(e) {
            var u = $this.keyMap[e.keyCode];
            if (u !== undefined) {
                u($this, true);
                e.cancelBubble = true;
                e.preventDefault();
            }
        }, true);
        element.addEventListener('keyup', function(e) {
            var u = $this.keyMap[e.keyCode];
            if (u !== undefined) {
                u($this, false);
                e.cancelBubble = true;
                e.preventDefault();
            }
        }, true);
    };
};


var HERO_COLOR = 'black';
var TREASURE_COLOR = 'black';
var PIT_COLOR = 'black';
var BACKGROUND_COLOR = 'black';
var SCORE_COLOR = 'black';


var GamerlyRealism = function() {
    this.init = function(canvas) {
        this.canvas = canvas;

        this.manager = (new yoob.SpriteManager()).init({ canvas: this.canvas });

        this.joystick = (new yoob.Joystick()).init();
        this.joystick.attach(document.documentElement);

        return this;
    };

    this.reset = function() {
        if (this.intervalId) {
            clearInterval(this.intervalId);
        }
        this.manager.clearSprites();

        this.numTreasures = 0;

        this.hero = this.makeHero();
        for (var i = 0; i < 10; i++) {
            this.makePit();
        }
        this.makeTreasures();

        this.score = 0;
        this.lives = 3;

        this.paused = false;
        var $this = this;
        this.intervalId = setInterval(function() {
            $this.update();
            $this.draw();
        }, 1000 / 50);
    };

    this.makeHero = function() {
        var d;
        var done = false;
        while (!done) {
            d = new yoob.Sprite();
            d.init({
                x: Math.floor(Math.random() * this.canvas.width),
                y: Math.floor(Math.random() * this.canvas.height),
                width: 32,
                height: 32
            });
            var cols = this.getCollisions(d);
            if (cols.length === 0) {
                done = true;
            }
        }
        d.type = 'hero';
        d.draw = function(ctx) {
            ctx.fillStyle = HERO_COLOR;
            ctx.fillRect(this.getLeftX(), this.getTopY(), this.getWidth(), this.getHeight());
        };
        this.manager.addSprite(d);
        return d;
    };

    this.makePit = function() {
        var d;
        var done = false;
        while (!done) {
            d = new yoob.Sprite();
            d.init({
                x: Math.floor(Math.random() * this.canvas.width),
                y: Math.floor(Math.random() * this.canvas.height),
                width: 32,
                height: 32
            });
            var cols = this.getCollisions(d);
            if (cols.length === 0) {
                done = true;
            }
        }
        d.type = 'pit';
        d.draw = function(ctx) {
            ctx.fillStyle = PIT_COLOR;
            ctx.fillRect(this.getLeftX(), this.getTopY(), this.getWidth(), this.getHeight());
        };
        this.manager.addSprite(d);
        return d;
    };

    this.makeTreasure = function() {
        var d;
        var done = false;
        while (!done) {
            d = new yoob.Sprite();
            d.init({
                x: Math.floor(Math.random() * this.canvas.width),
                y: Math.floor(Math.random() * this.canvas.height),
                width: 16,
                height: 16
            });
            var cols = this.getCollisionsSmaller(d);
            if (cols.length === 0) {
                done = true;
            }
        }
        d.type = 'treasure';
        d.draw = function(ctx) {
            ctx.fillStyle = TREASURE_COLOR;
            ctx.fillRect(this.getLeftX(), this.getTopY(), this.getWidth(), this.getHeight());
        };
        this.manager.addSprite(d);
        this.numTreasures += 1;
        return d;
    };

    this.makeTreasures = function() {
        for (var i = 0; i < 10; i++) {
            this.makeTreasure();
        }
    };

    this.draw = function() {
        var ctx = this.canvas.getContext('2d');
        ctx.fillStyle = BACKGROUND_COLOR;
        ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
        this.manager.draw();

        ctx.textBaseline = "top";
        ctx.font = "32px Arial,Sans-serif";
        ctx.fillStyle = SCORE_COLOR;
        ctx.fillText('' + this.score, 0, 0);

        ctx.textBaseline = "bottom";
        var s = '';
        for (var i = 0; i < this.lives; i++) {
            s += 'O';
        }
        ctx.fillStyle = SCORE_COLOR;
        ctx.fillText(s, 0, this.canvas.height);

    };

    // assumes target is LARGER than all sprites
    this.getCollisions = function(target) {
        var collisions = [];
        this.manager.foreach(function(sprite) {
            if (sprite !== target && sprite.intersects(target)) {
                collisions.push(sprite);
            }
        });
        return collisions;
    };

    // assumes target is SMALLER than all sprites
    this.getCollisionsSmaller = function(target) {
        var collisions = [];
        this.manager.foreach(function(sprite) {
            if (sprite !== target && target.intersects(sprite)) {
                collisions.push(sprite);
            }
        });
        return collisions;
    };

    this.update = function() {
        if (this.paused || this.lives === 0) return;
        this.hero.dx = this.joystick.dx;
        this.hero.dy = this.joystick.dy;
        this.manager.move();

        var collisions = this.getCollisions(this.hero);
        for (var i = 0; i < collisions.length; i++) {
            var collided = collisions[i];
            if (collided.type === 'treasure') {
                this.manager.removeSprite(collided);
                this.numTreasures -= 1;
                this.score += 10;
                if (this.numTreasures === 0) {
                    this.makeTreasures();
                }
            } else if (collided.type === 'pit') {
                this.manager.removeSprite(collided);
                this.manager.removeSprite(this.hero);
                this.lives -= 1;
                if (this.lives > 0) {
                    this.hero = this.makeHero();
                }
            }
        }
    };
};