<commit_before>/**
 * From https://en.wikipedia.org/wiki/User:Nageh/mathJax/config/TeX-AMS-texvc_HTML.js
 */

MathJax.Extension.wiki2jax = {
  version: "1.0",

  config: {
    element: null,    // The ID of the element to be processed
                      //   (defaults to full document)

    preview: "TeX"    // Set to "none" to prevent preview strings from being inserted
                      //   or to an array that specifies an HTML snippet to use for
                      //   the preview.
  },

  PreProcess: function (element) {
    if (!this.configured) {
	  this.config = MathJax.Hub.CombineConfig("wiki2jax", this.config);
      if (this.config.Augment) {MathJax.Hub.Insert(this,this.config.Augment)}

      this.previewClass = MathJax.Hub.config.preRemoveClass;
      this.setupPrefilter();
      this.configured = true;
    }
    var that = this;
    $('span.tex, img.tex', element || document).each(function(i, span) {
		that.ConvertMath(span);
	});
  },

  setupPrefilter: function() {  // used to fix a number of common wiki math hacks
    MathJax.Hub.Register.StartupHook("TeX Jax Ready", function() {
      MathJax.InputJax.TeX.prefilterHooks.Add( function(data) {
        data.math = data.math.replace(/^\s*\\scriptstyle(\W)/,"\\textstyle$1").replace(/^\s*\\scriptscriptstyle(\W)/,"\\scriptstyle$1");
        if (data.script.type.match(/(;|\s|\n)mode\s*=\s*display-nobreak(;|\s|\n|$)/) != null)
          data.math = "\\displaystyle " + data.math;
      });
    });
  },

  ConvertMath: function (node) {
    var parent = node.parentNode,
        mode = parent.tagName === "DD" && parent.childNodes.length === 1 ? "; mode=display" : "",
		tex;
	if (node.nodeName == 'IMG') {
		tex = node.alt;
	} else {
        tex = node.innerHTML.replace(/^\$/,"").replace(/\$$/,"");
	    tex = tex.replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&").replace(/ /g," ");
	}

    // We don't allow comments (%) in texvc and escape all literal % by default.
    tex = tex.replace(/([^\\])%/g, "$1\\%" );

    tex = tex.replace(/\\iiint([^!]*)!\\!\\!\\!\\!.*\\subset\\!\\supset/g,"\\iiint$1mkern-2.5em\\subset\\!\\supset").replace(/\\iint([^!]*)!\\!\\!\\!\\!\\!\\!\\!\\!\\!\\!(.*)\\subset\\!\\supset/g,"\\iint$1mkern-1.65em$2\\subset\\!\\!\\supset").replace(/\\int\\!\\!\\!(\\!)+\\int\\!\\!\\!(\\!)+\\int([^!]*)!\\!\\!\\!\\!.*\\bigcirc(\\,)*/g,"\\iiint$3mkern-2.5em\\subset\\!\\supset").replace(/\\int\\!\\!\\!(\\!)+\\int([^!]*)!\\!\\!\\!\\!\\!\\!\\!\\!(.*)\\bigcirc(\\,)*/g,"\\iint$2mkern-1.65em$3\\subset\\!\\!\\supset");

    if (mode === "" && parent.firstChild === node) mode = "; mode=display-nobreak";

    var script = document.createElement("script");
    script.type = "math/tex" + mode;
	MathJax.HTML.setScript(script, tex);

    if (node.nextSibling) {parent.insertBefore(script,node.nextSibling)}
      else {parent.appendChild(script)}
    if (this.config.preview !== "none") {this.createPreview(node)}
    parent.removeChild(node);
  },

  createPreview: function (node) {
    var preview;
    if (this.config.preview === "TeX") {preview = [this.filterPreview(node.innerHTML)]}
    else if (this.config.preview instanceof Array) {preview = this.config.preview}
    if (preview) {
      preview = MathJax.HTML.Element("span",{className: MathJax.Hub.config.preRemoveClass},preview);
      node.parentNode.insertBefore(preview,node);
    }
  },

  filterPreview: function (tex) {return tex}

};

MathJax.Hub.Register.PreProcessor(["PreProcess",MathJax.Extension.wiki2jax]);
MathJax.Ajax.loadComplete("[MathJax]/extensions/wiki2jax.js");
<commit_msg>Bug 41677: Protect against tex nodes wrapped in tex nodes.<commit_after>/**
 * From https://en.wikipedia.org/wiki/User:Nageh/mathJax/config/TeX-AMS-texvc_HTML.js
 */

MathJax.Extension.wiki2jax = {
  version: "1.0",

  config: {
    element: null,    // The ID of the element to be processed
                      //   (defaults to full document)

    preview: "TeX"    // Set to "none" to prevent preview strings from being inserted
                      //   or to an array that specifies an HTML snippet to use for
                      //   the preview.
  },

  PreProcess: function (element) {
    if (!this.configured) {
	  this.config = MathJax.Hub.CombineConfig("wiki2jax", this.config);
      if (this.config.Augment) {MathJax.Hub.Insert(this,this.config.Augment)}

      this.previewClass = MathJax.Hub.config.preRemoveClass;
      this.setupPrefilter();
      this.configured = true;
    }
    var that = this;
    $('span.tex, img.tex', element || document).each(function(i, span) {
		that.ConvertMath(span);
	});
  },

  setupPrefilter: function() {  // used to fix a number of common wiki math hacks
    MathJax.Hub.Register.StartupHook("TeX Jax Ready", function() {
      MathJax.InputJax.TeX.prefilterHooks.Add( function(data) {
        data.math = data.math.replace(/^\s*\\scriptstyle(\W)/,"\\textstyle$1").replace(/^\s*\\scriptscriptstyle(\W)/,"\\scriptstyle$1");
        if (data.script.type.match(/(;|\s|\n)mode\s*=\s*display-nobreak(;|\s|\n|$)/) != null)
          data.math = "\\displaystyle " + data.math;
      });
    });
  },

  ConvertMath: function (node) {
    var parent = node.parentNode,
        mode = parent.tagName === "DD" && parent.childNodes.length === 1 ? "; mode=display" : "",
		tex;
	if (node.nodeName == 'IMG') {
		tex = node.alt;
	} else {
        tex = $(node).text().replace(/^\$/,"").replace(/\$$/,"");
	    tex = tex.replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&").replace(/ /g," ");
	}

    // We don't allow comments (%) in texvc and escape all literal % by default.
    tex = tex.replace(/([^\\])%/g, "$1\\%" );

    tex = tex.replace(/\\iiint([^!]*)!\\!\\!\\!\\!.*\\subset\\!\\supset/g,"\\iiint$1mkern-2.5em\\subset\\!\\supset").replace(/\\iint([^!]*)!\\!\\!\\!\\!\\!\\!\\!\\!\\!\\!(.*)\\subset\\!\\supset/g,"\\iint$1mkern-1.65em$2\\subset\\!\\!\\supset").replace(/\\int\\!\\!\\!(\\!)+\\int\\!\\!\\!(\\!)+\\int([^!]*)!\\!\\!\\!\\!.*\\bigcirc(\\,)*/g,"\\iiint$3mkern-2.5em\\subset\\!\\supset").replace(/\\int\\!\\!\\!(\\!)+\\int([^!]*)!\\!\\!\\!\\!\\!\\!\\!\\!(.*)\\bigcirc(\\,)*/g,"\\iint$2mkern-1.65em$3\\subset\\!\\!\\supset");

    if (mode === "" && parent.firstChild === node) mode = "; mode=display-nobreak";

    var script = document.createElement("script");
    script.type = "math/tex" + mode;
	MathJax.HTML.setScript(script, tex);

    if (node.nextSibling) {parent.insertBefore(script,node.nextSibling)}
      else {parent.appendChild(script)}
    if (this.config.preview !== "none") {this.createPreview(node)}
    parent.removeChild(node);
  },

  createPreview: function (node) {
    var preview;
    if (this.config.preview === "TeX") {preview = [this.filterPreview($(node).text())]}
    else if (this.config.preview instanceof Array) {preview = this.config.preview}
    if (preview) {
      preview = MathJax.HTML.Element("span",{className: MathJax.Hub.config.preRemoveClass},preview);
      node.parentNode.insertBefore(preview,node);
    }
  },

  filterPreview: function (tex) {return tex}

};

MathJax.Hub.Register.PreProcessor(["PreProcess",MathJax.Extension.wiki2jax]);
MathJax.Ajax.loadComplete("[MathJax]/extensions/wiki2jax.js");
<|endoftext|>


<commit_before>const DrawCard = require('../../drawcard.js');
const AbilityDsl = require('../../abilitydsl');
const { CardTypes } = require('../../Constants');

class SeventhTower extends DrawCard {
    setupCardAbilities() {
        this.reaction({
            title: 'Resolve the ring effect',
            when: {
                afterConflict: (event, context) => {
                    let cards = context.player.getDynastyCardsInProvince(event.conflict.conflictProvince.location);
                    return cards.some((card) => !card.facedown && card.type === CardTypes.Holding && card.hasTrait('kaiu-wall'))
                            && context.player.isDefendingPlayer() && event.conflict.winner === context.player;
                }
            },
            gameAction: AbilityDsl.actions.resolveConflictRing()
        });
    }
}

SeventhTower.id = 'seventh-tower';

