<commit_before>'use strict';

var anymatch = require('./');
var assert = require('assert');
var path = require('path');

describe('anymatch', function() {
  var matchers = [
    'path/to/file.js',
    'path/anyjs/**/*.js',
    /foo.js$/,
    function(string) {
      return string.indexOf('bar') !== -1 && string.length > 10;
    }
  ];
  it('should resolve string matchers', function() {
    assert(anymatch(matchers, 'path/to/file.js'));
    assert(anymatch(matchers[0], 'path/to/file.js'));
    assert(!anymatch(matchers[0], 'bar.js'));
  });
  it('should resolve glob matchers', function() {
    assert(anymatch(matchers, 'path/anyjs/baz.js'));
    assert(anymatch(matchers[1], 'path/anyjs/baz.js'));
    assert(!anymatch(matchers[1], 'bar.js'));
  });
  it('should resolve regexp matchers', function() {
    assert(anymatch(matchers, 'path/to/foo.js'));
    assert(anymatch(matchers[2], 'path/to/foo.js'));
    assert(!anymatch(matchers[2], 'bar.js'));
  });
  it('should resolve function matchers', function() {
    assert(anymatch(matchers, 'path/to/bar.js'));
    assert(anymatch(matchers[3], 'path/to/bar.js'));
    assert(!anymatch(matchers[3], 'bar.js'));
  });
  it('should return false for unmatched strings', function() {
    assert(!anymatch(matchers, 'bar.js'));
  });
  it('should ignore improperly typed matchers', function() {
    var emptyObj = {};
    assert(!anymatch(emptyObj, emptyObj));
    assert(!anymatch(Infinity, Infinity));
  });

  describe('with returnIndex = true', function() {
    it('should return the array index of first positive matcher', function() {
      var result = anymatch(matchers, 'foo.js', true);
      assert.equal(result, 2);
    });
    it('should return 0 if provided non-array matcher', function() {
      var result = anymatch(matchers[2], 'foo.js', true);
      assert.equal(result, 0);
    });
    it('should return -1 if no match', function() {
      var result = anymatch(matchers, 'bar.js', true);
      assert.equal(result, -1);
    });
  });

  describe('curried matching function', function() {
    var matchFn = anymatch(matchers);
    it('should resolve matchers', function() {
      assert(matchFn('path/to/file.js'));
      assert(matchFn('path/anyjs/baz.js'));
      assert(matchFn('path/to/foo.js'));
      assert(matchFn('path/to/bar.js'));
      assert(!matchFn('bar.js'));
    });
    it('should be usable as an Array.prototype.filter callback', function() {
      var arr = [
        'path/to/file.js',
        'path/anyjs/baz.js',
        'path/to/foo.js',
        'path/to/bar.js',
        'bar.js',
        'foo.js'
      ];
      var expected = arr.slice();
      expected.splice(arr.indexOf('bar.js'), 1);
      assert.deepEqual(arr.filter(matchFn), expected);
    });
    it('should bind individual criterion', function() {
      assert(anymatch(matchers[0])('path/to/file.js'));
      assert(!anymatch(matchers[0])('path/to/other.js'));
      assert(anymatch(matchers[1])('path/anyjs/baz.js'));
      assert(!anymatch(matchers[1])('path/to/baz.js'));
      assert(anymatch(matchers[2])('path/to/foo.js'));
      assert(!anymatch(matchers[2])('path/to/foo.js.bak'));
      assert(anymatch(matchers[3])('path/to/bar.js'));
      assert(!anymatch(matchers[3])('bar.js'));
    });
  });

  describe('using matcher subsets', function() {
    it('should skip matchers before the startIndex', function() {
      assert(anymatch(matchers, 'path/to/file.js', false));
      assert(!anymatch(matchers, 'path/to/file.js', false, 1));
    });
    it('should skip matchers after and including the endIndex', function() {
      assert(anymatch(matchers, 'path/to/bars.js', false));
      assert(!anymatch(matchers, 'path/to/bars.js', false, 0, 3));
      assert(!anymatch(matchers, 'foo.js', false, 0, 1));
    });
  });

  describe('extra args', function() {
    it('should allow string to be passed as first member of an array', function() {
      assert(anymatch(matchers, ['path/to/bar.js']));
    });
    it('should pass extra args to function matchers', function() {
      matchers.push(function(string, arg1, arg2) { return arg1 || arg2; });
      assert(!anymatch(matchers, 'bar.js'), 1);
      assert(!anymatch(matchers, ['bar.js', 0]), 2);
      assert(anymatch(matchers, ['bar.js', true]), 3);
      assert(anymatch(matchers, ['bar.js', 0, true]), 4);
      // with returnIndex
      assert.equal(anymatch(matchers, ['bar.js', 1], true), 4, 5);
      // curried versions
      var matchFn1 = anymatch(matchers);
      var matchFn2 = anymatch(matchers[4]);
      assert(!matchFn1(['bar.js', 0]), 6);
      assert(!matchFn2(['bar.js', 0]), 7);
      assert(matchFn1(['bar.js', true]), 8);
      assert(matchFn2(['bar.js', true]), 9);
      assert(matchFn1(['bar.js', 0, true]), 10);
      assert(matchFn2(['bar.js', 0, true]), 11);
      // curried with returnIndex
      assert.equal(matchFn1(['bar.js', 1], true), 4, 12);
      assert.equal(matchFn2(['bar.js', 1], true), 0, 13);
      assert.equal(matchFn1(['bar.js', 0], true), -1, 14);
      assert.equal(matchFn2(['bar.js', 0], true), -1, 15);
      matchers.pop();
    });
  });

  describe('glob negation', function() {
    after(matchers.splice.bind(matchers, 4, 2));
    it('should respect negated globs included in a matcher array', function() {
      assert(anymatch(matchers, 'path/anyjs/no/no.js'), 'matches existing glob');
      matchers.push('!path/anyjs/no/*.js');
      assert(!anymatch(matchers, 'path/anyjs/no/no.js'), 'should be negated');
      assert(!anymatch(matchers)('path/anyjs/no/no.js'), 'should be negated (curried)');
    });
    it('should not break returnIndex option', function() {
      assert.equal(anymatch(matchers, 'path/anyjs/yes.js', true), 1);
      assert.equal(anymatch(matchers)('path/anyjs/yes.js', true), 1);
      assert.equal(anymatch(matchers, 'path/anyjs/no/no.js', true), -1);
      assert.equal(anymatch(matchers)('path/anyjs/no/no.js', true), -1);
    });
    it('should allow negated globs to negate non-glob matchers', function() {
      assert.equal(anymatch(matchers, 'path/to/bar.js', true), 3);
      matchers.push('!path/to/bar.*');
      assert(!anymatch(matchers, 'path/to/bar.js'));
    });
  });

  describe('windows paths', function() {
    var origSep = path.sep;
    path.sep = '\\';

    after(function() {
      path.sep = origSep;
    });

    it('should resolve backslashes against string matchers', function() {
      assert(anymatch(matchers, 'path\\to\\file.js'));
      assert(anymatch(matchers)('path\\to\\file.js'));
    });
    it('should resolve backslashes against glob matchers', function() {
      assert(anymatch(matchers, 'path\\anyjs\\file.js'));
      assert(anymatch(matchers)('path\\anyjs\\file.js'));
    });
    it('should resolve backslashes against regex matchers', function() {
      assert(anymatch(/path\/to\/file\.js/, 'path\\to\\file.js'));
      assert(anymatch(/path\/to\/file\.js/)('path\\to\\file.js'));
    });
  });
});
<commit_msg>Fix path.sep override<commit_after>'use strict';

var anymatch = require('./');
var assert = require('assert');
var path = require('path');

describe('anymatch', function() {
  var matchers = [
    'path/to/file.js',
    'path/anyjs/**/*.js',
    /foo.js$/,
    function(string) {
      return string.indexOf('bar') !== -1 && string.length > 10;
    }
  ];
  it('should resolve string matchers', function() {
    assert(anymatch(matchers, 'path/to/file.js'));
    assert(anymatch(matchers[0], 'path/to/file.js'));
    assert(!anymatch(matchers[0], 'bar.js'));
  });
  it('should resolve glob matchers', function() {
    assert(anymatch(matchers, 'path/anyjs/baz.js'));
    assert(anymatch(matchers[1], 'path/anyjs/baz.js'));
    assert(!anymatch(matchers[1], 'bar.js'));
  });
  it('should resolve regexp matchers', function() {
    assert(anymatch(matchers, 'path/to/foo.js'));
    assert(anymatch(matchers[2], 'path/to/foo.js'));
    assert(!anymatch(matchers[2], 'bar.js'));
  });
  it('should resolve function matchers', function() {
    assert(anymatch(matchers, 'path/to/bar.js'));
    assert(anymatch(matchers[3], 'path/to/bar.js'));
    assert(!anymatch(matchers[3], 'bar.js'));
  });
  it('should return false for unmatched strings', function() {
    assert(!anymatch(matchers, 'bar.js'));
  });
  it('should ignore improperly typed matchers', function() {
    var emptyObj = {};
    assert(!anymatch(emptyObj, emptyObj));
    assert(!anymatch(Infinity, Infinity));
  });

  describe('with returnIndex = true', function() {
    it('should return the array index of first positive matcher', function() {
      var result = anymatch(matchers, 'foo.js', true);
      assert.equal(result, 2);
    });
    it('should return 0 if provided non-array matcher', function() {
      var result = anymatch(matchers[2], 'foo.js', true);
      assert.equal(result, 0);
    });
    it('should return -1 if no match', function() {
      var result = anymatch(matchers, 'bar.js', true);
      assert.equal(result, -1);
    });
  });

  describe('curried matching function', function() {
    var matchFn = anymatch(matchers);
    it('should resolve matchers', function() {
      assert(matchFn('path/to/file.js'));
      assert(matchFn('path/anyjs/baz.js'));
      assert(matchFn('path/to/foo.js'));
      assert(matchFn('path/to/bar.js'));
      assert(!matchFn('bar.js'));
    });
    it('should be usable as an Array.prototype.filter callback', function() {
      var arr = [
        'path/to/file.js',
        'path/anyjs/baz.js',
        'path/to/foo.js',
        'path/to/bar.js',
        'bar.js',
        'foo.js'
      ];
      var expected = arr.slice();
      expected.splice(arr.indexOf('bar.js'), 1);
      assert.deepEqual(arr.filter(matchFn), expected);
    });
    it('should bind individual criterion', function() {
      assert(anymatch(matchers[0])('path/to/file.js'));
      assert(!anymatch(matchers[0])('path/to/other.js'));
      assert(anymatch(matchers[1])('path/anyjs/baz.js'));
      assert(!anymatch(matchers[1])('path/to/baz.js'));
      assert(anymatch(matchers[2])('path/to/foo.js'));
      assert(!anymatch(matchers[2])('path/to/foo.js.bak'));
      assert(anymatch(matchers[3])('path/to/bar.js'));
      assert(!anymatch(matchers[3])('bar.js'));
    });
  });

  describe('using matcher subsets', function() {
    it('should skip matchers before the startIndex', function() {
      assert(anymatch(matchers, 'path/to/file.js', false));
      assert(!anymatch(matchers, 'path/to/file.js', false, 1));
    });
    it('should skip matchers after and including the endIndex', function() {
      assert(anymatch(matchers, 'path/to/bars.js', false));
      assert(!anymatch(matchers, 'path/to/bars.js', false, 0, 3));
      assert(!anymatch(matchers, 'foo.js', false, 0, 1));
    });
  });

  describe('extra args', function() {
    it('should allow string to be passed as first member of an array', function() {
      assert(anymatch(matchers, ['path/to/bar.js']));
    });
    it('should pass extra args to function matchers', function() {
      matchers.push(function(string, arg1, arg2) { return arg1 || arg2; });
      assert(!anymatch(matchers, 'bar.js'), 1);
      assert(!anymatch(matchers, ['bar.js', 0]), 2);
      assert(anymatch(matchers, ['bar.js', true]), 3);
      assert(anymatch(matchers, ['bar.js', 0, true]), 4);
      // with returnIndex
      assert.equal(anymatch(matchers, ['bar.js', 1], true), 4, 5);
      // curried versions
      var matchFn1 = anymatch(matchers);
      var matchFn2 = anymatch(matchers[4]);
      assert(!matchFn1(['bar.js', 0]), 6);
      assert(!matchFn2(['bar.js', 0]), 7);
      assert(matchFn1(['bar.js', true]), 8);
      assert(matchFn2(['bar.js', true]), 9);
      assert(matchFn1(['bar.js', 0, true]), 10);
      assert(matchFn2(['bar.js', 0, true]), 11);
      // curried with returnIndex
      assert.equal(matchFn1(['bar.js', 1], true), 4, 12);
      assert.equal(matchFn2(['bar.js', 1], true), 0, 13);
      assert.equal(matchFn1(['bar.js', 0], true), -1, 14);
      assert.equal(matchFn2(['bar.js', 0], true), -1, 15);
      matchers.pop();
    });
  });

  describe('glob negation', function() {
    after(matchers.splice.bind(matchers, 4, 2));
    it('should respect negated globs included in a matcher array', function() {
      assert(anymatch(matchers, 'path/anyjs/no/no.js'), 'matches existing glob');
      matchers.push('!path/anyjs/no/*.js');
      assert(!anymatch(matchers, 'path/anyjs/no/no.js'), 'should be negated');
      assert(!anymatch(matchers)('path/anyjs/no/no.js'), 'should be negated (curried)');
    });
    it('should not break returnIndex option', function() {
      assert.equal(anymatch(matchers, 'path/anyjs/yes.js', true), 1);
      assert.equal(anymatch(matchers)('path/anyjs/yes.js', true), 1);
      assert.equal(anymatch(matchers, 'path/anyjs/no/no.js', true), -1);
      assert.equal(anymatch(matchers)('path/anyjs/no/no.js', true), -1);
    });
    it('should allow negated globs to negate non-glob matchers', function() {
      assert.equal(anymatch(matchers, 'path/to/bar.js', true), 3);
      matchers.push('!path/to/bar.*');
      assert(!anymatch(matchers, 'path/to/bar.js'));
    });
  });

  describe('windows paths', function() {
    var origSep = path.sep;

    before(function() {
      path.sep = '\\';
    });

    after(function() {
      path.sep = origSep;
    });

    it('should resolve backslashes against string matchers', function() {
      assert(anymatch(matchers, 'path\\to\\file.js'));
      assert(anymatch(matchers)('path\\to\\file.js'));
    });
    it('should resolve backslashes against glob matchers', function() {
      assert(anymatch(matchers, 'path\\anyjs\\file.js'));
      assert(anymatch(matchers)('path\\anyjs\\file.js'));
    });
    it('should resolve backslashes against regex matchers', function() {
      assert(anymatch(/path\/to\/file\.js/, 'path\\to\\file.js'));
      assert(anymatch(/path\/to\/file\.js/)('path\\to\\file.js'));
    });
  });
});
<|endoftext|>


