Source: chip8-screen.js

var chip8;

(function (chip8) {
    'use strict';

    /**
     * Pixel size (square pixel side size)
     * @type {number}
     * @private
     */
    var PIXEL_SIZE = 10;

    /**
     * Constructs monochromatic screen instance. Handles chip8 output.
     * This class should be treated as abstract class. It implements logic but does not perform any rendering.
     * @param {number} cols - number of colums
     * @param {number} rows - number of rows
     * @param {object} [options] - additional options
     * @class chip8.Screen
     */
    function Screen(cols, rows, options) {
        this.cols = cols;
        this.rows = rows;
        this.options = options || {};
        this.pixelmap = null;
        this.clear();
    }

    /**
     * Toggles pixel at location (x, y).
     * @param {number} x
     * @param {number} y
     * @returns {boolean} true if collision occurs, false otherwise.
     * @function togglePixel
     * @memberof chip8.Screen
     * @instance
     */
    Screen.prototype.togglePixel = function (x, y) {
        var idx = x + y * this.cols,
            collision;

        collision = !!this.pixelmap[idx];
        this.pixelmap[idx] ^= 1;

        return collision;
    };

    /**
     * Clears internal screen representation.
     * @function clear
     * @memberof chip8.Screen
     * @instance
     */
    Screen.prototype.clear = function () {
        this.pixelmap = new Uint8Array(this.rows * this.cols);
    };

    /**
     * Does repaint. Implement in subclass.
     * @function repaint
     * @memberof chip8.Screen
     * @instance
     */
    Screen.prototype.repaint = function () {
        // override
    };

    /**
     * Extends abstract screen. Extending object should implement *render* method and optionally *initialize* method
     * which works as constructor.
     * @param {object} obj - object which extends base functionality.
     * @returns {function} new constructor.
     * @function extend
     * @memberof chip8.Screen
     * @static
     */
    Screen.extend = function (obj) {
        var F = function () {
            if (obj.initialize) {
                obj.initialize.apply(this, arguments);
            } else {
                F.prototype.constructor.apply(this, arguments);
            }
        };
        F.prototype = new Screen(null, null, {});
        F.prototype.repaint = obj.repaint;
        return F;
    };
    chip8.Screen = Screen;

    chip8.CanvasScreen = Screen.extend(/** lends chip8.CanvasScreen.prototype */ {
        /**
         * Renders to canvas.
         * @param {number} cols - number of colums
         * @param {number} rows - number of rows
         * @param {object} options - additional options
         * @param {object} options.ctx - canvas context
         * @param {boolean} [options.drawGrid] - true if pixel grid should be rendered (default: true)
         * @param {string} [options.bgColor] - background color (default: #080808)
         * @param {string} [options.fgColor] - foreground color (default: #FFF)
         * @param {string} [options.gridColor] - grid color (default: #121212)
         * @constructs chip8.CanvasScreen
         * @extends chip8.Screen
         */
        initialize: function (cols, rows, options) {
            chip8.CanvasScreen.prototype.constructor.apply(this, arguments);
            this.drawGrid = ((typeof options.drawGrid !== 'undefined') ? options.drawGrid : true);
            this.bgColor = ((typeof options.bgColor !== 'undefined') ? options.bgColor : '#080808');
            this.fgColor = ((typeof options.fgColor !== 'undefined') ? options.fgColor : '#FFF');
            this.gridColor = ((typeof options.gridColor !== 'undefined') ? options.gridColor : '#121212');

            this.ctx = options.ctx;
            this.ctx.canvas.width = cols * PIXEL_SIZE;
            this.ctx.canvas.height = rows * PIXEL_SIZE;
        },

        /**
         * Repaints screen.
         * @memberof chip8.CanvasScreen#
         */
        repaint: function () {
            var x, y, i, len;

            // background
            this.ctx.fillStyle = this.bgColor;
            this.ctx.fillRect(0, 0, this.cols * PIXEL_SIZE, this.rows * PIXEL_SIZE);

            // pixels
            this.ctx.fillStyle = this.fgColor;
            for (i = 0, len = this.rows * this.cols; i < len; ++i) {
                if (this.pixelmap[i]) {
                    x = i % this.cols;
                    y = Math.floor(i / this.cols);
                    this.ctx.fillRect(x * PIXEL_SIZE, y * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE);
                }
            }

            // grid
            if (this.drawGrid) {
                this.ctx.strokeStyle = this.gridColor;
                for (i = 1; i < this.cols; ++i) {
                    this.ctx.beginPath();
                    this.ctx.moveTo(i * PIXEL_SIZE, 0);
                    this.ctx.lineTo(i * PIXEL_SIZE, this.rows * PIXEL_SIZE);
                    this.ctx.stroke();
                }

                for (i = 1; i < this.rows; ++i) {
                    this.ctx.beginPath();
                    this.ctx.moveTo(0, i * PIXEL_SIZE);
                    this.ctx.lineTo(this.cols * PIXEL_SIZE, i * PIXEL_SIZE);
                    this.ctx.stroke();
                }
            }
        }
    });

})(chip8 || (chip8 = {}));