module.exports = SeventhTower;
<commit_msg>Added null checks to Seventh Tower (#3829)<commit_after>const DrawCard = require('../../drawcard.js');
const AbilityDsl = require('../../abilitydsl');
const { CardTypes } = require('../../Constants');

class SeventhTower extends DrawCard {
    setupCardAbilities() {
        this.reaction({
            title: 'Resolve the ring effect',
            when: {
                afterConflict: (event, context) => {
                    if(event && event.conflict && event.conflict.conflictProvince && event.conflict.conflictProvince.location) {
                        let cards = context.player.getDynastyCardsInProvince(event.conflict.conflictProvince.location);
                        return cards.some((card) => !card.facedown && card.type === CardTypes.Holding && card.hasTrait('kaiu-wall'))
                                && context.player.isDefendingPlayer() && event.conflict.winner === context.player;
                    }
                    return false;
                }
            },
            gameAction: AbilityDsl.actions.resolveConflictRing()
        });
    }
}

SeventhTower.id = 'seventh-tower';

module.exports = SeventhTower;
<|endoftext|>


<commit_before>(function() {

    var columnDefs = [
        {headerName: 'Product', field: 'product', chartType: 'category'},
        {headerName: 'Book', field: 'book', chartType: 'category'},

        {headerName: 'Current', field: 'current', type: 'measure'},
        {headerName: 'Previous', field: 'previous', type: 'measure'},
        {headerName: 'PL 1', field: 'pl1', type: 'measure'},
        {headerName: 'PL 2', field: 'pl2', type: 'measure'},
        {headerName: 'Gain-DX', field: 'gainDx', type: 'measure'},
        {headerName: 'SX / PX', field: 'sxPx', type: 'measure'},

        {headerName: 'Trade', field: 'trade', type: 'measure'},
        {headerName: 'Submitter ID', field: 'submitterID', type: 'measure'},
        {headerName: 'Submitted Deal ID', field: 'submitterDealID', type: 'measure', width: 150},

        {headerName: 'Portfolio', field: 'portfolio'},
        {headerName: 'Deal Type', field: 'dealType'},
        {headerName: 'Bid', field: 'bidFlag'}
    ];

    var chartRef;

    var gridOptions = {
        columnDefs: columnDefs,
        defaultColDef: {
            width: 120,
            sortable: true,
            resizable: true
        },
        columnTypes: {
            measure: {
                chartType: 'series',
                cellClass: 'number',
                valueFormatter: numberCellFormatter,
                cellRenderer:'agAnimateShowChangeCellRenderer'
            }
        },
        animateRows: true,
        enableCharts: true,
        suppressAggFuncInHeader: true,
        getRowNodeId: function(data) { return data.trade; },
        onFirstDataRendered: function(params) {
            var chartRangeParams = {
                cellRange: {
                    columns: ['product', 'current', 'previous', 'pl1', 'pl2', 'gainDx', 'sxPx']
                },
                chartType: 'groupedBar',
                chartContainer: document.querySelector('#integrated-charting-chart'),
                suppressChartRanges: true,
                aggregate: true
            };

            chartRef = params.api.chartRange(chartRangeParams);
        },
        processChartOptions: function(params) {
            var opts = params.options;

            opts.legendPosition = 'bottom';
            opts.yAxis.labelFormatter = yAxisLabelFormatter;
            opts.seriesDefaults.tooltipEnabled = true;
            opts.seriesDefaults.fills =['#c16068', '#a2bf8a', '#ebcc87', '#80a0c3', '#b58dae', '#85c0d1'];
            opts.seriesDefaults.strokes =['#874349', '#718661', '#a48f5f', '#5a7088', '#7f637a', '#5d8692'];

            opts.seriesDefaults.tooltipRenderer = (params) => {
                var strArr = params.yField.replace(/([A-Z])/g, " $1");
                var seriesName = strArr.charAt(0).toUpperCase() + strArr.slice(1);
                var value = params.datum[params.yField].toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
                return `<b>${seriesName}</b>: ${value}`;
            };

            return opts;
        },
        getChartToolbarItems: function() {
            return []; // hide toolbar items
        }
    };

    function createChart(type) {

        // destroy existing chart
        if (chartRef) {
            chartRef.destroyChart();
        }

        var params = {
            cellRange: {
                columns: ['product', 'current', 'previous', 'pl1', 'pl2', 'gainDx', 'sxPx']
            },
            chartContainer: document.querySelector('#myChart'),
            chartType: type,
            suppressChartRanges: true,
            aggregate: true
        };

        chartRef = gridOptions.api.chartRange(params);
    }

    function numberCellFormatter(params) {
        return Math.floor(params.value).toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
    }

    function yAxisLabelFormatter(n) {
        if (n < 1e3) return n;
        if (n >= 1e3 && n < 1e6) return +(n / 1e3).toFixed(1) + "K";
        if (n >= 1e6 && n < 1e9) return +(n / 1e6).toFixed(1) + "M";
        if (n >= 1e9 && n < 1e12) return +(n / 1e9).toFixed(1) + "B";
        if (n >= 1e12) return +(n / 1e12).toFixed(1) + "T";
    }

    function initLiveStreamUpdates() {
        var eGridDiv = document.querySelector("#integrated-charting-grid");
        new agGrid.Grid(eGridDiv, gridOptions);
        startWorker();
    }

    function startWorker() {
        worker = new Worker("/integrated-charting/worker.js");
        worker.onmessage = function(e) {
            if (e.data.type === 'setRowData') {
                gridOptions.api.setRowData(e.data.records);
            }
            if (e.data.type === 'updateData') {
                gridOptions.api.batchUpdateRowData({update: e.data.records});
            }
        };
        worker.postMessage('start');
    }

    if (document.readyState == "complete") {
        initLiveStreamUpdates();
    } else {
        document.addEventListener("readystatechange", initLiveStreamUpdates);
    }
})();
<commit_msg>AG-3002 - updated integrated charting example on main page.<commit_after>(function() {

    var columnDefs = [
        {headerName: 'Product', field: 'product', chartType: 'category'},
        {headerName: 'Book', field: 'book', chartType: 'category'},

        {headerName: 'Current', field: 'current', type: 'measure'},
        {headerName: 'Previous', field: 'previous', type: 'measure'},
        {headerName: 'PL 1', field: 'pl1', type: 'measure'},
        {headerName: 'PL 2', field: 'pl2', type: 'measure'},
        {headerName: 'Gain-DX', field: 'gainDx', type: 'measure'},
        {headerName: 'SX / PX', field: 'sxPx', type: 'measure'},

        {headerName: 'Trade', field: 'trade', type: 'measure'},
        {headerName: 'Submitter ID', field: 'submitterID', type: 'measure'},
        {headerName: 'Submitted Deal ID', field: 'submitterDealID', type: 'measure', width: 150},

        {headerName: 'Portfolio', field: 'portfolio'},
        {headerName: 'Deal Type', field: 'dealType'},
        {headerName: 'Bid', field: 'bidFlag'}
    ];

    var chartRef;

    var gridOptions = {
        columnDefs: columnDefs,
        defaultColDef: {
            width: 120,
            sortable: true,
            resizable: true
        },
        columnTypes: {
            measure: {
                chartType: 'series',
                cellClass: 'number',
                valueFormatter: numberCellFormatter,
                cellRenderer:'agAnimateShowChangeCellRenderer'
            }
        },
        animateRows: true,
        enableCharts: true,
        suppressAggFuncInHeader: true,
        getRowNodeId: function(data) { return data.trade; },
        onFirstDataRendered: function(params) {
            var chartRangeParams = {
                cellRange: {
                    columns: ['product', 'current', 'previous', 'pl1', 'pl2', 'gainDx', 'sxPx']
                },
                chartType: 'groupedBar',
                chartContainer: document.querySelector('#integrated-charting-chart'),
                suppressChartRanges: true,
                aggregate: true
            };

            chartRef = params.api.chartRange(chartRangeParams);
        },
        processChartOptions: function(params) {
            var opts = params.options;

            opts.legendPosition = 'bottom';
            opts.yAxis.labelFormatter = yAxisLabelFormatter;
            opts.xAxis.labelRotation = 0;

            opts.seriesDefaults.fills =['#c16068', '#a2bf8a', '#ebcc87', '#80a0c3', '#b58dae', '#85c0d1'];
            opts.seriesDefaults.strokes =['#874349', '#718661', '#a48f5f', '#5a7088', '#7f637a', '#5d8692'];

            opts.seriesDefaults.tooltipEnabled = false;
            // opts.seriesDefaults.tooltipRenderer = (params) => {
            //
            //     var strArr = params.yField.replace(/([A-Z])/g, " $1");
            //     var seriesName = strArr.charAt(0).toUpperCase() + strArr.slice(1);
            //     var value = params.datum[params.yField].toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
            //     return `<b>${seriesName}</b>: ${value}`;
            // };

            return opts;
        },
        getChartToolbarItems: function() {
            return []; // hide toolbar items
        }
    };

    function numberCellFormatter(params) {
        return Math.floor(params.value).toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
    }

    function yAxisLabelFormatter(n) {
        if (n < 1e3) return n;
        if (n >= 1e3 && n < 1e6) return +(n / 1e3).toFixed(1) + "K";
        if (n >= 1e6 && n < 1e9) return +(n / 1e6).toFixed(1) + "M";
        if (n >= 1e9 && n < 1e12) return +(n / 1e9).toFixed(1) + "B";
        if (n >= 1e12) return +(n / 1e12).toFixed(1) + "T";
    }

    function initLiveStreamUpdates() {
        var eGridDiv = document.querySelector("#integrated-charting-grid");
        new agGrid.Grid(eGridDiv, gridOptions);
        startWorker();
    }

    function startWorker() {
        worker = new Worker("/integrated-charting/worker.js");
        worker.onmessage = function(e) {
            if (e.data.type === 'setRowData') {
                gridOptions.api.setRowData(e.data.records);
            }
            if (e.data.type === 'updateData') {
                gridOptions.api.batchUpdateRowData({update: e.data.records});
            }
        };
        worker.postMessage('start');
    }

    if (document.readyState == "complete") {
        initLiveStreamUpdates();
    } else {
        document.addEventListener("readystatechange", initLiveStreamUpdates);
    }
})();
<|endoftext|>


<commit_before>describe('Blueprint', function() {

    it('Should be able to initialise the module;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg);

        expect(blueprint.element.node()).toEqual(svg);
        expect(Array.isArray(blueprint.shapes)).toBeTruthy();
        expect(typeof blueprint.options).toBe('object');
        expect(typeof blueprint.dispatcher.send).toBe('function');
        expect(blueprint.groups.shapes.node().nodeName).toEqual('G');
        expect(blueprint.groups.handles.node().nodeName).toEqual('G');
        expect(blueprint.map.rect).toBeDefined();

        blueprint = new Blueprint(svg, { dataAttribute: 'data-blueprint-id' });
        blueprint.add('rect');
        var group = svg.querySelector('rect').parentNode;
        expect(group.getAttribute('data-blueprint-id')).toEqual('BP1');

    });

    it('Should be able to add an element;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect').x(100).y(100).setAttr({ strokeWidth: 10 }),
            element   = d3.select(svg.querySelector('rect'));

        expect(rectangle.toString()).toEqual('[object Interface: BP1]');
        expect(rectangle.label).toEqual('BP1');
        expect(typeof rectangle.width).toBe('function');
        expect(element.attr('x')).toBeNull();
        expect(element.attr('y')).toBeNull();
        expect(element.attr('z')).toBeNull();
        expect(element.attr('transform')).not.toBeNull();
        expect(element.attr('stroke-width')).toEqual((10).toString());
        expect(element.datum()['stroke-width']).toEqual(10);
        expect(element.datum().transform).toEqual('translate(100, 100)');

    });

    it('Should be able to return the selected shapes;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            first     = blueprint.add('rect').select(),
            second    = blueprint.add('rect'),
            third     = blueprint.add('rect').select();

        expect(blueprint.selected().length).toEqual(2);
        expect(blueprint.selected()[0]).toEqual(first);
        expect(blueprint.selected()[1]).toEqual(third);

        second.select();
        expect(blueprint.selected().length).toEqual(3);
        first.deselect();
        expect(blueprint.selected().length).toEqual(2);

    });

    it('Should be able to add an element whilst passing in a DOM reference;', function() {

        var svgContainer = document.createElement('svg');
        svgContainer.setAttribute('id', 'svg-container');
        document.body.appendChild(svgContainer);

        var blueprint = new Blueprint('#svg-container'),
            rectangle = blueprint.add(document.createElement('rect')).x(300).y(550);

        expect(svgContainer.querySelectorAll('g[data-id="BP1"]').length).toEqual(1);
        expect(rectangle.x()).toEqual(300);
        expect(rectangle.y()).toEqual(550);

    });

    it('Should be able to add a specialised interface method;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect');

        expect(rectangle.fill('red').fill()).toEqual('red');

    });

    it('Should be able to read attributes from the shape;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect').x(250).y(250).setAttr({ strokeWidth: 10 });

        expect(rectangle.getAttr().x).toEqual(250);
        expect(rectangle.getAttr().y).toEqual(250);
        expect(rectangle.getAttr().strokeWidth).toEqual(10);

    });

    it('Should be able to select and deselect shapes', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect');

        expect(rectangle.isSelected()).toBeFalsy();
        expect(rectangle.select().isSelected()).toBeTruthy();
        expect(rectangle.deselect().isSelected()).toBeFalsy();

    });

    it('Should be able to manage the z-indexes;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            first     = blueprint.add('rect'),
            second    = blueprint.add('rect'),
            third     = blueprint.add('rect');

        expect(second.z(3).z()).toEqual(3);
        expect(first.z()).toEqual(1);
        expect(third.z()).toEqual(2);

        expect(second.z(1).z()).toEqual(1);
        expect(first.z()).toEqual(2);
        expect(third.z()).toEqual(3);

        expect(first.z(9001).z()).toEqual(3);
        expect(second.z(Infinity).z()).toEqual(3);
        expect(third.z(-Infinity).z()).toEqual(1);

        expect(first.bringToFront().z()).toEqual(3);
        expect(first.sendBackwards().z()).toEqual(2);

        expect(second.sendToBack().z()).toEqual(1);
        expect(third.z()).toEqual(2);
        expect(first.z()).toEqual(3);

    });

    it('Should be able to set the zIndex of the shapes;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect'),
            group     = d3.select(svg.querySelector('g[data-id]')),
            element   = d3.select(svg.querySelector('rect'));

        // Add some more rectangles so that the `sort` method is invoked.
        blueprint.add('rect');
        blueprint.add('rect');
        blueprint.add('rect');

        var secondGroup = d3.select(svg.querySelectorAll('g[data-id]')[1]);
        expect(secondGroup.datum().z).toEqual(2);
        var thirdGroup = d3.select(svg.querySelectorAll('g[data-id]')[2]);
        expect(thirdGroup.datum().z).toEqual(3);
        var fourthGroup = d3.select(svg.querySelectorAll('g[data-id]')[3]);
        expect(fourthGroup.datum().z).toEqual(4);

        expect(group.attr('data-id')).toEqual(rectangle.label);
        expect(element.attr('z')).toBeNull();
        expect(element.datum().z).toBeUndefined();
        expect(group.datum().z).toEqual(1);

        rectangle.z(3);
        expect(element.attr('z')).toBeNull();
        expect(element.datum().z).toBeUndefined();
        expect(group.datum().z).toEqual(3);

        var fifth      = blueprint.add('rect'),
            fifthGroup = d3.select(svg.querySelectorAll('g[data-id]')[4]);
        expect(fifthGroup.datum().z).toEqual(5);
        fifth.z(-201);

    });

    it('Should be able to remove shapes from the canvas;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            first     = blueprint.add('rect'),
            second    = blueprint.add('rect'),
            third     = blueprint.add('rect');

        var mapLabels = function() {
            return blueprint.all().map(function(d) {
                return d.label;
            });
        };

        expect(blueprint.all().length).toEqual(3);

        first.remove();
        expect(blueprint.all().length).toEqual(2);
        expect(mapLabels()).toEqual(['BP2', 'BP3']);

        third.remove();
        expect(blueprint.all().length).toEqual(1);
        expect(mapLabels()).toEqual(['BP2']);

        second.remove();
        expect(blueprint.all().length).toEqual(0);
        expect(mapLabels()).toEqual([]);

    });

    it('Should be able to use the `attr` accessor to set/get', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect');

        expect(rectangle.x(125).x()).toEqual(125);
        expect(rectangle.z(1).z()).toEqual(1);
        expect(rectangle.y(275).y()).toEqual(275);
        expect(rectangle.width(300).width()).toEqual(300);
        expect(rectangle.height(250).height()).toEqual(250);

    });

    it('Should be able to register a custom shape;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg);

        expect(function() {
            blueprint.register('new-element', {});
        }).toThrow(new Error('Blueprint.js: Custom shape must be an instance of `Shape`. See: https://github.com/Wildhoney/Blueprint/blob/master/EXCEPTIONS.md#instance-of-shape'));

        var Rect = function Rectangle() {};
        Rect.__proto__ = blueprint.getShapePrototype();
        Rect.prototype = blueprint.getShapePrototype().prototype;

        expect(function() {
            blueprint.register('rect', Rect);
        }).toThrow(new Error('Blueprint.js: Refusing to overwrite existing rect shape without explicit overwrite. See: https://github.com/Wildhoney/Blueprint/blob/master/EXCEPTIONS.md#overwriting-existing-shapes'));

        blueprint.register('rect', Rect, true);
        expect(blueprint.map.rect).toEqual(Rect);
        Rect.prototype.getTag = function getTag() {
            return 'custom-rect';
        };

        blueprint.add('rect');
        expect(blueprint.shapes[0].shape.getTag()).toEqual('custom-rect');

    });

    describe('Selectable', function() {

        it('Should be able to select and deselect the element;', function() {

            var svg       = document.createElement('svg'),
                blueprint = new Blueprint(svg);

            blueprint.add('rect');
            blueprint.add('rect');

            var firstShape       = blueprint.shapes[0].shape,
                secondShape      = blueprint.shapes[1].shape,
                firstSelectable  = firstShape.features.selectable,
                secondSelectable = secondShape.features.selectable;

            spyOn(firstShape.dispatcher, 'send').and.callThrough();
            spyOn(firstSelectable, 'select').and.callThrough();
            spyOn(firstSelectable, 'deselect').and.callThrough();
            expect(firstSelectable.selected).toBeFalsy();
            expect(secondSelectable.selected).toBeFalsy();
            expect(firstShape.group.classed('selected')).toBeFalsy();
            expect(secondShape.group.classed('selected')).toBeFalsy();

            firstShape.element.node().dispatchEvent(new MouseEvent('mousedown', {
                bubbles: true, cancelable: true
            }));

            expect(firstShape.dispatcher.send).toHaveBeenCalled();
            expect(firstSelectable.select).toHaveBeenCalled();
            expect(firstSelectable.deselect).toHaveBeenCalled();
            expect(firstSelectable.selected).toBeTruthy();
            expect(secondSelectable.selected).toBeFalsy();
            expect(firstShape.group.classed('selected')).toBeTruthy();
            expect(secondShape.group.classed('selected')).toBeFalsy();

            svg.dispatchEvent(new MouseEvent('click', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeFalsy();
            expect(secondSelectable.selected).toBeFalsy();

            // And what happens when we select two of the elements without multi-select?
            firstShape.element.node().dispatchEvent(new MouseEvent('mousedown', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeTruthy();
            expect(secondSelectable.selected).toBeFalsy();

            secondShape.element.node().dispatchEvent(new MouseEvent('mousedown', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeFalsy();
            expect(secondSelectable.selected).toBeTruthy();

            // We'll then click on the first element with multi-select enabled (mod key).
            Mousetrap.trigger('mod', 'keydown');

            firstShape.element.node().dispatchEvent(new MouseEvent('mousedown', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeTruthy();
            expect(secondSelectable.selected).toBeTruthy();

            // User disables multi-select.
            Mousetrap.trigger('mod', 'keyup');

            secondShape.element.node().dispatchEvent(new MouseEvent('mousedown', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeFalsy();
            expect(secondSelectable.selected).toBeTruthy();
            expect(firstShape.group.classed('selected')).toBeFalsy();
            expect(secondShape.group.classed('selected')).toBeTruthy();

            // With mod+a we should be able to select all of the elements.

            Mousetrap.trigger('mod+a');

            expect(firstSelectable.selected).toBeTruthy();
            expect(secondSelectable.selected).toBeTruthy();
            expect(firstShape.group.classed('selected')).toBeTruthy();
            expect(secondShape.group.classed('selected')).toBeTruthy();

            svg.dispatchEvent(new MouseEvent('click', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeFalsy();
            expect(secondSelectable.selected).toBeFalsy();

            Mousetrap.trigger('mod+a');

            expect(firstShape.group.classed('selected')).toBeTruthy();
            expect(secondShape.group.classed('selected')).toBeTruthy();

        });

    });

    describe('Movable', function() {

        it('Should be able to move an element;', function() {

            var svg       = document.createElement('svg'),
                blueprint = new Blueprint(svg),
                rectangle = blueprint.add('rect');

            expect(rectangle.x(250).x()).toEqual(250);
            expect(rectangle.y(250).y()).toEqual(250);

            var shape = blueprint.shapes[0].shape,
                movable = shape.features.movable;

            movable.dragStart(95, 191);
            expect(movable.start).toEqual({ x: 95, y: 191 });
            expect(shape.group.classed('dragging')).toBeTruthy();

            movable.drag(191, 301, 10);
            expect(rectangle.x()).toEqual(100);
            expect(rectangle.y()).toEqual(110);
            expect(shape.group.classed('dragging')).toBeTruthy();

            movable.dragEnd();
            expect(shape.group.classed('dragging')).toBeFalsy();

        });

        it('Should be able to move an element by pressing the arrow keys;', function() {

            var svg       = document.createElement('svg'),
                blueprint = new Blueprint(svg),
                first     = blueprint.add('rect'),
                second    = blueprint.add('rect').x(100).y(100);

            expect(first.x(250).x()).toEqual(250);
            expect(first.y(250).y()).toEqual(250);

            Mousetrap.trigger('left');

            // Shouldn't move because it's not been selected.
            expect(first.isSelected()).toBeFalsy();
            expect(first.x(250).x()).toEqual(250);
            expect(first.y(250).y()).toEqual(250);

            // Element is now selected and should therefore listen to the arrow key events.
            first.select();
            Mousetrap.trigger('left');
            expect(first.isSelected()).toBeTruthy();
            expect(first.x()).toEqual(249);
            expect(second.x()).toEqual(100);
            expect(first.y()).toEqual(250);

            Mousetrap.trigger('right');
            expect(first.x()).toEqual(250);
            expect(second.x()).toEqual(100);

            Mousetrap.trigger('up');
            expect(first.x()).toEqual(250);
            expect(first.y()).toEqual(249);
            expect(second.x()).toEqual(100);
            expect(second.y()).toEqual(100);

            Mousetrap.trigger('down');
            expect(first.y()).toEqual(250);
            expect(second.y()).toEqual(100);

        });

        it('Should be able to move in large steps with the shift+arrow keys combination;', function() {

            var svg       = document.createElement('svg'),
                blueprint = new Blueprint(svg),
                rectangle = blueprint.add('rect').select().transform(100, 100);

            Mousetrap.trigger('shift+left');
            expect(rectangle.x()).toEqual(90);
            Mousetrap.trigger('shift+left');
            expect(rectangle.x()).toEqual(80);

            Mousetrap.trigger('shift+up');
            expect(rectangle.x()).toEqual(80);
            expect(rectangle.y()).toEqual(90);
            Mousetrap.trigger('shift+down');
            expect(rectangle.y()).toEqual(100);

        });

    });

});<commit_msg>Added tests for 'selected' hook<commit_after>describe('Blueprint', function() {

    it('Should be able to initialise the module;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg);

        expect(blueprint.element.node()).toEqual(svg);
        expect(Array.isArray(blueprint.shapes)).toBeTruthy();
        expect(typeof blueprint.options).toBe('object');
        expect(typeof blueprint.dispatcher.send).toBe('function');
        expect(blueprint.groups.shapes.node().nodeName).toEqual('G');
        expect(blueprint.groups.handles.node().nodeName).toEqual('G');
        expect(blueprint.map.rect).toBeDefined();

        blueprint = new Blueprint(svg, { dataAttribute: 'data-blueprint-id' });
        blueprint.add('rect');
        var group = svg.querySelector('rect').parentNode;
        expect(group.getAttribute('data-blueprint-id')).toEqual('BP1');

    });

    it('Should be able to add an element;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect').x(100).y(100).setAttr({ strokeWidth: 10 }),
            element   = d3.select(svg.querySelector('rect'));

        expect(rectangle.toString()).toEqual('[object Interface: BP1]');
        expect(rectangle.label).toEqual('BP1');
        expect(typeof rectangle.width).toBe('function');
        expect(element.attr('x')).toBeNull();
        expect(element.attr('y')).toBeNull();
        expect(element.attr('z')).toBeNull();
        expect(element.attr('transform')).not.toBeNull();
        expect(element.attr('stroke-width')).toEqual((10).toString());
        expect(element.datum()['stroke-width']).toEqual(10);
        expect(element.datum().transform).toEqual('translate(100, 100)');

    });

    it('Should be able to return the selected shapes;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            first     = blueprint.add('rect').select(),
            second    = blueprint.add('rect'),
            third     = blueprint.add('rect').select();

        expect(blueprint.selected().length).toEqual(2);
        expect(blueprint.selected()[0]).toEqual(first);
        expect(blueprint.selected()[1]).toEqual(third);

        second.select();
        expect(blueprint.selected().length).toEqual(3);
        first.deselect();
        expect(blueprint.selected().length).toEqual(2);

    });

    it('Should be able to add an element whilst passing in a DOM reference;', function() {

        var svgContainer = document.createElement('svg');
        svgContainer.setAttribute('id', 'svg-container');
        document.body.appendChild(svgContainer);

        var blueprint = new Blueprint('#svg-container'),
            rectangle = blueprint.add(document.createElement('rect')).x(300).y(550);

        expect(svgContainer.querySelectorAll('g[data-id="BP1"]').length).toEqual(1);
        expect(rectangle.x()).toEqual(300);
        expect(rectangle.y()).toEqual(550);

    });

    it('Should be able to add a specialised interface method;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect');

        expect(rectangle.fill('red').fill()).toEqual('red');

    });

    it('Should be able to read attributes from the shape;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect').x(250).y(250).setAttr({ strokeWidth: 10 });

        expect(rectangle.getAttr().x).toEqual(250);
        expect(rectangle.getAttr().y).toEqual(250);
        expect(rectangle.getAttr().strokeWidth).toEqual(10);

    });

    it('Should be able to select and deselect shapes', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect');

        expect(rectangle.isSelected()).toBeFalsy();
        expect(rectangle.select().isSelected()).toBeTruthy();
        expect(rectangle.deselect().isSelected()).toBeFalsy();

    });

    it('Should be able to manage the z-indexes;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            first     = blueprint.add('rect'),
            second    = blueprint.add('rect'),
            third     = blueprint.add('rect');

        expect(second.z(3).z()).toEqual(3);
        expect(first.z()).toEqual(1);
        expect(third.z()).toEqual(2);

        expect(second.z(1).z()).toEqual(1);
        expect(first.z()).toEqual(2);
        expect(third.z()).toEqual(3);

        expect(first.z(9001).z()).toEqual(3);
        expect(second.z(Infinity).z()).toEqual(3);
        expect(third.z(-Infinity).z()).toEqual(1);

        expect(first.bringToFront().z()).toEqual(3);
        expect(first.sendBackwards().z()).toEqual(2);

        expect(second.sendToBack().z()).toEqual(1);
        expect(third.z()).toEqual(2);
        expect(first.z()).toEqual(3);

    });

    it('Should be able to set the zIndex of the shapes;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect'),
            group     = d3.select(svg.querySelector('g[data-id]')),
            element   = d3.select(svg.querySelector('rect'));

        // Add some more rectangles so that the `sort` method is invoked.
        blueprint.add('rect');
        blueprint.add('rect');
        blueprint.add('rect');

        var secondGroup = d3.select(svg.querySelectorAll('g[data-id]')[1]);
        expect(secondGroup.datum().z).toEqual(2);
        var thirdGroup = d3.select(svg.querySelectorAll('g[data-id]')[2]);
        expect(thirdGroup.datum().z).toEqual(3);
        var fourthGroup = d3.select(svg.querySelectorAll('g[data-id]')[3]);
        expect(fourthGroup.datum().z).toEqual(4);

        expect(group.attr('data-id')).toEqual(rectangle.label);
        expect(element.attr('z')).toBeNull();
        expect(element.datum().z).toBeUndefined();
        expect(group.datum().z).toEqual(1);

        rectangle.z(3);
        expect(element.attr('z')).toBeNull();
        expect(element.datum().z).toBeUndefined();
        expect(group.datum().z).toEqual(3);

        var fifth      = blueprint.add('rect'),
            fifthGroup = d3.select(svg.querySelectorAll('g[data-id]')[4]);
        expect(fifthGroup.datum().z).toEqual(5);
        fifth.z(-201);

    });

    it('Should be able to remove shapes from the canvas;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            first     = blueprint.add('rect'),
            second    = blueprint.add('rect'),
            third     = blueprint.add('rect');

        var mapLabels = function() {
            return blueprint.all().map(function(d) {
                return d.label;
            });
        };

        expect(blueprint.all().length).toEqual(3);

        first.remove();
        expect(blueprint.all().length).toEqual(2);
        expect(mapLabels()).toEqual(['BP2', 'BP3']);

        third.remove();
        expect(blueprint.all().length).toEqual(1);
        expect(mapLabels()).toEqual(['BP2']);

        second.remove();
        expect(blueprint.all().length).toEqual(0);
        expect(mapLabels()).toEqual([]);

    });

    it('Should be able to use the `attr` accessor to set/get', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg),
            rectangle = blueprint.add('rect');

        expect(rectangle.x(125).x()).toEqual(125);
        expect(rectangle.z(1).z()).toEqual(1);
        expect(rectangle.y(275).y()).toEqual(275);
        expect(rectangle.width(300).width()).toEqual(300);
        expect(rectangle.height(250).height()).toEqual(250);

    });

    it('Should be able to register a custom shape;', function() {

        var svg       = document.createElement('svg'),
            blueprint = new Blueprint(svg);

        expect(function() {
            blueprint.register('new-element', {});
        }).toThrow(new Error('Blueprint.js: Custom shape must be an instance of `Shape`. See: https://github.com/Wildhoney/Blueprint/blob/master/EXCEPTIONS.md#instance-of-shape'));

        var Rect = function Rectangle() {};
        Rect.__proto__ = blueprint.getShapePrototype();
        Rect.prototype = blueprint.getShapePrototype().prototype;

        expect(function() {
            blueprint.register('rect', Rect);
        }).toThrow(new Error('Blueprint.js: Refusing to overwrite existing rect shape without explicit overwrite. See: https://github.com/Wildhoney/Blueprint/blob/master/EXCEPTIONS.md#overwriting-existing-shapes'));

        blueprint.register('rect', Rect, true);
        expect(blueprint.map.rect).toEqual(Rect);
        Rect.prototype.getTag = function getTag() {
            return 'custom-rect';
        };

        blueprint.add('rect');
        expect(blueprint.shapes[0].shape.getTag()).toEqual('custom-rect');

    });

    describe('Selectable', function() {

        it('Should be able to select and deselect the element;', function() {

            var svg       = document.createElement('svg'),
                blueprint = new Blueprint(svg);

            blueprint.add('rect');
            blueprint.add('rect');

            var firstShape       = blueprint.shapes[0].shape,
                secondShape      = blueprint.shapes[1].shape,
                firstSelectable  = firstShape.features.selectable,
                secondSelectable = secondShape.features.selectable;

            spyOn(firstShape.dispatcher, 'send').and.callThrough();
            spyOn(firstSelectable, 'select').and.callThrough();
            spyOn(firstSelectable, 'deselect').and.callThrough();
            expect(firstSelectable.selected).toBeFalsy();
            expect(secondSelectable.selected).toBeFalsy();
            expect(firstShape.group.classed('selected')).toBeFalsy();
            expect(secondShape.group.classed('selected')).toBeFalsy();

            firstShape.element.node().dispatchEvent(new MouseEvent('mousedown', {
                bubbles: true, cancelable: true
            }));

            expect(firstShape.dispatcher.send).toHaveBeenCalled();
            expect(firstSelectable.select).toHaveBeenCalled();
            expect(firstSelectable.deselect).toHaveBeenCalled();
            expect(firstSelectable.selected).toBeTruthy();
            expect(secondSelectable.selected).toBeFalsy();
            expect(firstShape.group.classed('selected')).toBeTruthy();
            expect(secondShape.group.classed('selected')).toBeFalsy();

            svg.dispatchEvent(new MouseEvent('click', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeFalsy();
            expect(secondSelectable.selected).toBeFalsy();

            // And what happens when we select two of the elements without multi-select?
            firstShape.element.node().dispatchEvent(new MouseEvent('mousedown', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeTruthy();
            expect(secondSelectable.selected).toBeFalsy();

            secondShape.element.node().dispatchEvent(new MouseEvent('mousedown', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeFalsy();
            expect(secondSelectable.selected).toBeTruthy();

            // We'll then click on the first element with multi-select enabled (mod key).
            Mousetrap.trigger('mod', 'keydown');

            firstShape.element.node().dispatchEvent(new MouseEvent('mousedown', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeTruthy();
            expect(secondSelectable.selected).toBeTruthy();

            // User disables multi-select.
            Mousetrap.trigger('mod', 'keyup');

            secondShape.element.node().dispatchEvent(new MouseEvent('mousedown', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeFalsy();
            expect(secondSelectable.selected).toBeTruthy();
            expect(firstShape.group.classed('selected')).toBeFalsy();
            expect(secondShape.group.classed('selected')).toBeTruthy();

            // With mod+a we should be able to select all of the elements.

            Mousetrap.trigger('mod+a');

            expect(firstSelectable.selected).toBeTruthy();
            expect(secondSelectable.selected).toBeTruthy();
            expect(firstShape.group.classed('selected')).toBeTruthy();
            expect(secondShape.group.classed('selected')).toBeTruthy();

            svg.dispatchEvent(new MouseEvent('click', {
                bubbles: true, cancelable: true
            }));

            expect(firstSelectable.selected).toBeFalsy();
            expect(secondSelectable.selected).toBeFalsy();

            Mousetrap.trigger('mod+a');

            expect(firstShape.group.classed('selected')).toBeTruthy();
            expect(secondShape.group.classed('selected')).toBeTruthy();

        });

    });

    describe('Movable', function() {

        it('Should be able to move an element;', function() {

            var svg       = document.createElement('svg'),
                blueprint = new Blueprint(svg),
                rectangle = blueprint.add('rect');

            expect(rectangle.x(250).x()).toEqual(250);
            expect(rectangle.y(250).y()).toEqual(250);

            var shape = blueprint.shapes[0].shape,
                movable = shape.features.movable;
            spyOn(movable, 'selected');

            movable.dragStart(95, 191);
            expect(movable.selected).toHaveBeenCalled();
            expect(movable.selected.calls.count()).toEqual(1);
            expect(movable.start).toEqual({ x: 95, y: 191 });
            expect(shape.group.classed('dragging')).toBeTruthy();

            movable.drag(191, 301, 10);
            expect(rectangle.x()).toEqual(100);
            expect(rectangle.y()).toEqual(110);
            expect(shape.group.classed('dragging')).toBeTruthy();

            movable.dragEnd();
            expect(shape.group.classed('dragging')).toBeFalsy();

        });

        it('Should be able to move an element by pressing the arrow keys;', function() {

            var svg       = document.createElement('svg'),
                blueprint = new Blueprint(svg),
                first     = blueprint.add('rect'),
                second    = blueprint.add('rect').x(100).y(100);

            expect(first.x(250).x()).toEqual(250);
            expect(first.y(250).y()).toEqual(250);

            Mousetrap.trigger('left');

            // Shouldn't move because it's not been selected.
            expect(first.isSelected()).toBeFalsy();
            expect(first.x(250).x()).toEqual(250);
            expect(first.y(250).y()).toEqual(250);

            // Element is now selected and should therefore listen to the arrow key events.
            first.select();
            Mousetrap.trigger('left');
            expect(first.isSelected()).toBeTruthy();
            expect(first.x()).toEqual(249);
            expect(second.x()).toEqual(100);
            expect(first.y()).toEqual(250);

            Mousetrap.trigger('right');
            expect(first.x()).toEqual(250);
            expect(second.x()).toEqual(100);

            Mousetrap.trigger('up');
            expect(first.x()).toEqual(250);
            expect(first.y()).toEqual(249);
            expect(second.x()).toEqual(100);
            expect(second.y()).toEqual(100);

            Mousetrap.trigger('down');
            expect(first.y()).toEqual(250);
            expect(second.y()).toEqual(100);

        });

        it('Should be able to move in large steps with the shift+arrow keys combination;', function() {

            var svg       = document.createElement('svg'),
                blueprint = new Blueprint(svg),
                rectangle = blueprint.add('rect').select().transform(100, 100);

            Mousetrap.trigger('shift+left');
            expect(rectangle.x()).toEqual(90);
            Mousetrap.trigger('shift+left');
            expect(rectangle.x()).toEqual(80);

            Mousetrap.trigger('shift+up');
            expect(rectangle.x()).toEqual(80);
            expect(rectangle.y()).toEqual(90);
            Mousetrap.trigger('shift+down');
            expect(rectangle.y()).toEqual(100);

        });

    });

});<|endoftext|>


<commit_before>import * as note from 'audio/notes';

const song = [
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [note.G3, 0.5],
  [note.C4, 0.5],
  [note.D4, 0.5],
  [note.G3, 0.5],
  [note.C4, 0.5],
  [note.D4, 0.5],
  [note.G3, 0.5],
  [note.C4, 0.5],
  [note.D4, 0.5],
  [note.G3, 0.5],
  [note.C4, 0.5],
  [note.D4, 0.5],
  [[note.G2, note.B3, note.D4, ], 0.5],
  [[note.G2, note.B3, note.D4, ], 0.5],
  [[note.G2, note.B3, note.D4, ], 0.5],
  [[note.G2, note.B3, note.D4, ], 0.125],
  [note.C4, 0.125],
  [note.C5, 0.125],
  [note.C5, 0.125],
  [note.G4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.C4, 0.125],
  [note.C5, 0.125],
  [note.C5, 0.125],
  [note.G4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.C4, 0.125],
  [note.C5, 0.125],
  [note.B4, 0.125],
  [note.A4, 0.125],
  [note.G4, 0.125],
  [note.F4, 0.125],
  [note.E4, 0.125],
  [note.D4, 0.125],
  [note.C4, 0.125],
  [note.C5, 0.125],
  [note.B4, 0.125],
  [note.A4, 0.125],
  [note.G4, 0.125],
  [note.F4, 0.125],
  [note.E4, 0.125],
  [note.D4, 0.125],
  [note.C4, 0.125],
  [note.G3, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.G3, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.G3, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.G3, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.5],
  [[note.C3, note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.125],
  [note.E4, 0.125],
  [note.E5, 0.125],
  [note.E5, 0.125],
  [note.B4, 0.125],
  [note.B4, 0.125],
  [note.G4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.E4, 0.125],
  [note.E5, 0.125],
  [note.E5, 0.125],
  [note.B4, 0.125],
  [note.B4, 0.125],
  [note.G4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.E4, 0.125],
  [note.E5, 0.125],
  [note.D5, 0.125],
  [note.C5, 0.125],
  [note.B4, 0.125],
  [note.A4, 0.125],
  [note.G4, 0.125],
  [note.F4, 0.125],
  [note.E4, 0.125],
  [note.E5, 0.125],
  [note.D5, 0.125],
  [note.C5, 0.125],
  [note.B4, 0.125],
  [note.A4, 0.125],
  [note.G4, 0.125],
  [note.F4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
];

export default song;<commit_msg>Regen winter after fixing parse bug.<commit_after>import * as note from 'audio/notes';

const song = [
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [note.F3, 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.E3, note.D4, note.B4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.C4, note.A4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.B3, note.D4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [[note.F3, note.C4, note.E4, ], 0.5],
  [note.G3, 0.5],
  [note.C4, 0.5],
  [note.D4, 0.5],
  [note.G3, 0.5],
  [note.C4, 0.5],
  [note.D4, 0.5],
  [note.G3, 0.5],
  [note.C4, 0.5],
  [note.D4, 0.5],
  [note.G3, 0.5],
  [note.C4, 0.5],
  [note.D4, 0.5],
  [[note.G2, note.B3, note.D4, ], 0.5],
  [[note.G2, note.B3, note.D4, ], 0.5],
  [[note.G2, note.B3, note.D4, ], 0.5],
  [[note.G2, note.B3, note.D4, ], 0.5],
  [note.C4, 0.125],
  [note.C5, 0.125],
  [note.C5, 0.125],
  [note.G4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.C4, 0.125],
  [note.C5, 0.125],
  [note.C5, 0.125],
  [note.G4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.C4, 0.125],
  [note.C5, 0.125],
  [note.B4, 0.125],
  [note.A4, 0.125],
  [note.G4, 0.125],
  [note.F4, 0.125],
  [note.E4, 0.125],
  [note.D4, 0.125],
  [note.C4, 0.125],
  [note.C5, 0.125],
  [note.B4, 0.125],
  [note.A4, 0.125],
  [note.G4, 0.125],
  [note.F4, 0.125],
  [note.E4, 0.125],
  [note.D4, 0.125],
  [note.C4, 0.125],
  [note.G3, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.G3, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.G3, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.G3, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [[note.C3, note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [[note.E3, note.G3, ], 0.5],
  [note.E4, 0.125],
  [note.E5, 0.125],
  [note.E5, 0.125],
  [note.B4, 0.125],
  [note.B4, 0.125],
  [note.G4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.E4, 0.125],
  [note.E5, 0.125],
  [note.E5, 0.125],
  [note.B4, 0.125],
  [note.B4, 0.125],
  [note.G4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.E4, 0.125],
  [note.E5, 0.125],
  [note.D5, 0.125],
  [note.C5, 0.125],
  [note.B4, 0.125],
  [note.A4, 0.125],
  [note.G4, 0.125],
  [note.F4, 0.125],
  [note.E4, 0.125],
  [note.E5, 0.125],
  [note.D5, 0.125],
  [note.C5, 0.125],
  [note.B4, 0.125],
  [note.A4, 0.125],
  [note.G4, 0.125],
  [note.F4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.G4, 0.125],
  [note.E4, 0.125],
  [note.C4, 0.125],
  [note.E4, 0.125],
  [note.G4, 0.125],
];

export default song;
<|endoftext|>


<commit_before>import React, { useState } from 'react';
import { c } from 'ttag';
import PropTypes from 'prop-types';
import { Icon } from 'react-components';

import { generateUID } from '../../helpers/component';
import useInput from './useInput';
import ErrorZone from '../text/ErrorZone';

const PasswordInput = (props) => {
    const [type, setType] = useState('password');

    const toggle = () => {
        setType(type === 'password' ? 'text' : 'password');
    };

    const { className, disabled, error, ...rest } = props;
    const { handlers, statusClasses, status } = useInput(props);
    const [uid] = useState(generateUID('passwordInput'));

    return (
        <>

                <input
                    className={`pm-field w100 ${className} ${statusClasses}`}
                    aria-invalid={error && status.isDirty}
                    aria-describedby={uid}
                    type={type}
                    disabled={disabled}
                    {...rest}
                    {...handlers}
                />
                <button
                    title={type === 'password' ? c('Label').t`Reveal password` : c('Label').t`Hide password`}
                    className="password-revealer inline-flex flex-item-noshrink"
                    disabled={disabled}
                    type="button"
                    onClick={toggle}
                >
                    <Icon className="mauto" name={type === 'password' ? 'read' : 'unread'} />
                </button>

            <ErrorZone id={uid}>{error && status.isDirty ? error : ''}</ErrorZone>
        </>
    );
};

PasswordInput.propTypes = {
    className: PropTypes.string,
    error: PropTypes.string,
    disabled: PropTypes.bool
};

export default PasswordInput;
<commit_msg>Don't allow focus on password toggle<commit_after>import React, { useState } from 'react';
import { c } from 'ttag';
import PropTypes from 'prop-types';
import { Icon } from 'react-components';

import { generateUID } from '../../helpers/component';
import useInput from './useInput';
import ErrorZone from '../text/ErrorZone';

const PasswordInput = (props) => {
    const [type, setType] = useState('password');

    const toggle = () => {
        setType(type === 'password' ? 'text' : 'password');
    };

    const { className, disabled, error, ...rest } = props;
    const { handlers, statusClasses, status } = useInput(props);
    const [uid] = useState(generateUID('passwordInput'));

    return (
        <>

                <input
                    className={`pm-field w100 ${className} ${statusClasses}`}
                    aria-invalid={error && status.isDirty}
                    aria-describedby={uid}
                    type={type}
                    disabled={disabled}
                    {...rest}
                    {...handlers}
                />
                <button
                    title={type === 'password' ? c('Label').t`Reveal password` : c('Label').t`Hide password`}
                    className="password-revealer inline-flex flex-item-noshrink"
                    tabIndex="-1"
                    disabled={disabled}
                    type="button"
                    onClick={toggle}
                >
                    <Icon className="mauto" name={type === 'password' ? 'read' : 'unread'} />
                </button>

            <ErrorZone id={uid}>{error && status.isDirty ? error : ''}</ErrorZone>
        </>
    );
};

PasswordInput.propTypes = {
    className: PropTypes.string,
    error: PropTypes.string,
    disabled: PropTypes.bool
};

export default PasswordInput;
<|endoftext|>


<commit_before>/*****************************************************************************************
 * Config Settings
 ****************************************************************************************/
var fps = 50;
var delay = 1000/fps;
var gameLength = 3000;

var players = 4;
var playerList = [ "Alpha", "Beta", "Gamma", "Delta", "DynamicTransit",
	"TheFriendlyOverlord", "Swag", "Creative","Hive","Insanity","BlackDeath",
	"Phi","CatUnicornLazerBeam" ];
var playerColors = ["#FF0000", "#00FF00", "#0000FF", "#FF00FF"];
var startingNumberOfUnits = 4;

var maxUnitsPerBaseOwned = 10;

/*****************************************************************************************
 * Game Globals (do not touch)
 ****************************************************************************************/
var points = [];
var bases = [];
var units = [];
var IDctr = 1;
var aiManager = {};
var gameInterval = {};

/*****************************************************************************************
 * Gameplay Logic
 ****************************************************************************************/
//initialize points array
function InitializeGame() {
	points = [];
	bases = [];
	units = [];
	IDctr = 1;

	var scorePanel = document.getElementById('scorePanel');
	scorePanel.innerHTML = "";
	for( var i = 0; i < players; i++ ){
		points[i]=0;
		scorePanel.innerHTML = scorePanel.innerHTML +
			'

';
	}

	//Load Map
	var mg = new MapGenerator( players );
	bases = mg.Generate();

	//Create Units
	units[0] = new Unit( -1, -1, -1, -1 ); // dummy unit
	for( var i = 0; i < players; i++ ) {
		bases[i].SetAllegiance( i );
		for( var j = 0; j < startingNumberOfUnits; j++ ) {
			units[IDctr] = ( new Unit( bases[i].locx, bases[i].locy, i, IDctr ) );
			IDctr++;
		}
	}
};

//Handler for AI messages and unit orders
function AIManagerOnMessage( ev ) {
	var orders = [];
	var ordered = [];
	//loop through orders
	if( ev.data["Orders"] ) {
		orders = ev.data["Orders"];
	}

	for( var i = 0; i < orders.length; i++ ) {
		var state = 0;
		var id = orders[i].unitID;
		//check if unit exists first
		if( !units[id] || units[id].health <= 0 || ordered[id]) {
			continue;
		}

		ordered[id] = true;

		//move
		if( orders[i].move ) {
			unitMove( id, orders[i].move );
			state = 1;
		}

		//run
		if( orders[i].dash ) {
			unitMove( id, orders[i].dash );
			state = 1;
			continue;
		}

		//farm
		if( orders[i].farm ) {
			for( var j = 0; j < bases.length; j++ ) {
				if( bases[j].allegiance == units[id].allegiance ) {
					units[id].Farm( bases[j] );
				}
			}
			continue;
		}

		//attack
		if( orders[i].attack ) {
			if( units[orders[i].attack] && units[orders[i].attack].health > 0) {
				if( units[orders[i].attack].allegiance == units[id].allegiance ) {
					points[units[id].allegiance] -= 10; //suicide deterrent
					continue;
				}
				units[id].Attack( units[orders[i].attack] );
				if( units[orders[i].attack].health <= 0 ) {
					points[units[id].allegiance] += 10;
				}
			}
			continue;
		}
	}

	for( var i = 0; i < units.length; i++ ) {
		if( units[i] ) {
			units[i].atkReload--;
		}
	}
};

/*****************************************************************************************
 * Gameplay Utility Functions
 ****************************************************************************************/
//Shuffles the order of an array of objects
function shuffle(o){
	for( var j, x, i = o.length;
		i;
		j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x
	);
	return o;
};

//Relocates unit based on movement string
function unitMove( id, dir ) {
	switch( dir ) {
		case "up":
			units[id].locy--;
			break;
		case "left":
			units[id].locx--;
			break;
		case "right":
			units[id].locx++;
			break;
		case "down":
			units[id].locy++;
			break;
	}
};

//Inspects all bases for allegiance changes
function checkBases () {
	for( var i = 0; i < bases.length; i++ ) {
		if( !isOccupiedByOwner( bases[i], units ) ){
			bases[i].SetAllegiance( -1 );
		}

		if( bases[i].allegiance == -1 ) {
			bases[i].SetAllegiance( greatestOccupancy( bases[i], units ) );
		}

		if( bases[i].farmTotal >= 1 ) {
			if( basesOwned( bases, bases[i].allegiance )*maxUnitsPerBaseOwned
				> unitsOwned( units, bases[i].allegiance ) ) {
				units[IDctr] =
					new Unit( bases[i].locx, bases[i].locy, bases[i].allegiance, IDctr );
				IDctr++;
				points[bases[i].allegiance]++;
			}
			bases[i].farmTotal = 0;
		}

		if( bases[i].allegiance >= 0 ) {
			points[bases[i].allegiance] += 0.02;
		}
	}
};

//does this base still have an allied unit in it?
function isOccupiedByOwner( b, u ) {
	for( var i = 0; i < u.length; i++ ) {
		if( u[i].health <= 0 ) {
			continue;
		}
		if( b.allegiance == u[i].allegiance ) {
			if( (u[i].locx - b.locx)*(u[i].locx - b.locx)
				+ (u[i].locy - b.locy)*(u[i].locy - b.locy) < b.R*b.R ) {
				return true; //base still occupied by owner.
			}
		}
	}
	return false;
};

//which army has the most units in this base?  return -1 for no units or tied max units
function greatestOccupancy( b, u ) {
	var count = [];
	var maxVal = -1;
	var max = 0;
	for( var i = 0; i  max ) {
				maxVal = u[i].allegiance;
				max = count[u[i].allegiance];
			} else if ( count[u[i].allegiance] == max ) {
				maxVal = -1;
			}
		}
	}
	return maxVal;
};

//count how many bases are owned by a particular army
function basesOwned( b, allegiance ) {
	var ctr = 0;
	for( var i = 0; i < b.length; i++ ){
		if( b[i].allegiance == allegiance ) {
			ctr++;
		}
	}
	return ctr;
};

//count how many units are owned by a particular army
function unitsOwned( u, allegiance ) {
	var ctr = 0;
	for( var i = 0; i  0 ) {
			ctr++;
		}
	}
	return ctr;
};

/*****************************************************************************************
 * Renderer Functions
 ****************************************************************************************/
function drawCanvas() {
	var c = document.getElementById("GameCanvas");
	var ctx = c.getContext("2d");
	ctx.clearRect(0,0,500,500);

	//draw bases
	ctx.fillStyle = "#888888";
	ctx.globalAlpha = 0.4;
	for( var i = 0; i < bases.length; i++ ) {
		ctx.beginPath();
		ctx.arc(bases[i].locx,bases[i].locy,bases[i].R,0,2*Math.PI);
		ctx.fill();
		ctx.lineWidth = 3;
		ctx.strokeStyle = bases[i].allegiance >= 0 ?
			playerColors[bases[i].allegiance] : '#000';
		ctx.stroke();
	}

	//draw farming indicator
	ctx.fillStyle = "#005500";
	ctx.globalAlpha = 0.4;
	for( var i = 0; i < bases.length; i++ ) {
		ctx.beginPath();
		ctx.arc(bases[i].locx,bases[i].locy,bases[i].R*bases[i].farmTotal,0,2*Math.PI);
		ctx.fill();
	}

	//draw units
	ctx.globalAlpha = 1.0;
	for( var i = 0; i < units.length; i++ ) {
		if( !units[i] ) {
			continue;
		}
		if( units[i].health <= 0 ){
			ctx.fillStyle = '#FFFFFF';
		} else {
			ctx.fillStyle = playerColors[units[i].allegiance];
		}
		ctx.beginPath();
		ctx.arc(units[i].locx,units[i].locy,5,0,2*Math.PI);
		ctx.fill();
	}
};

function drawScore ( t, gamePlayers ) {
	if( t % 10 != 0 ) {
		return;
	}
	updateTimer(t);
	//determine ranking
	var rank = [];
	for( var i = 0; i < points.length; i++ ) {
		rank.push({"p":i,"s":points[i]});
	}
	rank.sort(function(a, b){ return b.s - a.s });
	for( var i = 0; i < points.length; i++ ) {
		var playerScoreBox = document.getElementById('player'+i+'score');
		playerScoreBox.innerHTML = gamePlayers[i] + ":  "
			+ parseFloat(points[i]).toFixed(2)
			+ " --- "
			+ unitsOwned( units, i )
			+ " / "
			+ basesOwned( bases, i)*maxUnitsPerBaseOwned;
		var trans = getRankSpot(rank, i)*1.75;
		playerScoreBox.style.transform = "translate(0,"+trans+"em)";
		playerScoreBox.style.transition = "100ms ease-in";
	}
};

function getRankSpot(rank, p) {
	for( var i = 0; i < rank.length; i++ ) {
		if(rank[i].p == p)
			return i;
	}
	return -1;
};

function updateTimer( t ) {
	var tbox = document.getElementById('gameTimer');
	tbox.style.width = 500*t/gameLength + 'px';
	tbox.style.backgroundColor = "rgb("+(255-255*t/gameLength)+","+(255*t/gameLength)+",0)";
};

/*****************************************************************************************
 * Gameplay
 ****************************************************************************************/

function StartGame() {
	if( aiManager.terminate ) {
		aiManager.terminate();
		aiManager = {};
	}
	if( gameInterval ) {
		clearInterval( gameInterval );
	}

	//initialize randomized players list
	var gamePlayers = shuffle( playerList ).slice(0, players);

	InitializeGame();

	//Load the AI scripts for this scrimmage
	aiManager = new Worker('AIMANAGER.js');
	aiManager.onmessage = AIManagerOnMessage;
	aiManager.postMessage( { "LoadAI" : gamePlayers } );

	//initialize postMessage data for AI Manager
	var gameData = {};
	gameData["bases"] = bases;
	gameData["units"] = units;
	var timer = gameLength;

	//Main data and animation loop
	gameInterval = setInterval( function () {
		if ( timer <= 0 ) {
			aiManager.terminate();
			clearInterval( gameInterval );
		}
		checkBases();
		drawCanvas();
		drawScore(timer, gamePlayers);
		aiManager.postMessage( { "Data" : gameData } );
		timer--;
	}, delay);
}
<commit_msg>fixing bug where aiManager does not clean up worker threads<commit_after>/*****************************************************************************************
 * Config Settings
 ****************************************************************************************/
var fps = 50;
var delay = 1000/fps;
var gameLength = 3000;

var players = 4;
var playerList = [ "Alpha", "Beta", "Gamma", "Delta", "DynamicTransit",
	"TheFriendlyOverlord", "Swag", "Creative","Hive","Insanity","BlackDeath",
	"Phi","CatUnicornLazerBeam" ];
var playerColors = ["#FF0000", "#00FF00", "#0000FF", "#FF00FF"];
var startingNumberOfUnits = 4;

var maxUnitsPerBaseOwned = 10;

/*****************************************************************************************
 * Game Globals (do not touch)
 ****************************************************************************************/
var points = [];
var bases = [];
var units = [];
var IDctr = 1;
var aiManager = {};
var gameInterval = {};

/*****************************************************************************************
 * Gameplay Logic
 ****************************************************************************************/
//initialize points array
function InitializeGame() {
	points = [];
	bases = [];
	units = [];
	IDctr = 1;

	var scorePanel = document.getElementById('scorePanel');
	scorePanel.innerHTML = "";
	for( var i = 0; i < players; i++ ){
		points[i]=0;
		scorePanel.innerHTML = scorePanel.innerHTML +
			'

';
	}

	//Load Map
	var mg = new MapGenerator( players );
	bases = mg.Generate();

	//Create Units
	units[0] = new Unit( -1, -1, -1, -1 ); // dummy unit
	for( var i = 0; i < players; i++ ) {
		bases[i].SetAllegiance( i );
		for( var j = 0; j < startingNumberOfUnits; j++ ) {
			units[IDctr] = ( new Unit( bases[i].locx, bases[i].locy, i, IDctr ) );
			IDctr++;
		}
	}
};

//Handler for AI messages and unit orders
function AIManagerOnMessage( ev ) {
	var orders = [];
	var ordered = [];
	//loop through orders
	if( ev.data["Orders"] ) {
		orders = ev.data["Orders"];
	}

	for( var i = 0; i < orders.length; i++ ) {
		var state = 0;
		var id = orders[i].unitID;
		//check if unit exists first
		if( !units[id] || units[id].health <= 0 || ordered[id]) {
			continue;
		}

		ordered[id] = true;

		//move
		if( orders[i].move ) {
			unitMove( id, orders[i].move );
			state = 1;
		}

		//run
		if( orders[i].dash ) {
			unitMove( id, orders[i].dash );
			state = 1;
			continue;
		}

		//farm
		if( orders[i].farm ) {
			for( var j = 0; j < bases.length; j++ ) {
				if( bases[j].allegiance == units[id].allegiance ) {
					units[id].Farm( bases[j] );
				}
			}
			continue;
		}

		//attack
		if( orders[i].attack ) {
			if( units[orders[i].attack] && units[orders[i].attack].health > 0) {
				if( units[orders[i].attack].allegiance == units[id].allegiance ) {
					points[units[id].allegiance] -= 10; //suicide deterrent
					continue;
				}
				units[id].Attack( units[orders[i].attack] );
				if( units[orders[i].attack].health <= 0 ) {
					points[units[id].allegiance] += 10;
				}
			}
			continue;
		}
	}

	for( var i = 0; i < units.length; i++ ) {
		if( units[i] ) {
			units[i].atkReload--;
		}
	}
};

/*****************************************************************************************
 * Gameplay Utility Functions
 ****************************************************************************************/
//Shuffles the order of an array of objects
function shuffle(o){
	for( var j, x, i = o.length;
		i;
		j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x
	);
	return o;
};

//Relocates unit based on movement string
function unitMove( id, dir ) {
	switch( dir ) {
		case "up":
			units[id].locy--;
			break;
		case "left":
			units[id].locx--;
			break;
		case "right":
			units[id].locx++;
			break;
		case "down":
			units[id].locy++;
			break;
	}
};

//Inspects all bases for allegiance changes
function checkBases () {
	for( var i = 0; i < bases.length; i++ ) {
		if( !isOccupiedByOwner( bases[i], units ) ){
			bases[i].SetAllegiance( -1 );
		}

		if( bases[i].allegiance == -1 ) {
			bases[i].SetAllegiance( greatestOccupancy( bases[i], units ) );
		}

		if( bases[i].farmTotal >= 1 ) {
			if( basesOwned( bases, bases[i].allegiance )*maxUnitsPerBaseOwned
				> unitsOwned( units, bases[i].allegiance ) ) {
				units[IDctr] =
					new Unit( bases[i].locx, bases[i].locy, bases[i].allegiance, IDctr );
				IDctr++;
				points[bases[i].allegiance]++;
			}
			bases[i].farmTotal = 0;
		}

		if( bases[i].allegiance >= 0 ) {
			points[bases[i].allegiance] += 0.02;
		}
	}
};

//does this base still have an allied unit in it?
function isOccupiedByOwner( b, u ) {
	for( var i = 0; i < u.length; i++ ) {
		if( u[i].health <= 0 ) {
			continue;
		}
		if( b.allegiance == u[i].allegiance ) {
			if( (u[i].locx - b.locx)*(u[i].locx - b.locx)
				+ (u[i].locy - b.locy)*(u[i].locy - b.locy) < b.R*b.R ) {
				return true; //base still occupied by owner.
			}
		}
	}
	return false;
};

//which army has the most units in this base?  return -1 for no units or tied max units
function greatestOccupancy( b, u ) {
	var count = [];
	var maxVal = -1;
	var max = 0;
	for( var i = 0; i  max ) {
				maxVal = u[i].allegiance;
				max = count[u[i].allegiance];
			} else if ( count[u[i].allegiance] == max ) {
				maxVal = -1;
			}
		}
	}
	return maxVal;
};

//count how many bases are owned by a particular army
function basesOwned( b, allegiance ) {
	var ctr = 0;
	for( var i = 0; i < b.length; i++ ){
		if( b[i].allegiance == allegiance ) {
			ctr++;
		}
	}
	return ctr;
};

//count how many units are owned by a particular army
function unitsOwned( u, allegiance ) {
	var ctr = 0;
	for( var i = 0; i  0 ) {
			ctr++;
		}
	}
	return ctr;
};

/*****************************************************************************************
 * Renderer Functions
 ****************************************************************************************/
function drawCanvas() {
	var c = document.getElementById("GameCanvas");
	var ctx = c.getContext("2d");
	ctx.clearRect(0,0,500,500);

	//draw bases
	ctx.fillStyle = "#888888";
	ctx.globalAlpha = 0.4;
	for( var i = 0; i < bases.length; i++ ) {
		ctx.beginPath();
		ctx.arc(bases[i].locx,bases[i].locy,bases[i].R,0,2*Math.PI);
		ctx.fill();
		ctx.lineWidth = 3;
		ctx.strokeStyle = bases[i].allegiance >= 0 ?
			playerColors[bases[i].allegiance] : '#000';
		ctx.stroke();
	}

	//draw farming indicator
	ctx.fillStyle = "#005500";
	ctx.globalAlpha = 0.4;
	for( var i = 0; i < bases.length; i++ ) {
		ctx.beginPath();
		ctx.arc(bases[i].locx,bases[i].locy,bases[i].R*bases[i].farmTotal,0,2*Math.PI);
		ctx.fill();
	}

	//draw units
	ctx.globalAlpha = 1.0;
	for( var i = 0; i < units.length; i++ ) {
		if( !units[i] ) {
			continue;
		}
		if( units[i].health <= 0 ){
			ctx.fillStyle = '#FFFFFF';
		} else {
			ctx.fillStyle = playerColors[units[i].allegiance];
		}
		ctx.beginPath();
		ctx.arc(units[i].locx,units[i].locy,5,0,2*Math.PI);
		ctx.fill();
	}
};

function drawScore ( t, gamePlayers ) {
	if( t % 10 != 0 ) {
		return;
	}
	updateTimer(t);
	//determine ranking
	var rank = [];
	for( var i = 0; i < points.length; i++ ) {
		rank.push({"p":i,"s":points[i]});
	}
	rank.sort(function(a, b){ return b.s - a.s });
	for( var i = 0; i < points.length; i++ ) {
		var playerScoreBox = document.getElementById('player'+i+'score');
		playerScoreBox.innerHTML = gamePlayers[i] + ":  "
			+ parseFloat(points[i]).toFixed(2)
			+ " --- "
			+ unitsOwned( units, i )
			+ " / "
			+ basesOwned( bases, i)*maxUnitsPerBaseOwned;
		var trans = getRankSpot(rank, i)*1.75;
		playerScoreBox.style.transform = "translate(0,"+trans+"em)";
		playerScoreBox.style.transition = "100ms ease-in";
	}
};

function getRankSpot(rank, p) {
	for( var i = 0; i < rank.length; i++ ) {
		if(rank[i].p == p)
			return i;
	}
	return -1;
};

function updateTimer( t ) {
	var tbox = document.getElementById('gameTimer');
	tbox.style.width = 500*t/gameLength + 'px';
	tbox.style.backgroundColor = "rgb("+(255-255*t/gameLength)+","+(255*t/gameLength)+",0)";
};

/*****************************************************************************************
 * Gameplay
 ****************************************************************************************/

function StartGame() {
	if( aiManager.terminate ) {
		aiManager.postMessage( { "Terminate" : "" } );
		aiManager = {};
	}
	if( gameInterval ) {
		clearInterval( gameInterval );
	}

	//initialize randomized players list
	var gamePlayers = shuffle( playerList ).slice(0, players);

	InitializeGame();

	//Load the AI scripts for this scrimmage
	aiManager = new Worker('AIMANAGER.js');
	aiManager.onmessage = AIManagerOnMessage;
	aiManager.postMessage( { "LoadAI" : gamePlayers } );

	//initialize postMessage data for AI Manager
	var gameData = {};
	gameData["bases"] = bases;
	gameData["units"] = units;
	var timer = gameLength;

	//Main data and animation loop
	gameInterval = setInterval( function () {
		if ( timer <= 0 ) {
			aiManager.postMessage( { "Terminate" : "" } );
			clearInterval( gameInterval );
		}
		checkBases();
		drawCanvas();
		drawScore(timer, gamePlayers);
		aiManager.postMessage( { "Data" : gameData } );
		timer--;
	}, delay);
}
<|endoftext|>


<commit_before><commit_msg>ft(GET recipe): Added GET '/api/recipes/' endpoint<commit_after>// create my major routes here and export out to be used in the server
import fs from 'fs';

/**
 * A class that gets recipes and returns it
 * @constructor
 * @return {array} of json objects.
 *
 */
export class GetRecipes {
  /**
   * stores recipes from the RecipeService
   */
  static getRecipes(req, res) {

    fs.readFile('recipe.json', (err, data) => {
      if (err) throw err;
      let rec = JSON.parse(data);
      console.log(rec);
      res.send(rec);
    });
    console.log('This is right under the fs system');
  }
}

<|endoftext|>