<commit_before>describe('supply', function () {
  'use strict';

  var assume = require('assume')
    , supply = require('./');

  function Supply() {}
  require('util').inherits(Supply, require('events').EventEmitter);

  require('./').middleware(Supply);
  require('./').plugin(Supply);

  describe('.middleware', function () {
    it('is exposed as function', function () {
      assume(supply.middleware).to.be.a('function');
    });
  });

  describe('.plugin', function () {
    it('is exposed as function', function () {
      assume(supply.plugin).to.be.a('function');
    });
  });

  describe('.before', function () {
    it('emits a `before` event when adding a layer', function (next) {
      var s = new Supply();

      s.once('before', function (layer) {
        assume(layer).to.be.instanceOf(Supply.Layer);
        assume(layer.name).to.equal('bar');

        next();
      });

      assume(s.before('bar', function (bar) {})).to.equal(s);
    });

    it('receives the supplied options in the `before` event', function (next) {
      var s = new Supply();

      s.once('before', function (layer, options) {
        assume(options).to.be.a('object');
        assume(options.foo).to.equal('bar');
        assume(options.index).to.be.a('number');
        assume(options.index).to.equal(0);

        next();
      });

      s.before('bar', function (bar) {}, {
        foo: 'bar'
      });
    });

    it('returns the layer if no function is supplied', function () {
      var s = new Supply();
      assume(s.before('bar', function (bar) {})).to.equal(s);

      var layer = s.before('bar');

      assume(layer).to.be.instanceOf(Supply.Layer);
      assume(layer.name).to.equal('bar');
    });

    it('adds the middleware layers in order of specification', function () {
      var s = new Supply();

      s.before('bar', function (bar) {});
      s.before('foo', function (foo) {});

      assume(s._before[0].name).to.equal('bar');
      assume(s._before[1].name).to.equal('foo');
    });

    it('allows specifiying a custom index', function () {
      var s = new Supply();

      s.before('bar', function (bar) {});
      s.before('foo', function (foo) {}, { index: 0 });

      assume(s._before[0].name).to.equal('foo');
      assume(s._before[1].name).to.equal('bar');
    });

    it('normalizes the index when its out of bound', function () {
      var s = new Supply();

      s.before('bar', function (bar) {});
      s.before('foo', function (foo) {}, { index: 100 });

      assume(s._before).to.have.length(2);
    });

    it('allows adding before a different middleware', function () {
      var s = new Supply();

      s.before('bar', function (bar) {});
      s.before('foo', function (foo) {}, { before: 'bar' });

      assume(s._before[0].name).to.equal('foo');
      assume(s._before[1].name).to.equal('bar');
    });

    it('stores the custom context');
    it('allows modification of the layer during `before` event');
  });
});
<commit_msg>[test] Move test to correct describe.<commit_after>describe('supply', function () {
  'use strict';

  var assume = require('assume')
    , supply = require('./');

  function Supply() {}
  require('util').inherits(Supply, require('events').EventEmitter);

  require('./').middleware(Supply);
  require('./').plugin(Supply);

  describe('.middleware', function () {
    it('is exposed as function', function () {
      assume(supply.middleware).to.be.a('function');
    });

    describe('.before', function () {
      it('emits a `before` event when adding a layer', function (next) {
        var s = new Supply();

        s.once('before', function (layer) {
          assume(layer).to.be.instanceOf(Supply.Layer);
          assume(layer.name).to.equal('bar');

          next();
        });

        assume(s.before('bar', function (bar) {})).to.equal(s);
      });

      it('receives the supplied options in the `before` event', function (next) {
        var s = new Supply();

        s.once('before', function (layer, options) {
          assume(options).to.be.a('object');
          assume(options.foo).to.equal('bar');
          assume(options.index).to.be.a('number');
          assume(options.index).to.equal(0);

          next();
        });

        s.before('bar', function (bar) {}, {
          foo: 'bar'
        });
      });

      it('returns the layer if no function is supplied', function () {
        var s = new Supply();
        assume(s.before('bar', function (bar) {})).to.equal(s);

        var layer = s.before('bar');

        assume(layer).to.be.instanceOf(Supply.Layer);
        assume(layer.name).to.equal('bar');
      });

      it('adds the middleware layers in order of specification', function () {
        var s = new Supply();

        s.before('bar', function (bar) {});
        s.before('foo', function (foo) {});

        assume(s._before[0].name).to.equal('bar');
        assume(s._before[1].name).to.equal('foo');
      });

      it('allows specifiying a custom index', function () {
        var s = new Supply();

        s.before('bar', function (bar) {});
        s.before('foo', function (foo) {}, { index: 0 });

        assume(s._before[0].name).to.equal('foo');
        assume(s._before[1].name).to.equal('bar');
      });

      it('normalizes the index when its out of bound', function () {
        var s = new Supply();

        s.before('bar', function (bar) {});
        s.before('foo', function (foo) {}, { index: 100 });

        assume(s._before).to.have.length(2);
      });

      it('allows adding before a different middleware', function () {
        var s = new Supply();

        s.before('bar', function (bar) {});
        s.before('foo', function (foo) {}, { before: 'bar' });

        assume(s._before[0].name).to.equal('foo');
        assume(s._before[1].name).to.equal('bar');
      });

      it('stores the custom context');
      it('allows modification of the layer during `before` event');
    });
  });

  describe('.plugin', function () {
    it('is exposed as function', function () {
      assume(supply.plugin).to.be.a('function');
    });
  });
});
<|endoftext|>


<commit_before>/**
 * Represents a Tile of the board in the game.
 * Same properties as Actor but will have a "Tile" tag.
 */

if (typeof(module) !== 'undefined') {
  var Actor = require('./actor.js').Actor;
}

(function() {
  var Tile = (function() {
    var Tile = function(name, symbol, description) {
      Actor.apply(this, [name, symbol, description]);
      this.addTag("Tile");
    };

    Tile.prototype = Object.create(Actor.prototype);
    Tile.prototype.constructor = Tile;

    return Tile;
  })();

  if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
    module.exports = {
      Tile: Tile
    };
  else
    window.Tile = Tile;
})();<commit_msg>add color to tiles<commit_after>/**
 * Represents a Tile of the board in the game.
 * Same properties as Actor but will have a "Tile" tag.
 */

if (typeof(module) !== 'undefined') {
  var Actor = require('./actor.js').Actor;
}

(function() {
  var Tile = (function() {
    var Tile = function(name, symbol, description) {
      Actor.apply(this, [name, symbol, description]);
      this.addTag("Tile");
      this.color = undefined;
    };

    Tile.prototype.setColor = function(color) {
      this.color = color;
    };

    Tile.prototype = Object.create(Actor.prototype);
    Tile.prototype.constructor = Tile;

    return Tile;
  })();

  if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
    module.exports = {
      Tile: Tile
    };
  else
    window.Tile = Tile;
})();<|endoftext|>


<commit_before>var _ = require("lodash");
var inquirer = require('inquirer');
var throwOut = require('./helpers/throw-out');
var displayImage = require('./helpers/display-image');
var Jimp = require("jimp");
var fs = require('mz/fs');
var path = require('path');
var mkdirp = require('mkdirp-then');

const DEFAULT_EMPTY_THRESHOLD = 0.05;
const DEFAULT_DUPE_THRESHOLD = 0.03;

function decodeBase64Image(dataString) {
    var matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/),
    response = {};

    if (matches.length !== 3) {
    return new Error('Invalid input string');
    }

    response.type = matches[1];
    response.data = Buffer.from(matches[2], 'base64');

    return response;
}

class Tile {

    constructor(img, tileSheetName, width, height, tags, tileIndex) {
        this.tileSheetName = tileSheetName;
        this.width = width;
        this.height = height;
        this.img = img;
        this.hash = this.img.hash();
        this.tags = tags || [];
        this.tileIndex = isNaN(tileIndex) ? null : tileIndex;
    }

    static slice(img, tileSheetName, maxTileWidth, maxTileHeight, tags) {
        var tiles = [];
        var remainingWidth = img.bitmap.width;
        var remainingHeight = img.bitmap.height;
        console.log("Slicing tiles...");

        while (remainingWidth > 0) {
            var tileWidth = Math.min(maxTileWidth, remainingWidth);
            while (remainingHeight > 0) {
                var tileHeight = Math.min(maxTileHeight, remainingHeight);

                tiles.push(new Tile(
                    img.clone().crop(remainingWidth - tileWidth, remainingHeight - tileHeight, tileWidth, tileHeight),
                    tileSheetName,
                    tileWidth,
                    tileHeight,
                    tags
                ));
                remainingHeight -= tileHeight;
            }
            remainingWidth -= tileWidth;
            remainingHeight = img.bitmap.height;
        }
        return tiles;
    }

    static rejectInvalid(tiles, referenceTiles, rejectedPath) {
        return Tile.filterOutEmpty(tiles, DEFAULT_EMPTY_THRESHOLD, rejectedPath)
            .then(function(nonEmptyTiles){
                return Tile.dedupe(nonEmptyTiles, referenceTiles, DEFAULT_DUPE_THRESHOLD, rejectedPath);
            });
    }

    static filterOutEmpty(tiles, dupeThreshold, rejectedPath) {
        var emptyImg;
        if (_.isUndefined(dupeThreshold)) dupeThreshold = DEFAULT_EMPTY_THRESHOLD;

        console.log("Filtering out empty tiles...");
        var promise = Promise.resolve();

        if (rejectedPath) {
            promise = promise
                .then(function(){
                    var emptyDir = path.join(rejectedPath, 'Empty');
                    return mkdirp(emptyDir)
                        .then(function(){
                            return emptyDir;
                        });
                })
        }
        var writePromises = [];
        var result = _.filter(tiles, function(tile){
            if (tile.hash == '00000000000') {
                var isDupe = true;
            } else {
                if (!emptyImg || (emptyImg.bitmap.width != tile.width || emptyImg.bitmap.height != tile.height)) {
                    emptyImg = new Jimp(tile.width, tile.height);
                }

                var distance = Jimp.distance(emptyImg, tile.img); // perceived distance
                var diff = Jimp.diff(emptyImg, tile.img);         // pixel difference

                isDupe = distance < dupeThreshold && diff.percent < dupeThreshold;
            }

            if (rejectedPath && isDupe) {
                writePromises.push(
                    promise
                        .then(function(emptyDir){
                            return tile.img.write(path.format({dir: emptyDir, base: 'empty-' + tile.hash + '.png'}));
                        })
                );
            }

            return !isDupe;
        });

        return promise
            .then(function(){
                return Promise.all(writePromises);
            })
            .then(function(){
                console.log("Filtered out", tiles.length - result.length, "empty tiles.");
                return result;
            });
    }

    checkIfDupe(refTile, dupeThreshold) {
        if (_.isUndefined(dupeThreshold)) dupeThreshold = DEFAULT_DUPE_THRESHOLD;
        var distance = Jimp.distance(refTile.img, this.img); // perceived distance
        var diff = Jimp.diff(refTile.img, this.img);         // pixel difference

        if (distance < dupeThreshold && diff.percent < dupeThreshold) {
            return true;
        }
        return false;
    }

    writeDupePair(dupe, rejectedPath) {
        var tile = this;
        var tileName = (tile.tileIndex ? tile.tileIndex : tile.hash);
        var dupeName = (dupe.tileIndex ? dupe.tileIndex : dupe.hash);

        var dupeDir = path.join(rejectedPath, 'Dupes', tile.tileSheetName, tileName + '-' + dupeName);

        return mkdirp(dupeDir)
            .then(function(){
                // console.log(arguments);
                var tilePath = path.format({dir: dupeDir, base: 'reject-' + tile.tileSheetName + '-' + tileName + '.png'});
                var dupePath = path.format({dir: dupeDir, base: 'keep-' + dupe.tileSheetName + '-' + dupeName + '.png'});
                // console.log(tilePath, dupePath);
                return Promise.all([
                    tile.img.write(tilePath),
                    dupe.img.write(dupePath)
                ]);
            })
    }

    static dedupe(inputTiles, referenceTiles, dupeThreshold, rejectedPath) {
        console.log("Deduping tiles...");
        var tiles = inputTiles;
        var checked = {};
        var dupeTiles = {};
        var result = [];
        var writePromises = [];

        var tilesToCheck = tiles.concat(referenceTiles);

        for (var ii = 0; ii < tiles.length; ++ii) {
            for (var jj = 0; jj < tilesToCheck.length; ++jj) {
                if (ii != jj && !(checked[ii] && checked[ii][jj])) {
                    if (jj < tiles.length) {
                        if (!(jj in checked)) {
                            checked[jj] = {};
                        }
                        checked[jj][ii] = 1;
                    }
                    if (tiles[ii].checkIfDupe(tilesToCheck[jj], dupeThreshold)) {
                        dupeTiles[ii] = jj;
                        if (rejectedPath) {
                            writePromises.push(tiles[ii].writeDupePair(tilesToCheck[jj], rejectedPath));
                        }
                        break;
                    }
                }
            }

            if (!(ii in dupeTiles)) {
                result.push(tiles[ii]);
            }
        }

        return Promise.all(writePromises)
            .then(function(){
                console.log("Removed", _.keys(dupeTiles).length, "dupes.");
                return result;
            });
    }

    confirm() {
        var self = this;
        return displayImage(self.img)
            .then(function(){
                return inquirer.prompt([{
                        type: 'list',
                        name: 'confirmTile',
                        message: self.hash + ' - ' + self.width + 'x' + self.height + ' tile (preview). Look ok?',
                        default: 1,
                        choices: [
                            {name: 'Skip', value: 'skip', short: 's'},
                            {name: 'Add', value: 'add', short: 'a'}
                            // {name: 'Tag', value: 'tag', short: 't'}
                        ]
                    }])
                    .then(function(answer){
                        if (answer.confirmTile == 'skip') throw new Error("Aborted tile.");
                        return answer;
                    }, throwOut);
            }, throwOut);
    }

    static deserialize(obj) {
        return Jimp.read(decodeBase64Image(obj.base64).data)
            .then(function(image){
                return new Tile(image, obj.tileSheetName, obj.width, obj.height, obj.tags, obj.tileIndex);
            }, throwOut);
    }

    serialize() {
        var self = this;
        return new Promise(function(resolve, reject){
            self.img.getBase64(Jimp.MIME_PNG, function(err, base64){
                if (err) reject(err);
                var serialized = _.toPlainObject(self);
                delete serialized.img;
                serialized.base64 = base64;
                resolve(serialized);
            });
        })
    }

}
module.exports = Tile;
<commit_msg>changed tile slicing order<commit_after>var _ = require("lodash");
var inquirer = require('inquirer');
var throwOut = require('./helpers/throw-out');
var displayImage = require('./helpers/display-image');
var Jimp = require("jimp");
var fs = require('mz/fs');
var path = require('path');
var mkdirp = require('mkdirp-then');

const DEFAULT_EMPTY_THRESHOLD = 0.05;
const DEFAULT_DUPE_THRESHOLD = 0.03;

function decodeBase64Image(dataString) {
    var matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/),
    response = {};

    if (matches.length !== 3) {
    return new Error('Invalid input string');
    }

    response.type = matches[1];
    response.data = Buffer.from(matches[2], 'base64');

    return response;
}

class Tile {

    constructor(img, tileSheetName, width, height, tags, tileIndex) {
        this.tileSheetName = tileSheetName;
        this.width = width;
        this.height = height;
        this.img = img;
        this.hash = this.img.hash();
        this.tags = tags || [];
        this.tileIndex = isNaN(tileIndex) ? null : tileIndex;
    }

    static slice(img, tileSheetName, maxTileWidth, maxTileHeight, tags) {
        var tiles = [];
        var remainingWidth = img.bitmap.width;
        var remainingHeight = img.bitmap.height;
        console.log("Slicing tiles...");

        while (remainingHeight > 0) {
            var tileHeight = Math.min(maxTileHeight, remainingHeight);

            while (remainingWidth > 0) {
                var tileWidth = Math.min(maxTileWidth, remainingWidth);

                tiles.push(new Tile(
                    img.clone().crop(remainingWidth - tileWidth, remainingHeight - tileHeight, tileWidth, tileHeight),
                    tileSheetName,
                    tileWidth,
                    tileHeight,
                    tags
                ));
                remainingWidth -= tileWidth;
            }
            remainingHeight -= tileHeight;
            remainingWidth = img.bitmap.width;
        }
        return tiles;
    }

    static rejectInvalid(tiles, referenceTiles, rejectedPath) {
        return Tile.filterOutEmpty(tiles, DEFAULT_EMPTY_THRESHOLD, rejectedPath)
            .then(function(nonEmptyTiles){
                return Tile.dedupe(nonEmptyTiles, referenceTiles, DEFAULT_DUPE_THRESHOLD, rejectedPath);
            });
    }

    static filterOutEmpty(tiles, dupeThreshold, rejectedPath) {
        var emptyImg;
        if (_.isUndefined(dupeThreshold)) dupeThreshold = DEFAULT_EMPTY_THRESHOLD;

        console.log("Filtering out empty tiles...");
        var promise = Promise.resolve();

        if (rejectedPath) {
            promise = promise
                .then(function(){
                    var emptyDir = path.join(rejectedPath, 'Empty');
                    return mkdirp(emptyDir)
                        .then(function(){
                            return emptyDir;
                        });
                })
        }
        var writePromises = [];
        var result = _.filter(tiles, function(tile){
            if (tile.hash == '00000000000') {
                var isDupe = true;
            } else {
                if (!emptyImg || (emptyImg.bitmap.width != tile.width || emptyImg.bitmap.height != tile.height)) {
                    emptyImg = new Jimp(tile.width, tile.height);
                }

                var distance = Jimp.distance(emptyImg, tile.img); // perceived distance
                var diff = Jimp.diff(emptyImg, tile.img);         // pixel difference

                isDupe = distance < dupeThreshold && diff.percent < dupeThreshold;
            }

            if (rejectedPath && isDupe) {
                writePromises.push(
                    promise
                        .then(function(emptyDir){
                            return tile.img.write(path.format({dir: emptyDir, base: 'empty-' + tile.hash + '.png'}));
                        })
                );
            }

            return !isDupe;
        });

        return promise
            .then(function(){
                return Promise.all(writePromises);
            })
            .then(function(){
                console.log("Filtered out", tiles.length - result.length, "empty tiles.");
                return result;
            });
    }

    checkIfDupe(refTile, dupeThreshold) {
        if (_.isUndefined(dupeThreshold)) dupeThreshold = DEFAULT_DUPE_THRESHOLD;
        var distance = Jimp.distance(refTile.img, this.img); // perceived distance
        var diff = Jimp.diff(refTile.img, this.img);         // pixel difference

        if (distance < dupeThreshold && diff.percent < dupeThreshold) {
            return true;
        }
        return false;
    }

    writeDupePair(dupe, rejectedPath) {
        var tile = this;
        var tileName = (tile.tileIndex ? tile.tileIndex : tile.hash);
        var dupeName = (dupe.tileIndex ? dupe.tileIndex : dupe.hash);

        var dupeDir = path.join(rejectedPath, 'Dupes', tile.tileSheetName, tileName + '-' + dupeName);

        return mkdirp(dupeDir)
            .then(function(){
                // console.log(arguments);
                var tilePath = path.format({dir: dupeDir, base: 'reject-' + tile.tileSheetName + '-' + tileName + '.png'});
                var dupePath = path.format({dir: dupeDir, base: 'keep-' + dupe.tileSheetName + '-' + dupeName + '.png'});
                // console.log(tilePath, dupePath);
                return Promise.all([
                    tile.img.write(tilePath),
                    dupe.img.write(dupePath)
                ]);
            })
    }

    static dedupe(inputTiles, referenceTiles, dupeThreshold, rejectedPath) {
        console.log("Deduping tiles...");
        var tiles = inputTiles;
        var checked = {};
        var dupeTiles = {};
        var result = [];
        var writePromises = [];

        var tilesToCheck = tiles.concat(referenceTiles);

        for (var ii = 0; ii < tiles.length; ++ii) {
            for (var jj = 0; jj < tilesToCheck.length; ++jj) {
                if (ii != jj && !(checked[ii] && checked[ii][jj])) {
                    if (jj < tiles.length) {
                        if (!(jj in checked)) {
                            checked[jj] = {};
                        }
                        checked[jj][ii] = 1;
                    }
                    if (tiles[ii].checkIfDupe(tilesToCheck[jj], dupeThreshold)) {
                        dupeTiles[ii] = jj;
                        if (rejectedPath) {
                            writePromises.push(tiles[ii].writeDupePair(tilesToCheck[jj], rejectedPath));
                        }
                        break;
                    }
                }
            }

            if (!(ii in dupeTiles)) {
                result.push(tiles[ii]);
            }
        }

        return Promise.all(writePromises)
            .then(function(){
                console.log("Removed", _.keys(dupeTiles).length, "dupes.");
                return result;
            });
    }

    confirm() {
        var self = this;
        return displayImage(self.img)
            .then(function(){
                return inquirer.prompt([{
                        type: 'list',
                        name: 'confirmTile',
                        message: self.hash + ' - ' + self.width + 'x' + self.height + ' tile (preview). Look ok?',
                        default: 1,
                        choices: [
                            {name: 'Skip', value: 'skip', short: 's'},
                            {name: 'Add', value: 'add', short: 'a'}
                            // {name: 'Tag', value: 'tag', short: 't'}
                        ]
                    }])
                    .then(function(answer){
                        if (answer.confirmTile == 'skip') throw new Error("Aborted tile.");
                        return answer;
                    }, throwOut);
            }, throwOut);
    }

    static deserialize(obj) {
        return Jimp.read(decodeBase64Image(obj.base64).data)
            .then(function(image){
                return new Tile(image, obj.tileSheetName, obj.width, obj.height, obj.tags, obj.tileIndex);
            }, throwOut);
    }

    serialize() {
        var self = this;
        return new Promise(function(resolve, reject){
            self.img.getBase64(Jimp.MIME_PNG, function(err, base64){
                if (err) reject(err);
                var serialized = _.toPlainObject(self);
                delete serialized.img;
                serialized.base64 = base64;
                resolve(serialized);
            });
        })
    }

}
module.exports = Tile;
<|endoftext|>


<commit_before>"use strict"

const asset = require('./asset')
const colorDepth = require('./converter/colorDepth')
const clamp = require('lodash/clamp')
const glob = require('glob')
const createMappoSession = require('./createMappoSession')
const createMappoMap = require('./createMappoMap')
const createMappoTileset = require('./createMappoTileset')
const detectFormat = require('./detectFormat')
const path = require('path')
const fs = require('fs')
const setupKeyboard = require('./setupKeyboard')
const renderTileHighlightInvertedOutline = require('./renderTileHighlightInvertedOutline')
const renderTileHighlightInvertedSolid = require('./renderTileHighlightInvertedSolid')
const renderTileHighlightColorOutline = require('./renderTileHighlightColorOutline')
const renderTile = require('./renderTile')
const renderLayer = require('./renderLayer')
const renderTileset = require('./renderTileset')
const createCheckerboardPattern = require('./createCheckerboardPattern')
const calcAutoScroll = require('./calcAutoScroll')
const clearCanvas = require('./clearCanvas')

// DOM REFERENCES
const pageTitle = document.querySelector('title')
const mapList = document.querySelector('.map-list')
const layerList = document.querySelector('.layer-list')
const canvas = document.querySelector('.mappo-viewport')
const context = canvas.getContext('2d')
const tilesetCanvasContainer = document.querySelector('.tileset-canvas-container')
const tilesetCanvas = document.querySelector('.tileset-canvas')
const tilesetContext = tilesetCanvas.getContext('2d')
const tilesetSelectedTileCanvas = document.querySelector('.tileset-selected-tile-canvas')
const tilesetSelectedTileContext = tilesetSelectedTileCanvas.getContext('2d')
const tilesetSelectedTileIndex = document.querySelector('.tileset-selected-tile-index')
const tilesetHoveringTileCanvas = document.querySelector('.tileset-hovering-tile-canvas')
const tilesetHoveringTileContext = tilesetHoveringTileCanvas.getContext('2d')
const tilesetHoveringTileIndex = document.querySelector('.tileset-hovering-tile-index')
const middlePanel = document.querySelector('.middle-panel')

const checkerboardPattern = createCheckerboardPattern({document})

const launchFolder = 'data' // TODO(chuck): temp hack for windows. empty string dunna work
console.log('launchFolder', launchFolder)
const mapGlob = '**/*.map'
const mapFilenames = glob.sync(mapGlob, {nocase: true})
const mappoSession = createMappoSession({
  fileSystem: {
    files: mapFilenames
  },
  launchFolder
})

const viewportScales = [0.25, 0.5, 0.75, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const DEFAULT_SCALE_INDEX = 4
const defaultMappoState = {
  scaleIndex: DEFAULT_SCALE_INDEX,
  isLoading: true,
  map: null,
  mapLayerOrder: null,
  mapLayerSelected: null,
  tileset: null,
  tilesetTileHovering: null,
  tilesetTileSelected: null,
  camera: {
    x: 0,
    y: 0,
    move: {
      x: 0,
      y: 0,
    },
  },
  cameraScrollAmount: 1,
  keyPressed: {},
  autoScroll: {},
}
let mappoState = Object.assign({}, defaultMappoState)

const getScale = () => viewportScales[mappoState.scaleIndex]

mappoSession.getMapFilenames().forEach(mapFilename => {
  const li = document.createElement('li')
  li.setAttribute('title', mapFilename)
  li.innerText = mapFilename
  // TODO(chuck): temp hack for windows. figure out better launchFolder shenanigans
  li.addEventListener('click', event => {
    loadMap('data/' + mapFilename)
    pageTitle.innerText = 'Mappo - ' + mapFilename
  })
  mapList.appendChild(li)
})

const refreshMapLayerList = () => {
  layerList.innerHTML = ''
  mappoState.map.tileLayers.forEach((layer, index) => {
    const li = document.createElement('li')
    li.setAttribute('title', layer.description)
    li.innerText = layer.description
    li.classList.add('layer-list-item')
    if (mappoState.mapLayerSelected === layer) {
      li.classList.add('selected')
    }
    li.addEventListener('click', event => {
      // TODO(chuck): use a proper element?
      if (event.offsetX < 35) {
        li.classList.toggle('is-layer-hidden')
        layer.isHidden = !layer.isHidden
      }
      mappoState.mapLayerSelected = layer
      const layerListItems = document.querySelectorAll('.layer-list .layer-list-item')
      layerListItems.forEach(layerListItem => {
        layerListItem.classList.remove('selected')
      })
      li.classList.add('selected')
    })
    layerList.appendChild(li)
  })
}

const loadMappoTileset = ({
  context,
  mapFilename,
  map,
}) => {
  const vspFilename = path.join(path.dirname(mapFilename), map.tilesetFilename)
  const vspBuffer = fs.readFileSync(vspFilename)
  const vspFormat = detectFormat(vspBuffer)
  console.log('vspFormat', vspFormat)

  let vspData
  try {
    vspData = asset.fromBuffer(vspBuffer, asset[vspFormat])
  } catch (exception) {
    console.log(exception)
    return
  }

  console.log(vspFilename, vspData)

  const tileset = createMappoTileset({context, tileset: vspData})

  return tileset
}

const loadMap = mapFilename => {
  console.group()
  try {
    mappoState = Object.assign({}, defaultMappoState)
    mappoState.isLoading = true

    const mapBuffer = fs.readFileSync(mapFilename)
    const mapFormat = detectFormat(mapBuffer)
    if (!mapFormat.includes('map')) {
      console.error('expected map but got', mapFormat)
      return
    }
    console.log('mapFormat', mapFormat)
    let mapData
    try {
      mapData = asset.fromBuffer(mapBuffer, asset[mapFormat])
    } catch (exception) {
      console.log(exception)
      return
    }
    mappoState.map = createMappoMap({map: mapData})
    console.log(mapFilename, mapData)
    mappoState.mapLayerSelected = mappoState.map.tileLayers[mappoState.map.mapLayerOrder[0]]
    refreshMapLayerList()

    mappoState.tileset = loadMappoTileset({context, mapFilename, map: mappoState.map})
    tilesetSelectedTileCanvas.width = mappoState.tileset.tileWidth
    tilesetSelectedTileCanvas.height = mappoState.tileset.tileHeight
    tilesetHoveringTileCanvas.width = mappoState.tileset.tileWidth
    tilesetHoveringTileCanvas.height = mappoState.tileset.tileHeight

    mappoState.tileset.imageBitmapPromise.then(() => {
      mappoState.isLoading = false
      resizeCanvas()
    })
  } catch (exception) {
    console.error('ack!', exception)
  } finally {
    console.groupEnd()
  }
}

const keyboard = setupKeyboard({
  addEventListener: document.addEventListener,
  keyPressed: mappoState.keyPressed,
})

let mousedown = false
let mousein = false
middlePanel.addEventListener('mousedown', event => {
  mousedown = true
})

let mapLayerTileHighlightCoord = null
middlePanel.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  const scale = getScale()
  if (mousedown) {
    moveCamera(
      -event.movementX / scale,
      -event.movementY / scale
    )
  }

  mappoState.autoScroll = {}
  if (mousein) {
    mappoState.autoScroll = calcAutoScroll({
      scale,
      threshold: (mappoState.tileset.tileWidth + mappoState.tileset.tileHeight) / 2,
      cursorX: event.offsetX,
      cursorY: event.offsetY,
      viewportWidth: middlePanel.offsetWidth,
      viewportHeight: middlePanel.offsetHeight,
    })
  }

  const mapLayerSelected = mappoState.mapLayerSelected
  if (mapLayerSelected) {
    const tileWidth = mappoState.tileset.tileWidth
    const tileHeight = mappoState.tileset.tileHeight
    const scaleX = ~~(event.offsetX / scale)
    const scaleY = ~~(event.offsetY / scale)
    const parallaxX = ~~(mappoState.camera.x * mapLayerSelected.parallax.x)
    const parallaxY = ~~(mappoState.camera.y * mapLayerSelected.parallax.y)
    const x = scaleX - ((parallaxX + scaleX) % tileWidth)
    const y = scaleY - ((parallaxY + scaleY) % tileHeight)

    mapLayerTileHighlightCoord = {x, y}
  }
})

const getTileCoordAndIndex = ({
  tileset,
  containerWidth,
  pixelX,
  pixelY,
}) => {
  const scale = getScale()
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const tileX = ~~(pixelX / (tileset.tileWidth * scale))
  const tileY = ~~(pixelY / (tileset.tileHeight * scale))
  const tileIndex = (tileY * tilesetColumns) + tileX

  return {
    tileX,
    tileY,
    tileIndex,
  }
}

tilesetCanvasContainer.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.tilesetTileHovering = getTileCoordAndIndex({
    tileset: mappoState.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

tilesetCanvasContainer.addEventListener('click', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.tilesetTileSelected = getTileCoordAndIndex({
    tileset: mappoState.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

middlePanel.addEventListener('mouseup', event => {
  mousedown = false
})

middlePanel.addEventListener('mouseenter', event => {
  mousein = true
})

middlePanel.addEventListener('mouseout', event => {
  mousein = false
  mappoState.camera.move = {x: 0, y: 0}
  mappoState.autoScroll = {}
  mapLayerTileHighlightCoord = null
})

const moveCamera = (moveX, moveY) => {
  mappoState.camera.x = clamp(mappoState.camera.x + moveX, 0, (mappoState.map.tileLayers[0].width * mappoState.tileset.tileWidth) - canvas.width)
  mappoState.camera.y = clamp(mappoState.camera.y + moveY, 0, (mappoState.map.tileLayers[0].height * mappoState.tileset.tileHeight) - canvas.height)
}

const getTilesetColumns = ({tileset, containerWidth}) => {
  const scaledTileWidth = tileset.tileWidth * getScale()
  return ~~(containerWidth / scaledTileWidth)
}

const getTilesetRows = ({tileset, containerWidth}) => {
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const roundedUpRows = ~~((tileset.numTiles + (tilesetColumns - 1)) / tilesetColumns)
  return roundedUpRows
}

const tick = () => {
  clearCanvas({canvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetCanvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetSelectedTileCanvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetHoveringTileCanvas, pattern: checkerboardPattern})

  if (!mappoState.isLoading) {
    const tileset = mappoState.tileset
    const tileWidth = tileset.tileWidth
    const tileHeight = tileset.tileHeight
    const containerWidth = tilesetCanvasContainer.offsetWidth

    mappoState.map.mapLayerOrder.forEach(layerIndex => {
      const tileLayer = mappoState.map.tileLayers[layerIndex]
      if (!tileLayer.isHidden) {
        renderLayer({
          tileset,
          layer: tileLayer,
          x: mappoState.camera.x * tileLayer.parallax.x,
          y: mappoState.camera.y * tileLayer.parallax.y,
          transparent: layerIndex > 0,
        })
      }
    })

    if (mapLayerTileHighlightCoord) {
      renderTileHighlightInvertedOutline({
        context,
        x: mapLayerTileHighlightCoord.x,
        y: mapLayerTileHighlightCoord.y,
        width: tileWidth,
        height: tileHeight,
      })
    }

    renderTileset({
      context: tilesetContext,
      tileset: mappoState.tileset,
      tilesetColumns: getTilesetColumns({tileset, containerWidth})
    })

    if (mappoState.tilesetTileHovering) {
      renderTileHighlightInvertedSolid({
        context: tilesetContext,
        x: mappoState.tilesetTileHovering.tileX * tileWidth,
        y: mappoState.tilesetTileHovering.tileY * tileHeight,
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetHoveringTileContext,
        tileset: mappoState.tileset,
        tileIndex: mappoState.tilesetTileHovering.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetHoveringTileIndex.innerText = mappoState.tilesetTileHovering.tileIndex
    }

    if (mappoState.tilesetTileSelected) {
      renderTileHighlightColorOutline({
        context: tilesetContext,
        x: mappoState.tilesetTileSelected.tileX * tileWidth,
        y: mappoState.tilesetTileSelected.tileY * tileHeight,
        color: 'white',
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetSelectedTileContext,
        tileset: mappoState.tileset,
        tileIndex: mappoState.tilesetTileSelected.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetSelectedTileIndex.innerText = mappoState.tilesetTileSelected.tileIndex
    }

    mappoState.camera.move = {x: 0, y: 0}
    const autoScroll = mappoState.autoScroll
    if (autoScroll) {
      (keyboard.isPressed(keyboard.KEYCODE_UP) || autoScroll.x < 0) && (mappoState.camera.move.y = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_DOWN) || autoScroll.y > 0) && (mappoState.camera.move.y = +mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_LEFT) || autoScroll.x < 0) && (mappoState.camera.move.x = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_RIGHT) || autoScroll.x > 0) && (mappoState.camera.move.x = +mappoState.cameraScrollAmount);
      moveCamera(mappoState.camera.move.x, mappoState.camera.move.y)
    }

    // map zooming
    if (keyboard.isPressed('ctrlKey')) {
      const prevScaleIndex = mappoState.scaleIndex
      if (keyboard.isPressed(keyboard.KEYCODE_PLUS)) {
        keyboard.release(keyboard.KEYCODE_PLUS)
        mappoState.scaleIndex++
      }
      if (keyboard.isPressed(keyboard.KEYCODE_MINUS)) {
        keyboard.release(keyboard.KEYCODE_MINUS)
        mappoState.scaleIndex--
      }
      if (keyboard.isPressed(keyboard.KEYCODE_0)) {
        keyboard.release(keyboard.KEYCODE_0)
        mappoState.scaleIndex = DEFAULT_SCALE_INDEX
      }
      mappoState.scaleIndex = clamp(mappoState.scaleIndex, 0, viewportScales.length - 1)
      // TODO(chuck): redux-ify?
      if (prevScaleIndex !== mappoState.scaleIndex) {
        resizeCanvas()
      }
    }
  }

  window.requestAnimationFrame(tick)
}

const resizeCanvas = () => {
  canvas.width = ~~((middlePanel.offsetWidth + (getScale() - 1)) / getScale())
  canvas.height = ~~((middlePanel.offsetHeight + (getScale() - 1)) / getScale())
  canvas.style.width = middlePanel.offsetWidth + 'px'
  canvas.style.height = middlePanel.offsetHeight + 'px'

  if (!mappoState.isLoading) {
    const containerWidth = tilesetCanvasContainer.offsetWidth
    const tileset = mappoState.tileset
    tilesetCanvas.width = getTilesetColumns({tileset, containerWidth}) * tileset.tileWidth
    tilesetCanvas.height = getTilesetRows({tileset, containerWidth}) * tileset.tileHeight
    tilesetCanvas.style.width = (tilesetCanvas.width * getScale()) + 'px'
    tilesetCanvas.style.height = (tilesetCanvas.height * getScale()) + 'px'
  }
}

window.addEventListener('resize', resizeCanvas)
resizeCanvas()

tick()

<commit_msg>rename loadMap -> loadMappoMap<commit_after>"use strict"

const asset = require('./asset')
const colorDepth = require('./converter/colorDepth')
const clamp = require('lodash/clamp')
const glob = require('glob')
const createMappoSession = require('./createMappoSession')
const createMappoMap = require('./createMappoMap')
const createMappoTileset = require('./createMappoTileset')
const detectFormat = require('./detectFormat')
const path = require('path')
const fs = require('fs')
const setupKeyboard = require('./setupKeyboard')
const renderTileHighlightInvertedOutline = require('./renderTileHighlightInvertedOutline')
const renderTileHighlightInvertedSolid = require('./renderTileHighlightInvertedSolid')
const renderTileHighlightColorOutline = require('./renderTileHighlightColorOutline')
const renderTile = require('./renderTile')
const renderLayer = require('./renderLayer')
const renderTileset = require('./renderTileset')
const createCheckerboardPattern = require('./createCheckerboardPattern')
const calcAutoScroll = require('./calcAutoScroll')
const clearCanvas = require('./clearCanvas')

// DOM REFERENCES
const pageTitle = document.querySelector('title')
const mapList = document.querySelector('.map-list')
const layerList = document.querySelector('.layer-list')
const canvas = document.querySelector('.mappo-viewport')
const context = canvas.getContext('2d')
const tilesetCanvasContainer = document.querySelector('.tileset-canvas-container')
const tilesetCanvas = document.querySelector('.tileset-canvas')
const tilesetContext = tilesetCanvas.getContext('2d')
const tilesetSelectedTileCanvas = document.querySelector('.tileset-selected-tile-canvas')
const tilesetSelectedTileContext = tilesetSelectedTileCanvas.getContext('2d')
const tilesetSelectedTileIndex = document.querySelector('.tileset-selected-tile-index')
const tilesetHoveringTileCanvas = document.querySelector('.tileset-hovering-tile-canvas')
const tilesetHoveringTileContext = tilesetHoveringTileCanvas.getContext('2d')
const tilesetHoveringTileIndex = document.querySelector('.tileset-hovering-tile-index')
const middlePanel = document.querySelector('.middle-panel')

const checkerboardPattern = createCheckerboardPattern({document})

const launchFolder = 'data' // TODO(chuck): temp hack for windows. empty string dunna work
console.log('launchFolder', launchFolder)
const mapGlob = '**/*.map'
const mapFilenames = glob.sync(mapGlob, {nocase: true})
const mappoSession = createMappoSession({
  fileSystem: {
    files: mapFilenames
  },
  launchFolder
})

const viewportScales = [0.25, 0.5, 0.75, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const DEFAULT_SCALE_INDEX = 4
const defaultMappoState = {
  scaleIndex: DEFAULT_SCALE_INDEX,
  isLoading: true,
  map: null,
  mapLayerOrder: null,
  mapLayerSelected: null,
  tileset: null,
  tilesetTileHovering: null,
  tilesetTileSelected: null,
  camera: {
    x: 0,
    y: 0,
    move: {
      x: 0,
      y: 0,
    },
  },
  cameraScrollAmount: 1,
  keyPressed: {},
  autoScroll: {},
}
let mappoState = Object.assign({}, defaultMappoState)

const getScale = () => viewportScales[mappoState.scaleIndex]

mappoSession.getMapFilenames().forEach(mapFilename => {
  const li = document.createElement('li')
  li.setAttribute('title', mapFilename)
  li.innerText = mapFilename
  // TODO(chuck): temp hack for windows. figure out better launchFolder shenanigans
  li.addEventListener('click', event => {
    loadMappoMap('data/' + mapFilename)
    pageTitle.innerText = 'Mappo - ' + mapFilename
  })
  mapList.appendChild(li)
})

const refreshMapLayerList = () => {
  layerList.innerHTML = ''
  mappoState.map.tileLayers.forEach((layer, index) => {
    const li = document.createElement('li')
    li.setAttribute('title', layer.description)
    li.innerText = layer.description
    li.classList.add('layer-list-item')
    if (mappoState.mapLayerSelected === layer) {
      li.classList.add('selected')
    }
    li.addEventListener('click', event => {
      // TODO(chuck): use a proper element?
      if (event.offsetX < 35) {
        li.classList.toggle('is-layer-hidden')
        layer.isHidden = !layer.isHidden
      }
      mappoState.mapLayerSelected = layer
      const layerListItems = document.querySelectorAll('.layer-list .layer-list-item')
      layerListItems.forEach(layerListItem => {
        layerListItem.classList.remove('selected')
      })
      li.classList.add('selected')
    })
    layerList.appendChild(li)
  })
}

const loadMappoTileset = ({
  context,
  mapFilename,
  map,
}) => {
  const vspFilename = path.join(path.dirname(mapFilename), map.tilesetFilename)
  const vspBuffer = fs.readFileSync(vspFilename)
  const vspFormat = detectFormat(vspBuffer)
  console.log('vspFormat', vspFormat)

  let vspData
  try {
    vspData = asset.fromBuffer(vspBuffer, asset[vspFormat])
  } catch (exception) {
    console.log(exception)
    return
  }

  console.log(vspFilename, vspData)

  const tileset = createMappoTileset({context, tileset: vspData})

  return tileset
}

const loadMappoMap = mapFilename => {
  console.group()
  try {
    mappoState = Object.assign({}, defaultMappoState)
    mappoState.isLoading = true

    const mapBuffer = fs.readFileSync(mapFilename)
    const mapFormat = detectFormat(mapBuffer)
    if (!mapFormat.includes('map')) {
      console.error('expected map but got', mapFormat)
      return
    }
    console.log('mapFormat', mapFormat)
    let mapData
    try {
      mapData = asset.fromBuffer(mapBuffer, asset[mapFormat])
    } catch (exception) {
      console.log(exception)
      return
    }
    mappoState.map = createMappoMap({map: mapData})
    console.log(mapFilename, mapData)
    mappoState.mapLayerSelected = mappoState.map.tileLayers[mappoState.map.mapLayerOrder[0]]
    refreshMapLayerList()

    mappoState.tileset = loadMappoTileset({context, mapFilename, map: mappoState.map})
    tilesetSelectedTileCanvas.width = mappoState.tileset.tileWidth
    tilesetSelectedTileCanvas.height = mappoState.tileset.tileHeight
    tilesetHoveringTileCanvas.width = mappoState.tileset.tileWidth
    tilesetHoveringTileCanvas.height = mappoState.tileset.tileHeight

    mappoState.tileset.imageBitmapPromise.then(() => {
      mappoState.isLoading = false
      resizeCanvas()
    })
  } catch (exception) {
    console.error('ack!', exception)
  } finally {
    console.groupEnd()
  }
}

const keyboard = setupKeyboard({
  addEventListener: document.addEventListener,
  keyPressed: mappoState.keyPressed,
})

let mousedown = false
let mousein = false
middlePanel.addEventListener('mousedown', event => {
  mousedown = true
})

let mapLayerTileHighlightCoord = null
middlePanel.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  const scale = getScale()
  if (mousedown) {
    moveCamera(
      -event.movementX / scale,
      -event.movementY / scale
    )
  }

  mappoState.autoScroll = {}
  if (mousein) {
    mappoState.autoScroll = calcAutoScroll({
      scale,
      threshold: (mappoState.tileset.tileWidth + mappoState.tileset.tileHeight) / 2,
      cursorX: event.offsetX,
      cursorY: event.offsetY,
      viewportWidth: middlePanel.offsetWidth,
      viewportHeight: middlePanel.offsetHeight,
    })
  }

  const mapLayerSelected = mappoState.mapLayerSelected
  if (mapLayerSelected) {
    const tileWidth = mappoState.tileset.tileWidth
    const tileHeight = mappoState.tileset.tileHeight
    const scaleX = ~~(event.offsetX / scale)
    const scaleY = ~~(event.offsetY / scale)
    const parallaxX = ~~(mappoState.camera.x * mapLayerSelected.parallax.x)
    const parallaxY = ~~(mappoState.camera.y * mapLayerSelected.parallax.y)
    const x = scaleX - ((parallaxX + scaleX) % tileWidth)
    const y = scaleY - ((parallaxY + scaleY) % tileHeight)

    mapLayerTileHighlightCoord = {x, y}
  }
})

const getTileCoordAndIndex = ({
  tileset,
  containerWidth,
  pixelX,
  pixelY,
}) => {
  const scale = getScale()
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const tileX = ~~(pixelX / (tileset.tileWidth * scale))
  const tileY = ~~(pixelY / (tileset.tileHeight * scale))
  const tileIndex = (tileY * tilesetColumns) + tileX

  return {
    tileX,
    tileY,
    tileIndex,
  }
}

tilesetCanvasContainer.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.tilesetTileHovering = getTileCoordAndIndex({
    tileset: mappoState.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

tilesetCanvasContainer.addEventListener('click', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.tilesetTileSelected = getTileCoordAndIndex({
    tileset: mappoState.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

middlePanel.addEventListener('mouseup', event => {
  mousedown = false
})

middlePanel.addEventListener('mouseenter', event => {
  mousein = true
})

middlePanel.addEventListener('mouseout', event => {
  mousein = false
  mappoState.camera.move = {x: 0, y: 0}
  mappoState.autoScroll = {}
  mapLayerTileHighlightCoord = null
})

const moveCamera = (moveX, moveY) => {
  mappoState.camera.x = clamp(mappoState.camera.x + moveX, 0, (mappoState.map.tileLayers[0].width * mappoState.tileset.tileWidth) - canvas.width)
  mappoState.camera.y = clamp(mappoState.camera.y + moveY, 0, (mappoState.map.tileLayers[0].height * mappoState.tileset.tileHeight) - canvas.height)
}

const getTilesetColumns = ({tileset, containerWidth}) => {
  const scaledTileWidth = tileset.tileWidth * getScale()
  return ~~(containerWidth / scaledTileWidth)
}

const getTilesetRows = ({tileset, containerWidth}) => {
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const roundedUpRows = ~~((tileset.numTiles + (tilesetColumns - 1)) / tilesetColumns)
  return roundedUpRows
}

const tick = () => {
  clearCanvas({canvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetCanvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetSelectedTileCanvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetHoveringTileCanvas, pattern: checkerboardPattern})

  if (!mappoState.isLoading) {
    const tileset = mappoState.tileset
    const tileWidth = tileset.tileWidth
    const tileHeight = tileset.tileHeight
    const containerWidth = tilesetCanvasContainer.offsetWidth

    mappoState.map.mapLayerOrder.forEach(layerIndex => {
      const tileLayer = mappoState.map.tileLayers[layerIndex]
      if (!tileLayer.isHidden) {
        renderLayer({
          tileset,
          layer: tileLayer,
          x: mappoState.camera.x * tileLayer.parallax.x,
          y: mappoState.camera.y * tileLayer.parallax.y,
          transparent: layerIndex > 0,
        })
      }
    })

    if (mapLayerTileHighlightCoord) {
      renderTileHighlightInvertedOutline({
        context,
        x: mapLayerTileHighlightCoord.x,
        y: mapLayerTileHighlightCoord.y,
        width: tileWidth,
        height: tileHeight,
      })
    }

    renderTileset({
      context: tilesetContext,
      tileset: mappoState.tileset,
      tilesetColumns: getTilesetColumns({tileset, containerWidth})
    })

    if (mappoState.tilesetTileHovering) {
      renderTileHighlightInvertedSolid({
        context: tilesetContext,
        x: mappoState.tilesetTileHovering.tileX * tileWidth,
        y: mappoState.tilesetTileHovering.tileY * tileHeight,
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetHoveringTileContext,
        tileset: mappoState.tileset,
        tileIndex: mappoState.tilesetTileHovering.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetHoveringTileIndex.innerText = mappoState.tilesetTileHovering.tileIndex
    }

    if (mappoState.tilesetTileSelected) {
      renderTileHighlightColorOutline({
        context: tilesetContext,
        x: mappoState.tilesetTileSelected.tileX * tileWidth,
        y: mappoState.tilesetTileSelected.tileY * tileHeight,
        color: 'white',
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetSelectedTileContext,
        tileset: mappoState.tileset,
        tileIndex: mappoState.tilesetTileSelected.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetSelectedTileIndex.innerText = mappoState.tilesetTileSelected.tileIndex
    }

    mappoState.camera.move = {x: 0, y: 0}
    const autoScroll = mappoState.autoScroll
    if (autoScroll) {
      (keyboard.isPressed(keyboard.KEYCODE_UP) || autoScroll.x < 0) && (mappoState.camera.move.y = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_DOWN) || autoScroll.y > 0) && (mappoState.camera.move.y = +mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_LEFT) || autoScroll.x < 0) && (mappoState.camera.move.x = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_RIGHT) || autoScroll.x > 0) && (mappoState.camera.move.x = +mappoState.cameraScrollAmount);
      moveCamera(mappoState.camera.move.x, mappoState.camera.move.y)
    }

    // map zooming
    if (keyboard.isPressed('ctrlKey')) {
      const prevScaleIndex = mappoState.scaleIndex
      if (keyboard.isPressed(keyboard.KEYCODE_PLUS)) {
        keyboard.release(keyboard.KEYCODE_PLUS)
        mappoState.scaleIndex++
      }
      if (keyboard.isPressed(keyboard.KEYCODE_MINUS)) {
        keyboard.release(keyboard.KEYCODE_MINUS)
        mappoState.scaleIndex--
      }
      if (keyboard.isPressed(keyboard.KEYCODE_0)) {
        keyboard.release(keyboard.KEYCODE_0)
        mappoState.scaleIndex = DEFAULT_SCALE_INDEX
      }
      mappoState.scaleIndex = clamp(mappoState.scaleIndex, 0, viewportScales.length - 1)
      // TODO(chuck): redux-ify?
      if (prevScaleIndex !== mappoState.scaleIndex) {
        resizeCanvas()
      }
    }
  }

  window.requestAnimationFrame(tick)
}

const resizeCanvas = () => {
  canvas.width = ~~((middlePanel.offsetWidth + (getScale() - 1)) / getScale())
  canvas.height = ~~((middlePanel.offsetHeight + (getScale() - 1)) / getScale())
  canvas.style.width = middlePanel.offsetWidth + 'px'
  canvas.style.height = middlePanel.offsetHeight + 'px'

  if (!mappoState.isLoading) {
    const containerWidth = tilesetCanvasContainer.offsetWidth
    const tileset = mappoState.tileset
    tilesetCanvas.width = getTilesetColumns({tileset, containerWidth}) * tileset.tileWidth
    tilesetCanvas.height = getTilesetRows({tileset, containerWidth}) * tileset.tileHeight
    tilesetCanvas.style.width = (tilesetCanvas.width * getScale()) + 'px'
    tilesetCanvas.style.height = (tilesetCanvas.height * getScale()) + 'px'
  }
}

window.addEventListener('resize', resizeCanvas)
resizeCanvas()

tick()

<|endoftext|>


<commit_before>import { isStr, empty, pipe } from './utils'
import { prefixSpec } from './spec'

function findPath(specs, pathKey) {
  let result
  specs.find(spec => !!(result = spec.find(pathKey)))
  return result
}

function addPrefix(prefix, path) {
  return isStr(prefix) && path.indexOf(prefix) !== 0 ? `${prefix}${path}` : path
}

function linkFromPathKey(specs, prefix, pathKey, values = [], usePrefix = true) {
  let link, path = findPath(specs, pathKey)
  if (usePrefix && path) link = addPrefix(prefix, path.makeLink(values))
  return link || ''
}

function matcher(specs, checks, msg) {
  let spec, result, { pathname } = msg
  spec = specs.find(spec => !!(result = spec.match(checks, pathname)))
  let success = spec && spec.success(result)
  result = Object.assign({}, msg, result)
  return { result, success, spec }
}

function process({ result, success, spec }) {
  if (spec) spec.resolve(result, success)
  return !!spec
}

function matchPrefix(matcher) {
  let { prefix, match, checks } = matcher, result = matcher
  if (!empty(prefix)) {
    let pspec = prefixSpec(prefix, match)
    result.match = pspec.match.bind(pspec, checks)
  }
  return result
}

export function match(specs, checks = {}, prefix, mapper) {
  if (!Array.isArray(specs)) specs = [].concat(specs)
  let match = pipe(mapper, matcher.bind(null, specs, checks))
  return matchPrefix({ match, process, prefix, specs, checks })
}
<commit_msg>rename mapper to entry, check if entry is fn<commit_after>import { isStr, empty, pipe } from './utils'
import { prefixSpec } from './spec'

function findPath(specs, pathKey) {
  let result
  specs.find(spec => !!(result = spec.find(pathKey)))
  return result
}

function addPrefix(prefix, path) {
  return isStr(prefix) && path.indexOf(prefix) !== 0 ? `${prefix}${path}` : path
}

function linkFromPathKey(specs, prefix, pathKey, values = [], usePrefix = true) {
  let link, path = findPath(specs, pathKey)
  if (usePrefix && path) link = addPrefix(prefix, path.makeLink(values))
  return link || ''
}

function matcher(specs, checks, msg) {
  let spec, result, { pathname } = msg
  spec = specs.find(spec => !!(result = spec.match(checks, pathname)))
  let success = spec && spec.success(result)
  result = Object.assign({}, msg, result)
  return { result, success, spec }
}

function process({ result, success, spec }) {
  if (spec) spec.resolve(result, success)
  return !!spec
}

function matchPrefix(matcher) {
  let { prefix, match, checks } = matcher, result = matcher
  if (!empty(prefix)) {
    let pspec = prefixSpec(prefix, match)
    result.match = pspec.match.bind(pspec, checks)
  }
  return result
}

function matchEntry(match, entry) {
  return isFn(entry) ? pipe(entry, match) : match
}

export function match(specs, checks = {}, prefix, entry) {
  if (!Array.isArray(specs)) specs = [].concat(specs)
  let match = matchEntry(matcher.bind(null, specs, checks), entry)
  return matchPrefix({ match, process, prefix, specs, checks })
}
<|endoftext|>


<commit_before>/** @license
 * RequireJS plugin for loading Markdown files and converting them into HTML.
 * Author: Miller Medeiros
 * Version: 0.1.0 (2012/02/07)
 * Released under the MIT license
 */

// NOTE :: if you don't need to load markdown files in production outside of
// the build, precompile them into modules and set
// `pragmasOnSave.excludeMdown=true`

define(
    [
//>>excludeStart('excludeMdown', pragmas.excludeMdown)
        'text',
        'markdownConverter'
//>>excludeEnd('excludeMdown')
    ],
    function (
//>>excludeStart('excludeMdown', pragmas.excludeMdown)
        text, markdownConverter
//>>excludeEnd('excludeMdown')
    ) {

//>>excludeStart('excludeMdown', pragmas.excludeMdown)
        var buildMap = {};
//>>excludeEnd('excludeMdown')

        //API
        return {

            load : function(name, req, onLoad, config) {
//>>excludeStart('excludeMdown', pragmas.excludeMdown)
                text.get(req.toUrl(name), function(data){
                    data = markdownConverter.makeHtml(data);
                    if (config.isBuild) {
                        buildMap[name] = data;
                        onLoad(data);
                    } else {
                        onLoad(data);
                    }
                });
            },

            //write method based on RequireJS official text plugin by James Burke
            //https://github.com/jrburke/requirejs/blob/master/text.js
            write : function(pluginName, moduleName, write){
                if(moduleName in buildMap){
                    var content = text.jsEscape(buildMap[moduleName]);
                    write('define("'+ pluginName +'!'+ moduleName +'", function(){ return "'+ content +'";});\n');
                }
//>>excludeEnd('excludeMdown')
            }

        };
});

<commit_msg>fix mdown plugin during build, use single quotes instead of double for string.<commit_after>/** @license
 * RequireJS plugin for loading Markdown files and converting them into HTML.
 * Author: Miller Medeiros
 * Version: 0.1.1 (2012/02/17)
 * Released under the MIT license
 */

// NOTE :: if you don't need to load markdown files in production outside of
// the build, precompile them into modules and set
// `pragmasOnSave.excludeMdown=true`

define(
    [
//>>excludeStart('excludeMdown', pragmas.excludeMdown)
        'text',
        'markdownConverter'
//>>excludeEnd('excludeMdown')
    ],
    function (
//>>excludeStart('excludeMdown', pragmas.excludeMdown)
        text, markdownConverter
//>>excludeEnd('excludeMdown')
    ) {

//>>excludeStart('excludeMdown', pragmas.excludeMdown)
        var buildMap = {};
//>>excludeEnd('excludeMdown')

        //API
        return {

            load : function(name, req, onLoad, config) {
//>>excludeStart('excludeMdown', pragmas.excludeMdown)
                text.get(req.toUrl(name), function(data){
                    data = markdownConverter.makeHtml(data);
                    if (config.isBuild) {
                        buildMap[name] = data;
                        onLoad(data);
                    } else {
                        onLoad(data);
                    }
                });
            },

            //write method based on RequireJS official text plugin by James Burke
            //https://github.com/jrburke/requirejs/blob/master/text.js
            write : function(pluginName, moduleName, write){
                if(moduleName in buildMap){
                    var content = text.jsEscape(buildMap[moduleName]);
                    write.asModule(pluginName + "!" + moduleName,
                                   "define(function () { return '" +
                                       content +
                                   "';});\n");
                }
//>>excludeEnd('excludeMdown')
            }

        };
});
<|endoftext|>


<commit_before>var Backbone = require('backbone');
var _ = require('underscore');

// Model
// ---------------

// Model is an extension of `Backbone.Model` with the addition of basic features. To use it, call
// `Model.extend()` as you would with Backbone.Model.

var Model = Backbone.Model.extend({

  // Default is empty. Override this to specify a list of attributes that will automatically get
  // converted to/from Models on `parse` and `toJSON` calls. Example config for a Book class (given
  // an additional Author and Publisher model classes):
  //
  //     modelAttributes: { author: Author, publisher: Publisher }
  //
  modelAttributes: {},

  // Extends `Backbone.Model` 's `get` with support for nested attributes. Attempts a deep get when
  // passed in an `attr` string that contains the `.` delimeter. Example:
  //
  //     var attr = model.get('firstLevel.secondLevel');
  //
  // The deep `get` calls will work so long as the attributes at each level is either an instance
  // of a `Lumbar.Model` or `Backbone.Model`, or if it's a plain object map whose keys match to the
  // corresponding attributes. Returns `undefined` if an attribute is not found at any level.
  get: function(attr) {
    var fields = attr.split('.');
    var result = this;
    for (var i = 0; result && i < fields.length; i++) {
      if (result instanceof Backbone.Model) {
        result = Model.__super__.get.call(result, fields[i]);
      } else {
        result = result[fields[i]];
      }
    }
    return result;
  },

  // Extends `Backbone.Model` 's `set` with support for deep sets, but only on single
  // key/value set calls, not {} set calls. The following would attempt a nested set call:
  //
  //     model.set('attr1.attr2', value);
  //
  // But the following wouldn't and would result in value getting set on model as `attr1.attr2`:
  //
  //     model.set({ 'attr1.attr2': value });
  //
  // The deep `set` calls will work so long as the key attributes at each level is either an
  // instance of a `Lumbar.Model` or `Backbone.Model`, or if it's a plain object map whose keys
  // match the corresponding key attributes. Triggers a `lumbar:invalid:set` event and returns
  // false for invalid key calls.
  set: function(key, val, options) {

    // Proxy back to Backbone if it's not a single key/val
    if (!key || _.isObject(key)) {
      return Model.__super__.set.apply(this, arguments);
    }

    options || (options = {});

    var fields = key.split('.');
    var result = this;
    for (var i = 0; result && i < fields.length; i++) {
      var isLast = (i === fields.length - 1);
      if (result instanceof Backbone.Model) {
        if (isLast) {
          BaseModel.__super__.set.call(result, fields[i], val, options);
        } else {
          result = result.get(fields[i]);
        }
      } else {
        if (isLast) {
          result[fields[i]] = val;
        } else {
          result = result[fields[i]];
        }
      }
    }

    // Trigger an error event if unable to evaluate nested key
    if (!result) {
      var error = 'Unable to do nested set for invalid key: ' + key;
      this.trigger('lumbar:invalid:set', this, error, _.extend(options, { setError: error });
      return false;
    }

    return this;
  },

  // Overrides `Backbone.Model` 's default `parse` . Expands attributes as Model instances for
  // attributes configured in `modelAttributes` .
  parse: function(resp) {
    _.each(this.modelAttributes, function(model, attr) {
      if (resp[attr]) {
        resp[attr] = new model(resp[attr], { parse: true });
      }
    });
    return resp;
  },

  // Extends `Backbone.Model` 's `toJSON` by also further calling toJSON on Model attributes as
  // specified in `modelAttributes`
  toJSON: function() {
    var json = Model.__super__.toJSON.apply(this, arguments);
    _.each(this.modelAttributes, function(model, attr) {
      if (this.get(attr)) {
        json[attr] = this.get(attr).toJSON();
      }
    }, this);
    return json;
  }

});

module.exports = Model;
<commit_msg>Fix spacing<commit_after>var Backbone = require('backbone');
var _ = require('underscore');

// Model
// ---------------

// Model is an extension of `Backbone.Model` with the addition of basic features. To use it, call
// `Model.extend()` as you would with Backbone.Model.

var Model = Backbone.Model.extend({

  // Default is empty. Override this to specify a list of attributes that will automatically get
  // converted to/from Models on `parse` and `toJSON` calls. Example config for a Book class (given
  // an additional Author and Publisher model classes):
  //
  //     modelAttributes: { author: Author, publisher: Publisher }
  //
  modelAttributes: {},

  // Extends `Backbone.Model` 's `get` with support for nested attributes. Attempts a deep get when
  // passed in an `attr` string that contains the `.` delimeter. Example:
  //
  //     var attr = model.get('firstLevel.secondLevel');
  //
  // The deep `get` calls will work so long as the attributes at each level is either an instance
  // of a `Lumbar.Model` or `Backbone.Model`, or if it's a plain object map whose keys match to the
  // corresponding attributes. Returns `undefined` if an attribute is not found at any level.
  get: function(attr) {
    var fields = attr.split('.');
    var result = this;
    for (var i = 0; result && i < fields.length; i++) {
      if (result instanceof Backbone.Model) {
        result = Model.__super__.get.call(result, fields[i]);
      } else {
        result = result[fields[i]];
      }
    }
    return result;
  },

  // Extends `Backbone.Model` 's `set` with support for deep sets, but only on single
  // key/value set calls, not {} set calls. The following would attempt a nested set call:
  //
  //     model.set('attr1.attr2', value);
  //
  // But the following wouldn't and would result in value getting set on model as `attr1.attr2` :
  //
  //     model.set({ 'attr1.attr2': value });
  //
  // The deep `set` calls will work so long as the key attributes at each level is either an
  // instance of a `Lumbar.Model` or `Backbone.Model`, or if it's a plain object map whose keys
  // match the corresponding key attributes. Triggers a `lumbar:invalid:set` event and returns
  // false for invalid key calls.
  set: function(key, val, options) {

    // Proxy back to Backbone if it's not a single key/val
    if (!key || _.isObject(key)) {
      return Model.__super__.set.apply(this, arguments);
    }

    options || (options = {});

    var fields = key.split('.');
    var result = this;
    for (var i = 0; result && i < fields.length; i++) {
      var isLast = (i === fields.length - 1);
      if (result instanceof Backbone.Model) {
        if (isLast) {
          BaseModel.__super__.set.call(result, fields[i], val, options);
        } else {
          result = result.get(fields[i]);
        }
      } else {
        if (isLast) {
          result[fields[i]] = val;
        } else {
          result = result[fields[i]];
        }
      }
    }

    // Trigger an error event if unable to evaluate nested key
    if (!result) {
      var error = 'Unable to do nested set for invalid key: ' + key;
      this.trigger('lumbar:invalid:set', this, error, _.extend(options, { setError: error });
      return false;
    }

    return this;
  },

  // Overrides `Backbone.Model` 's default `parse` . Expands attributes as Model instances for
  // attributes configured in `modelAttributes` .
  parse: function(resp) {
    _.each(this.modelAttributes, function(model, attr) {
      if (resp[attr]) {
        resp[attr] = new model(resp[attr], { parse: true });
      }
    });
    return resp;
  },

  // Extends `Backbone.Model` 's `toJSON` by also further calling toJSON on Model attributes as
  // specified in `modelAttributes`
  toJSON: function() {
    var json = Model.__super__.toJSON.apply(this, arguments);
    _.each(this.modelAttributes, function(model, attr) {
      if (this.get(attr)) {
        json[attr] = this.get(attr).toJSON();
      }
    }, this);
    return json;
  }

});

module.exports = Model;
<|endoftext|>


<commit_before>function centerWindow() {
    "use strict";
    var centeredWidth = $('.centered').width() / 2,
        centeredHeight = $('.centered').height() / 2;
    $('.centered').css({ 'margin-left' : -centeredWidth , 'margin-top' : -centeredHeight });
}

function requestData() {
    "use strict";
    var req = new XMLHttpRequest();
    req.open("GET", "/games", true);
    req.setRequestHeader("accept", "application/json");

    req.onload = function() {
        if (req.status >= 200 && req.status < 400){
            var gamesjson = req.responseText;
            var gamesData = JSON.parse(gamesjson);
            var gamesToHtml = "";
            var key, x = 0;

            for (key in gamesData.games) {
                gamesToHtml += "
";
                var gameurl = "/#" + gamesData.games[x].id;
                var threadurl = gamesData.games[x].url;
                gamesToHtml +=  '' + gamesData.games[x].name + "	";
                gamesToHtml += '' + threadurl + "	";
 //             gamesToHtml += "" + gamesData.games[x].start_date + "	";
                gamesToHtml += "
";
                x++;
            }

            document.getElementById("games").innerHTML = gamesToHtml;
        } else {
            console.log("Error. Status code " + req.status);
        }
    };

    req.send(null);
    locationHashChanged();
}

function locationHashChanged() {
    "use strict";
    if (window.location.hash != "") {
        var id = "" + window.location.hash;
        id = id.slice(1, id.length);
        showGame(id);
    }
}

function showCreateGame() {
    "use strict";
    var createGame = document.getElementById("creategame");
    document.getElementById("index").className += ' hidden';
    createGame.className = createGame.className.replace( /(?:^|\s)hidden(?!\S)/g , '' );

    centerWindow();
}

function hideCreateGame() {
    "use strict";
    document.getElementById("creategame").className += ' hidden';
    document.getElementById("index").className =
    document.getElementById("index").className.replace
      ( /(?:^|\s)hidden(?!\S)/g , '' );
    window.location.hash = "";
}

function showGame(id) {
//                  table :vote    ---> votes!!!
//                  (integer :day :not-null) - äänestyspäivä
//                  (integer :index :not-null) - viestin numero #
//                  (varchar :voter 40 :not-null) - äänestäjä
//                  (varchar :target 40)))) - äänestyksen kohde
    "use strict";
    var req = new XMLHttpRequest();
    var address = "/game/" + id;
    req.open("GET", address, true);
    req.setRequestHeader("accept", "application/json");

    req.onload = function() {
        if (req.status >= 200 && req.status < 400){
            var gamejson = req.responseText;
            var gameData = JSON.parse(gamejson);
            var gameToHtml = "";

            document.getElementById("gameheader").innerHTML = gameData.game.name;

            gameToHtml += '' + gameData.game.url + "

";

            gameToHtml += "Hosts";

            for (var i = 0; i < gameData.hosts.length; i++) {
                gameToHtml += "
" + gameData.hosts[i] + "
";
            }

            gameToHtml += "
Players";

            for (var i = 0; i < gameData.players.length; i++) {
                gameToHtml += "
" + gameData.players[i].name + "
";
            }

            var firstNewDayVote = false;
            var currentDay = 0;

            for (var i = 0; i < gameData.votes.length; i++) {
                var post = gameData.votes[i].index;
                var page = Math.floor(post/20) + 1;
                var address = gameData.game.url + "?page=" + page + "#" + post;

                if (currentDay != gameData.votes[i].day) {
                    firstNewDayVote = true;
                    currentDay = gameData.votes[i].day;
                }
                if (firstNewDayVote === true) {
                    gameToHtml += "

Day " + gameData.votes[i].day + "

";
                    firstNewDayVote = false;
                }
                if (gameData.votes[i].target == "") {
                    gameToHtml += '
' + gameData.votes[i].voter + " unvoted
";
                } else if (gameData.votes[i].target == null) {
                    gameToHtml += '
' + gameData.votes[i].voter + " gave an invalid vote!
";
                } else {
                    gameToHtml += '
' + gameData.votes[i].voter +
                        " voted for " + gameData.votes[i].target + "
";
                }
            }

            if (gameData.votes.length === 0) {
                gameToHtml += "
No votes have been extracted from the given thread yet (this may take more time or there may have been an error while reading the thread)
";
            }

//                            [b]Bill Murray (8):[/b] Holyflare, Eden1892, rsoultin, Superbia, Vivax, Breshke, raynpelikoneet, Palmar
//                            [b]Vivax (7):[/b] [s]Holyflare[/s], Artanis[Xp], [s]Damdred[/s], LightningStrike, sicklucker, Toadesstern, Trfel, ExO_,                             [s]rsoultin[/s], [s]Palmar[/s], Damdred
//                            [b]LightningStrike (3):[/b] Bill Murray, [s]Toadesstern[/s], FecalFeast, [s]raynpelikoneet[/s], [s]Vivax[/s], Onegu
//                            [b]Toadesstern (1):[/b] [s]Palmar[/s], [s]Artanis[Xp][/s], [s]raynpelikoneet[/s], [s]Vivax[/s], VisceraEyes,         //                              [s]Palmar[/s]
//                            [b]sicklucker (1):[/b] [s]Eden1892[/s], [s]Holyflare[/s], [s]rsoultin[/s], [s]Alakaslam[/s], [s]Superbia[/s],         //                              Alakaslam, [s]Eden1892[/s], [s]Breshke[/s]
//                            [b]raynpelikoneet (0):[/b] [s]Holyflare[/s], [s]Damdred[/s], [s]rsoultin[/s], [s]Trfel[/s], [s]ExO_[/s], [s]Damdred[/s]
//                            [b]Artanis[Xp] (0):[/b] [s]Toadesstern[/s], [s]Eden1892[/s], [s]VisceraEyes[/s], [s]Eden1892[/s]
//                            [b]Eden1892 (0):[/b] [s]Fecalfeast[/s], [s]Onegu[/s], [s]Bill Murray[/s], [s]ExO[/s]
//                            [b]Damdred (0):[/b] [s]raynpelikoneet[/s], [s]sicklucker[/s], [s]Artanis[Xp][/s]
//                            [b]Superbia (0):[/b] [s]Eden1892[/s]
//                            [b]VisceraEyes (0):[/b] [s]Artanis[Xp][/s]
//                            [b]Alakaslam (0):[/b] [s]Holyflare[/s], [s]Artanis[/s], [s]sicklucker[/s], [s]Eden1892[/s]
//                            [b]ExO_ (0):[/b] [s]Eden1892[/s]
//                            [b]Palmar (0):[/b] [s]Toadesstern[/s], [s]Toadesstern[/s], [s]Bill Murray[/s]
//                            [b]Trfel (0):[/b] [s]Artanis[/s], [s]Superbia[/s], [s]Eden1892[/s]
//                            [b]Holyflare (0):[/b] [s]Alakaslam[/s]
//
//                            [b]Not Voting (0):[/b]

            document.getElementById("gamedata").innerHTML = gameToHtml;
        } else {
            console.log("Error. Status code " + req.status);
        }
    };

    req.send(null);
    document.getElementById("index").className += ' hidden';
    document.getElementById("gameview").className =
    document.getElementById("gameview").className.replace
      ( /(?:^|\s)hidden(?!\S)/g , '' );
}

function hideGame() {
    "use strict";
    document.getElementById("gameview").className += ' hidden';
    document.getElementById("index").className =
    document.getElementById("index").className.replace
      ( /(?:^|\s)hidden(?!\S)/g , '' );
    window.location.hash = "";
}

requestData();
<commit_msg>testing vote target list<commit_after>function centerWindow() {
    "use strict";
    var centeredWidth = $('.centered').width() / 2,
        centeredHeight = $('.centered').height() / 2;
    $('.centered').css({ 'margin-left' : -centeredWidth , 'margin-top' : -centeredHeight });
}

function requestData() {
    "use strict";
    var req = new XMLHttpRequest();
    req.open("GET", "/games", true);
    req.setRequestHeader("accept", "application/json");

    req.onload = function() {
        if (req.status >= 200 && req.status < 400){
            var gamesjson = req.responseText;
            var gamesData = JSON.parse(gamesjson);
            var gamesToHtml = "";
            var key, x = 0;

            for (key in gamesData.games) {
                gamesToHtml += "
";
                var gameurl = "/#" + gamesData.games[x].id;
                var threadurl = gamesData.games[x].url;
                gamesToHtml +=  '' + gamesData.games[x].name + "	";
                gamesToHtml += '' + threadurl + "	";
 //             gamesToHtml += "" + gamesData.games[x].start_date + "	";
                gamesToHtml += "
";
                x++;
            }

            document.getElementById("games").innerHTML = gamesToHtml;
        } else {
            console.log("Error. Status code " + req.status);
        }
    };

    req.send(null);
    locationHashChanged();
}

function locationHashChanged() {
    "use strict";
    if (window.location.hash != "") {
        var id = "" + window.location.hash;
        id = id.slice(1, id.length);
        showGame(id);
    }
}

function showCreateGame() {
    "use strict";
    var createGame = document.getElementById("creategame");
    document.getElementById("index").className += ' hidden';
    createGame.className = createGame.className.replace( /(?:^|\s)hidden(?!\S)/g , '' );

    centerWindow();
}

function hideCreateGame() {
    "use strict";
    document.getElementById("creategame").className += ' hidden';
    document.getElementById("index").className =
    document.getElementById("index").className.replace
      ( /(?:^|\s)hidden(?!\S)/g , '' );
    window.location.hash = "";
}

function showGame(id) {
//                  table :vote    ---> votes!!!
//                  (integer :day :not-null) - äänestyspäivä
//                  (integer :index :not-null) - viestin numero #
//                  (varchar :voter 40 :not-null) - äänestäjä
//                  (varchar :target 40)))) - äänestyksen kohde
    "use strict";
    var req = new XMLHttpRequest();
    var address = "/game/" + id;
    req.open("GET", address, true);
    req.setRequestHeader("accept", "application/json");

    req.onload = function() {
        if (req.status >= 200 && req.status < 400){
            var gamejson = req.responseText;
            var gameData = JSON.parse(gamejson);
            var gameToHtml = "";

            document.getElementById("gameheader").innerHTML = gameData.game.name;

            gameToHtml += '' + gameData.game.url + "

";

            gameToHtml += "Hosts";

            for (var i = 0; i < gameData.hosts.length; i++) {
                gameToHtml += "
" + gameData.hosts[i] + "
";
            }

            gameToHtml += "
Players";

            for (var i = 0; i < gameData.players.length; i++) {
                gameToHtml += "
" + gameData.players[i].name + "
";
            }

            var firstNewDayVote = false;
            var currentDay = 0;

            for (var i = 0; i < gameData.votes.length; i++) {
                var post = gameData.votes[i].index;
                var page = Math.floor(post/20) + 1;
                var address = gameData.game.url + "?page=" + page + "#" + post;

                if (currentDay != gameData.votes[i].day) {
                    firstNewDayVote = true;
                    currentDay = gameData.votes[i].day;
                }
                if (firstNewDayVote === true) {
                    gameToHtml += "

Day " + gameData.votes[i].day + "

";
                    firstNewDayVote = false;
                }
                if (gameData.votes[i].target == "") {
                    gameToHtml += '
' + gameData.votes[i].voter + " unvoted
";
                } else if (gameData.votes[i].target == null) {
                    gameToHtml += '
' + gameData.votes[i].voter + " gave an invalid vote!
";
                } else {
                    gameToHtml += '
' + gameData.votes[i].voter +
                        " voted for " + gameData.votes[i].target + "
";
                }
            }

            var targets = [];

            for (var i = 0; i < gameData.votes.length; i++) {
     //           if (currentDay == gameData.votes[i].day) {
                        if (!targets.contains(gameData.votes[i].target)) {
                            targets.push(gameData.votes[i].target);
                        }
         //             }
            }

            for (var i = 0; i < targets.length; i++) {
                        gameToHtml += targets[i];
            }

            if (gameData.votes.length === 0) {
                gameToHtml += "
No votes have been extracted from the given thread yet (this may take more time or there may have been an error while reading the thread)
";
            }

//                            [b]Bill Murray (8):[/b] Holyflare, Eden1892, rsoultin, Superbia, Vivax, Breshke, raynpelikoneet, Palmar
//                            [b]Vivax (7):[/b] [s]Holyflare[/s], Artanis[Xp], [s]Damdred[/s], LightningStrike, sicklucker, Toadesstern, Trfel, ExO_,                             [s]rsoultin[/s], [s]Palmar[/s], Damdred
//                            [b]LightningStrike (3):[/b] Bill Murray, [s]Toadesstern[/s], FecalFeast, [s]raynpelikoneet[/s], [s]Vivax[/s], Onegu
//                            [b]Toadesstern (1):[/b] [s]Palmar[/s], [s]Artanis[Xp][/s], [s]raynpelikoneet[/s], [s]Vivax[/s], VisceraEyes,         //                              [s]Palmar[/s]
//                            [b]sicklucker (1):[/b] [s]Eden1892[/s], [s]Holyflare[/s], [s]rsoultin[/s], [s]Alakaslam[/s], [s]Superbia[/s],         //                              Alakaslam, [s]Eden1892[/s], [s]Breshke[/s]
//                            [b]raynpelikoneet (0):[/b] [s]Holyflare[/s], [s]Damdred[/s], [s]rsoultin[/s], [s]Trfel[/s], [s]ExO_[/s], [s]Damdred[/s]
//                            [b]Artanis[Xp] (0):[/b] [s]Toadesstern[/s], [s]Eden1892[/s], [s]VisceraEyes[/s], [s]Eden1892[/s]
//                            [b]Eden1892 (0):[/b] [s]Fecalfeast[/s], [s]Onegu[/s], [s]Bill Murray[/s], [s]ExO[/s]
//                            [b]Damdred (0):[/b] [s]raynpelikoneet[/s], [s]sicklucker[/s], [s]Artanis[Xp][/s]
//                            [b]Superbia (0):[/b] [s]Eden1892[/s]
//                            [b]VisceraEyes (0):[/b] [s]Artanis[Xp][/s]
//                            [b]Alakaslam (0):[/b] [s]Holyflare[/s], [s]Artanis[/s], [s]sicklucker[/s], [s]Eden1892[/s]
//                            [b]ExO_ (0):[/b] [s]Eden1892[/s]
//                            [b]Palmar (0):[/b] [s]Toadesstern[/s], [s]Toadesstern[/s], [s]Bill Murray[/s]
//                            [b]Trfel (0):[/b] [s]Artanis[/s], [s]Superbia[/s], [s]Eden1892[/s]
//                            [b]Holyflare (0):[/b] [s]Alakaslam[/s]
//
//                            [b]Not Voting (0):[/b]

            document.getElementById("gamedata").innerHTML = gameToHtml;
        } else {
            console.log("Error. Status code " + req.status);
        }
    };

    req.send(null);
    document.getElementById("index").className += ' hidden';
    document.getElementById("gameview").className =
    document.getElementById("gameview").className.replace
      ( /(?:^|\s)hidden(?!\S)/g , '' );
}

function hideGame() {
    "use strict";
    document.getElementById("gameview").className += ' hidden';
    document.getElementById("index").className =
    document.getElementById("index").className.replace
      ( /(?:^|\s)hidden(?!\S)/g , '' );
    window.location.hash = "";
}

requestData();
<|endoftext|>