<commit_before>/*************************************************

  TODO
  ====
   * choice between grow down and grow left should be based on root ratio (force down if root.w > (root.h + h), force right if root.h > (root.w + w))
   * 3-way tree to grow more robustly ?
   * provide combo box with various canvas sizes (including "AUTO GROW")
   * display whitespace ratio
   * animation render 1 by 1

  OPTIMIZATIONS
  =============
   * mark branches as "full" to avoid walking them
   * dont bother with nodes that are less than some threshold w/h (2? 5?)

**************************************************/

Packer = function(w, h) {
  this.init(w, h);
};

Packer.prototype = {

  init: function(w, h) {
    this.root = { x: 0, y: 0, w: w, h: h }
  },

  fit: function(blocks) {
    var n, node, block;
    for (n = 0; n < blocks.length; n++) {
      block = blocks[n];
      if (node = this.findNode(this.root, block.w, block.h))
        block.fit = this.splitNode(node, block.w, block.h);
    }
  },

  findNode: function(root, w, h) {
    if (root.used)
      return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
    else if ((w <= root.w) && (h <= root.h))
      return root;
    else
      return null;
  },

  splitNode: function(node, w, h) {
    node.used = true;
    node.down  = { x: node.x,     y: node.y + h, w: node.w,     h: node.h - h };
    node.right = { x: node.x + w, y: node.y,     w: node.w - w, h: h          };
    return node;
  }

}

/*****************************************************************************/

GrowingPacker = function() { };

GrowingPacker.prototype = {

  fit: function(blocks) {
    var n, node, block, len = blocks.length, maxw = maxh = 0;
    for(n = 0 ; n < len ; n++) {
      maxw = Math.max(maxw, blocks[n].w);
      maxh = Math.max(maxh, blocks[n].h);
    }
    this.root = { x: 0, y: 0, w: maxw, h: maxh }; // this initial size, ensures that we can ALWAYS grow either down or right
    for (n = 0; n < len ; n++) {
      block = blocks[n];
      if (node = this.findNode(this.root, block.w, block.h))
        block.fit = this.splitNode(node, block.w, block.h);
      else
        block.fit = this.growNode(block.w, block.h);
    }
  },

  findNode: function(root, w, h) {
    if (root.used)
      return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
    else if ((w <= root.w) && (h <= root.h))
      return root;
    else
      return null;
  },

  splitNode: function(node, w, h) {
    node.used = true;
    node.down  = { x: node.x,     y: node.y + h, w: node.w,     h: node.h - h };
    node.right = { x: node.x + w, y: node.y,     w: node.w - w, h: h          };
    return node;
  },

  growNode: function(w, h) {
    var canGrowDown  = (w <= this.root.w);
    var canGrowRight = (h <= this.root.h);
    var growDown     = canGrowDown  && (w >= h);
    var growRight    = canGrowRight && (w <= h);

    if (growDown && growRight) {
      if (this.lastGrow == 'right') {
        growRight = false;
        this.lastGrow = 'down';
      }
      else {
        growDown = false;
        this.lastGrow = 'right';
      }
    }

    if (growDown) {
      this.root = {
        used: true,
        x: 0,
        y: 0,
        w: this.root.w,
        h: this.root.h + h,
        down:  { x: 0, y: this.root.h, w: this.root.w, h: h },
        right: this.root
      };
    }
    else if (growRight) {
      this.root = {
        used: true,
        x: 0,
        y: 0,
        w: this.root.w + w,
        h: this.root.h,
        down: this.root,
        right: { x: this.root.w, y: 0, w: w, h: this.root.h }
      };
    }

    if (node = this.findNode(this.root, w, h))
      return this.splitNode(node, w, h);

    return null;
  }

}

/*****************************************************************************/

Packing = {

  //---------------------------------------------------------------------------

  colors: [ "#10F090", "#1090F0", "#90F010", "#9010F0", "#F01090", "#F09010" ],
  color:  function(n) { return Packing.colors[n % Packing.colors.length]; },

  //---------------------------------------------------------------------------

  init: function() {

    Packing.el = {
      blocks: $('#blocks'),
      canvas: $('#canvas')[0],
      sort:   $('#sort'),
      fill:   $('#fill'),
      go:     $('#go'),
      nofit:  $('#nofit')
    };

    if (!Packing.el.canvas.getContext) // no support for canvas
      return false;

    Packing.el.draw = Packing.el.canvas.getContext("2d");
    Packing.el.blocks.val(Packing.blocks.save(Packing.blocks.default));
    Packing.el.blocks.change(Packing.run);
    Packing.el.sort.change(Packing.run);
    Packing.el.fill.change(Packing.run);
    Packing.el.go.click(Packing.run);
    Packing.run();
  },

  //---------------------------------------------------------------------------

  run: function() {

    var i, n, len, pos, all, block, nofit = [];
    var packer = new GrowingPacker();
//    var packer = new Packer(500, 500);
    var blocks = Packing.blocks.load(Packing.el.blocks.val());
    var all    = blocks.expanded;

    // sort
    var sort = Packing.el.sort.val();
    if (sort != 'none')
      all.sort(Packing.sort[sort]);

    // fit
    packer.fit(all);

    // draw
    Packing.canvas.reset(packer.root.w, packer.root.h);
    for (n = 0 ; n < all.length ; n++) {
      block = all[n];
      if (block.fit) {
        if (Packing.el.fill.is(':checked')) {
          Packing.canvas.rect(block.fit.x, block.fit.y, block.w, block.h, Packing.color(n));
        }
      }
      else {
        nofit.push(block)
      }
    }
    Packing.canvas.boundary(packer.root);
    Packing.showNoFit(nofit);
  },

  showNoFit: function(nofit) {
    if (nofit.length > 0) {
      for(n = 0 ; n < nofit.length ; n++)
        nofit[n] = "" + nofit[n].w + "x" + nofit[n].h;
      Packing.el.nofit.html("Did not fit (" + nofit.length + ") :
" + nofit.join(", ")).show();
    }
    else {
      Packing.el.nofit.hide();
    }
  },

  //---------------------------------------------------------------------------

  sort: {
    none    : function (a,b) { return 0; },
    random  : function (a,b) { return Math.random() - 0.5; },
    w       : function (a,b) { return b.w - a.w; },
    h       : function (a,b) { return b.h - a.h; },
    area    : function (a,b) { return (b.w * b.h) - (a.w * a.h) },
    max     : function (a,b) { return Math.max(b.w, b.h) - Math.max(a.w, a.h); },
    min     : function (a,b) { return Math.min(b.w, b.h) - Math.min(a.w, a.h); },
    width   : function (a,b) { var primary = Packing.sort.w(a,b);   return (primary != 0) ? primary : Packing.sort.h(a,b);   },
    height  : function (a,b) { var primary = Packing.sort.h(a,b);   return (primary != 0) ? primary : Packing.sort.w(a,b);   },
    maxside : function (a,b) { var primary = Packing.sort.max(a,b); return (primary != 0) ? primary : Packing.sort.min(a,b); }
  },

  //---------------------------------------------------------------------------

  canvas: {

    reset: function(width, height) {
      Packing.el.canvas.width  = width  + 1; // add 1 because we draw boundaries offset by 0.5 in order to pixel align and get crisp boundaries
      Packing.el.canvas.height = height + 1; // (ditto)
      Packing.el.draw.clearRect(0, 0, Packing.el.canvas.width, Packing.el.canvas.height);
    },

    rect:  function(x, y, w, h, color) {
      Packing.el.draw.fillStyle = color;
      Packing.el.draw.fillRect(x, y, w, h);
    },

    boundary: function(node) {
      if (node) {
        Packing.el.draw.strokeRect(node.x + 0.5, node.y + 0.5, node.w, node.h);
        Packing.canvas.boundary(node.down);
        Packing.canvas.boundary(node.right);
      }
    }
  },

  //---------------------------------------------------------------------------

  blocks: {

    default: [
      {w: 100, h: 100, num:   3},
      {w:  60, h:  60, num:  10},
      {w:  50, h:  20, num:  20},
      {w:  20, h:  50, num:  20},
      {w: 250, h: 250, num:   4},
      {w: 250, h: 100, num:   1},
      {w: 100, h: 250, num:   1},
//      {w: 500, h:  80, num:   1},
//      {w: 80,  h: 500, num:   1},
      {w:  10, h:  10, num: 100},
      {w:   5, h:   5, num: 500}
    ],

    load: function(val) {
      var i, j, block, blocks = val.split("\n"), result = [];
      for(i = 0 ; i < blocks.length ; i++) {
        block = blocks[i].split("x");
        if (block.length >= 2)
          result.push({w: parseInt(block[0]), h: parseInt(block[1]), num: (block.length == 2 ? 1 : parseInt(block[2])) });
      }
      result.expanded = [];
      for(i = 0 ; i < result.length ; i++) {
        for(j = 0 ; j < result[i].num ; j++)
          result.expanded.push({w: result[i].w, h: result[i].h});
      }
      return result;
    },

    save: function(blocks) {
      var i, block, str = "";
      for(i = 0; i < blocks.length ; i++) {
        block = blocks[i];
        str = str + block.w + "x" + block.h + (block.num > 1 ? "x" + block.num : "") + "\n";
      }
      return str;
    }

  }

  //---------------------------------------------------------------------------

}

$(Packing.init);

<commit_msg>trying to make smarter decision about growing right vs growing left....<commit_after>/*************************************************

  TODO
  ====
   * 3-way tree to grow more robustly ?
   * provide combo box with various canvas sizes (including "AUTO GROW")
   * display whitespace ratio
   * animation render 1 by 1

  OPTIMIZATIONS
  =============
   * mark branches as "full" to avoid walking them
   * dont bother with nodes that are less than some threshold w/h (2? 5?)

**************************************************/

Packer = function(w, h) {
  this.init(w, h);
};

Packer.prototype = {

  init: function(w, h) {
    this.root = { x: 0, y: 0, w: w, h: h }
  },

  fit: function(blocks) {
    var n, node, block;
    for (n = 0; n < blocks.length; n++) {
      block = blocks[n];
      if (node = this.findNode(this.root, block.w, block.h))
        block.fit = this.splitNode(node, block.w, block.h);
    }
  },

  findNode: function(root, w, h) {
    if (root.used)
      return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
    else if ((w <= root.w) && (h <= root.h))
      return root;
    else
      return null;
  },

  splitNode: function(node, w, h) {
    node.used = true;
    node.down  = { x: node.x,     y: node.y + h, w: node.w,     h: node.h - h };
    node.right = { x: node.x + w, y: node.y,     w: node.w - w, h: h          };
    return node;
  }

}

/*****************************************************************************/

GrowingPacker = function() { };

GrowingPacker.prototype = {

  fit: function(blocks) {
    var n, node, block, len = blocks.length, maxw = maxh = 0;
    for(n = 0 ; n < len ; n++) {
      maxw = Math.max(maxw, blocks[n].w);
      maxh = Math.max(maxh, blocks[n].h);
    }
    this.root = { x: 0, y: 0, w: maxw, h: maxh }; // this initial size, ensures that we can ALWAYS grow either down or right
    for (n = 0; n < len ; n++) {
      block = blocks[n];
      if (node = this.findNode(this.root, block.w, block.h))
        block.fit = this.splitNode(node, block.w, block.h);
      else
        block.fit = this.growNode(block.w, block.h);
    }
  },

  findNode: function(root, w, h) {
    if (root.used)
      return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
    else if ((w <= root.w) && (h <= root.h))
      return root;
    else
      return null;
  },

  splitNode: function(node, w, h) {
    node.used = true;
    node.down  = { x: node.x,     y: node.y + h, w: node.w,     h: node.h - h };
    node.right = { x: node.x + w, y: node.y,     w: node.w - w, h: h          };
    return node;
  },

  growNode: function(w, h) {
    if (this.root.h >= (this.root.w + w)) {
      return this.growRight(w, h);
    }
    else if (this.root.w >= (this.root.h + h)) {
      return this.growDown(w, h);
    }
    else {
      if (this.root.w > this.root.h)
        return this.growDown(w, h);
      else
        return this.growRight(w, h);
    }
  },

  growRight: function(w, h) {
    this.root = {
      used: true,
      x: 0,
      y: 0,
      w: this.root.w + w,
      h: this.root.h,
      down: this.root,
      right: { x: this.root.w, y: 0, w: w, h: this.root.h }
    };
    return this.splitNode(this.findNode(this.root, w, h), w, h);
  },

  growDown: function(w, h) {
    this.root = {
      used: true,
      x: 0,
      y: 0,
      w: this.root.w,
      h: this.root.h + h,
      down:  { x: 0, y: this.root.h, w: this.root.w, h: h },
      right: this.root
    };
    return this.splitNode(this.findNode(this.root, w, h), w, h);
  }

}

/*****************************************************************************/

Packing = {

  //---------------------------------------------------------------------------

  colors: [ "#10F090", "#1090F0", "#90F010", "#9010F0", "#F01090", "#F09010" ],
  color:  function(n) { return Packing.colors[n % Packing.colors.length]; },

  //---------------------------------------------------------------------------

  init: function() {

    Packing.el = {
      blocks: $('#blocks'),
      canvas: $('#canvas')[0],
      sort:   $('#sort'),
      fill:   $('#fill'),
      go:     $('#go'),
      nofit:  $('#nofit')
    };

    if (!Packing.el.canvas.getContext) // no support for canvas
      return false;

    Packing.el.draw = Packing.el.canvas.getContext("2d");
    Packing.el.blocks.val(Packing.blocks.save(Packing.blocks.default));
    Packing.el.blocks.change(Packing.run);
    Packing.el.sort.change(Packing.run);
    Packing.el.fill.change(Packing.run);
    Packing.el.go.click(Packing.run);
    Packing.run();
  },

  //---------------------------------------------------------------------------

  run: function() {

    var i, n, len, pos, all, block, nofit = [];
    var packer = new GrowingPacker();
//    var packer = new Packer(500, 500);
    var blocks = Packing.blocks.load(Packing.el.blocks.val());
    var all    = blocks.expanded;

    // sort
    var sort = Packing.el.sort.val();
    if (sort != 'none')
      all.sort(Packing.sort[sort]);

    // fit
    packer.fit(all);

    // draw
    Packing.canvas.reset(packer.root.w, packer.root.h);
    for (n = 0 ; n < all.length ; n++) {
      block = all[n];
      if (block.fit) {
        if (Packing.el.fill.is(':checked')) {
          Packing.canvas.rect(block.fit.x, block.fit.y, block.w, block.h, Packing.color(n));
        }
      }
      else {
        nofit.push(block)
      }
    }
    Packing.canvas.boundary(packer.root);
    Packing.showNoFit(nofit);
  },

  showNoFit: function(nofit) {
    if (nofit.length > 0) {
      for(n = 0 ; n < nofit.length ; n++)
        nofit[n] = "" + nofit[n].w + "x" + nofit[n].h;
      Packing.el.nofit.html("Did not fit (" + nofit.length + ") :
" + nofit.join(", ")).show();
    }
    else {
      Packing.el.nofit.hide();
    }
  },

  //---------------------------------------------------------------------------

  sort: {
    none    : function (a,b) { return 0; },
    random  : function (a,b) { return Math.random() - 0.5; },
    w       : function (a,b) { return b.w - a.w; },
    h       : function (a,b) { return b.h - a.h; },
    area    : function (a,b) { return (b.w * b.h) - (a.w * a.h) },
    max     : function (a,b) { return Math.max(b.w, b.h) - Math.max(a.w, a.h); },
    min     : function (a,b) { return Math.min(b.w, b.h) - Math.min(a.w, a.h); },
    width   : function (a,b) { var primary = Packing.sort.w(a,b);   return (primary != 0) ? primary : Packing.sort.h(a,b);   },
    height  : function (a,b) { var primary = Packing.sort.h(a,b);   return (primary != 0) ? primary : Packing.sort.w(a,b);   },
    maxside : function (a,b) { var primary = Packing.sort.max(a,b); return (primary != 0) ? primary : Packing.sort.min(a,b); }
  },

  //---------------------------------------------------------------------------

  canvas: {

    reset: function(width, height) {
      Packing.el.canvas.width  = width  + 1; // add 1 because we draw boundaries offset by 0.5 in order to pixel align and get crisp boundaries
      Packing.el.canvas.height = height + 1; // (ditto)
      Packing.el.draw.clearRect(0, 0, Packing.el.canvas.width, Packing.el.canvas.height);
    },

    rect:  function(x, y, w, h, color) {
      Packing.el.draw.fillStyle = color;
      Packing.el.draw.fillRect(x, y, w, h);
    },

    boundary: function(node) {
      if (node) {
        Packing.el.draw.strokeRect(node.x + 0.5, node.y + 0.5, node.w, node.h);
        Packing.canvas.boundary(node.down);
        Packing.canvas.boundary(node.right);
      }
    }
  },

  //---------------------------------------------------------------------------

  blocks: {

    default: [
      {w: 100, h: 100, num:   3},
      {w:  60, h:  60, num:  10},
      {w:  50, h:  20, num:  20},
      {w:  20, h:  50, num:  20},
      {w: 250, h: 250, num:   4},
      {w: 250, h: 100, num:   1},
      {w: 100, h: 250, num:   1},
      {w: 500, h:  80, num:   1},
      {w: 80,  h: 500, num:   1},
      {w:  10, h:  10, num: 100},
      {w:   5, h:   5, num: 500}
    ],

    load: function(val) {
      var i, j, block, blocks = val.split("\n"), result = [];
      for(i = 0 ; i < blocks.length ; i++) {
        block = blocks[i].split("x");
        if (block.length >= 2)
          result.push({w: parseInt(block[0]), h: parseInt(block[1]), num: (block.length == 2 ? 1 : parseInt(block[2])) });
      }
      result.expanded = [];
      for(i = 0 ; i < result.length ; i++) {
        for(j = 0 ; j < result[i].num ; j++)
          result.expanded.push({w: result[i].w, h: result[i].h});
      }
      return result;
    },

    save: function(blocks) {
      var i, block, str = "";
      for(i = 0; i < blocks.length ; i++) {
        block = blocks[i];
        str = str + block.w + "x" + block.h + (block.num > 1 ? "x" + block.num : "") + "\n";
      }
      return str;
    }

  }

  //---------------------------------------------------------------------------

}

$(Packing.init);

<|endoftext|>


<commit_before>var tilelive = require('tilelive');
var TileJSON = require('tilejson');
var url = require('url');
var assert = require('assert');
var path = require('path');
var fs = require('fs');
var crypto = require('crypto');
var Vector = require('..');

function md5(str) {
    return crypto.createHash('md5').update(str).digest('hex');
}

// Load fixture data.
var fixtureDir = path.resolve(__dirname + '/fixtures/tm2z'),
    remotePath = 'http://mapbox.s3.amazonaws.com/tilelive-vector/test-tm2z.tm2z',
    xml = fs.readFileSync(fixtureDir + '/project/project.xml');

// Register vector:, tm2z:, tm2z+http: and mapbox: tilelive protocols
Vector.registerProtocols(tilelive);
tilelive.protocols['mapbox:'] = function Source(uri, callback) {
    return new TileJSON('http://a.tiles.mapbox.com/v3' + uri.pathname + '.json', callback);
};

// Register font
Vector.mapnik.register_fonts(__dirname + '/fonts/source-sans-pro/');

describe('tm2z', function() {
    it('exposes the mapnik binding', function() {
        assert.ok(Vector.mapnik);
    });
    it('loads a tm2z url', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/project.tm2z', function(err, source) {
            assert.ifError(err);
            done();
        });
    });
    it('matches expected xml', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/project.tm2z', function(err, source) {
            assert.ifError(err);
            assert.equal(source._xml, xml);
            done();
        });
    });
    it('gunzips then untars', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/project.tar.gz', function(err, source) {
            assert.ifError(err);
            done();
        });
    });
    it('errors out if not gzipped', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/project.tar', function(err, source) {
            assert.equal(err.code, 'Z_DATA_ERROR');
            assert.equal(err.message, 'incorrect header check');
            done();
        });
    });
    it('errors out on bad gunzip', function(done) {
       tilelive.load('tm2z://' + fixtureDir + '/doublezip.tm2z', function(err, source) {
            assert.equal(err.message, 'invalid tar file');
            done();
        });
    });
    it('errors out if file size exceeds max size', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/filesize.tm2z', function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Upload size should not exceed 750KB.');
            done();
        });
    });
    it('errors out if file size exceeds custom max size', function(done) {
        tilelive.load({
            protocol: 'tm2z:',
            pathname: fixtureDir + '/filesize.tm2z',
            filesize: 500 * 1024
        }, function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Upload size should not exceed 500KB.');
            done();
        });
    });
    it('errors out if unzipped size exceeds max size', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/gunzipsize.tm2z', function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Unzipped size should not exceed 5MB.');
            done();
        });
    });
    it('errors out if unzipped size exceeds custom max size', function(done) {
        tilelive.load({
            protocol: 'tm2z:',
            pathname: fixtureDir + '/gunzipsize.tm2z',
            gunzipsize: 1024 * 1024
        }, function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Unzipped size should not exceed 1MB.');
            done();
        });
    });
    it('errors out if unzipped project.xml size exceeds max size', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/xmlsize.tm2z', function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Unzipped project.xml size should not exceed 750KB.');
            done();
        });
    });
    it('errors out if unzipped project.xml size exceeds custom max size', function(done) {
        tilelive.load({
            protocol: 'tm2z:',
            pathname: fixtureDir + '/xmlsize.tm2z',
            xmlsize: 300 * 1024
        }, function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Unzipped project.xml size should not exceed 300KB.');
            done();
        });
    });
    it('errors out if not a directory', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/nodirectory.tm2z', function(err, source) {
            assert.equal(err.message.split(',')[0], 'EISDIR');
            done();
        });
    });
    it('errors out if missing project.xml', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/empty.tm2z', function(err, source) {
            assert.equal(err.message, 'project.xml not found in package');
            done();
        });
    });
    it('errors out on invalid project.xml', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/malformed.tm2z', function(err, source) {
            assert.equal(err.message.split(':')[0], 'XML document not well formed');
            done();
        });
    });
    it('errors out if style references a missing font', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/missing_font.tm2z', function(err, source) {
            assert.equal('EMAPNIK', err.code);
            assert.equal(err.message.split("'")[0], 'Failed to find font face ');
            done();
        });
    });
    it('does not error out if style references a registered font', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/project.tm2z', function(err, source) {
            assert.ifError(err);
            done();
        });
    });
    it('errors out if style references a missing image', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/missing_image.tm2z', function(err, source) {
            assert.equal('EMAPNIK', err.code);
            assert.equal(err.message.split(':')[0], 'file could not be found');
            done();
        });
    });
    it('profiles a tm2z file', function(done) {
        this.timeout(0);
        tilelive.load('tm2z://' + fixtureDir + '/project.tm2z', function(err, source) {
            assert.ifError(err);
            source.profile(function(err, profile) {
                assert.ifError(err);
                assert.equal('number', typeof profile.xmltime);
                assert.deepEqual(['avg','min','max'], Object.keys(profile.drawtime));
                assert.deepEqual(['avg','min','max'], Object.keys(profile.loadtime));
                assert.deepEqual(['avg','min','max'], Object.keys(profile.srcbytes));
                assert.deepEqual(['avg','min','max'], Object.keys(profile.imgbytes));
                assert.equal(23, profile.tiles.length);
                assert.deepEqual([
                    '0/0/0',
                    '1/1/0',
                    '2/2/1',
                    '3/4/3',
                    '4/9/7',
                    '5/19/14',
                    '6/39/29',
                    '7/79/58',
                    '8/159/117',
                    '9/319/235',
                    '10/638/470',
                    '11/1276/940',
                    '12/2553/1880',
                    '13/5106/3761',
                    '14/10212/7522',
                    '15/20424/15045',
                    '16/40849/30091',
                    '17/81699/60182',
                    '18/163398/120364',
                    '19/326797/240728',
                    '20/653594/481456',
                    '21/1307188/962913',
                    '22/2614376/1925826'
                ], profile.tiles.map(function(t) { return t.z + '/' + t.x + '/' + t.y }));
                done();
            });
        });
    });
});

describe('tm2z+http', function() {
    it('loads a tm2z+http url', function(done) {
        this.timeout(5000);
        tilelive.load('tm2z+' + remotePath, function(err, source) {
            assert.ifError(err);
            done();
        });
    });
    it('matches expected xml', function(done) {
        this.timeout(5000);
        tilelive.load('tm2z+' + remotePath, function(err, source) {
            assert.ifError(err);
            assert.equal(xml, source._xml);
            done();
        });
    });
    it('errors out on an invalid S3 url', function(done) {
        tilelive.load('tm2z+http://mapbox.s3.amazonaws.com/tilelive-vector/invalid.tm2z', function(err, source) {
            assert.equal('Z_DATA_ERROR', err.code);
            done();
        });
    });
});
<commit_msg>Add assert for profile obj keys and remove redundant assert.<commit_after>var tilelive = require('tilelive');
var TileJSON = require('tilejson');
var url = require('url');
var assert = require('assert');
var path = require('path');
var fs = require('fs');
var crypto = require('crypto');
var Vector = require('..');

function md5(str) {
    return crypto.createHash('md5').update(str).digest('hex');
}

// Load fixture data.
var fixtureDir = path.resolve(__dirname + '/fixtures/tm2z'),
    remotePath = 'http://mapbox.s3.amazonaws.com/tilelive-vector/test-tm2z.tm2z',
    xml = fs.readFileSync(fixtureDir + '/project/project.xml');

// Register vector:, tm2z:, tm2z+http: and mapbox: tilelive protocols
Vector.registerProtocols(tilelive);
tilelive.protocols['mapbox:'] = function Source(uri, callback) {
    return new TileJSON('http://a.tiles.mapbox.com/v3' + uri.pathname + '.json', callback);
};

// Register font
Vector.mapnik.register_fonts(__dirname + '/fonts/source-sans-pro/');

describe('tm2z', function() {
    it('exposes the mapnik binding', function() {
        assert.ok(Vector.mapnik);
    });
    it('loads a tm2z url', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/project.tm2z', function(err, source) {
            assert.ifError(err);
            done();
        });
    });
    it('matches expected xml', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/project.tm2z', function(err, source) {
            assert.ifError(err);
            assert.equal(source._xml, xml);
            done();
        });
    });
    it('gunzips then untars', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/project.tar.gz', function(err, source) {
            assert.ifError(err);
            done();
        });
    });
    it('errors out if not gzipped', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/project.tar', function(err, source) {
            assert.equal(err.code, 'Z_DATA_ERROR');
            assert.equal(err.message, 'incorrect header check');
            done();
        });
    });
    it('errors out on bad gunzip', function(done) {
       tilelive.load('tm2z://' + fixtureDir + '/doublezip.tm2z', function(err, source) {
            assert.equal(err.message, 'invalid tar file');
            done();
        });
    });
    it('errors out if file size exceeds max size', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/filesize.tm2z', function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Upload size should not exceed 750KB.');
            done();
        });
    });
    it('errors out if file size exceeds custom max size', function(done) {
        tilelive.load({
            protocol: 'tm2z:',
            pathname: fixtureDir + '/filesize.tm2z',
            filesize: 500 * 1024
        }, function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Upload size should not exceed 500KB.');
            done();
        });
    });
    it('errors out if unzipped size exceeds max size', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/gunzipsize.tm2z', function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Unzipped size should not exceed 5MB.');
            done();
        });
    });
    it('errors out if unzipped size exceeds custom max size', function(done) {
        tilelive.load({
            protocol: 'tm2z:',
            pathname: fixtureDir + '/gunzipsize.tm2z',
            gunzipsize: 1024 * 1024
        }, function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Unzipped size should not exceed 1MB.');
            done();
        });
    });
    it('errors out if unzipped project.xml size exceeds max size', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/xmlsize.tm2z', function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Unzipped project.xml size should not exceed 750KB.');
            done();
        });
    });
    it('errors out if unzipped project.xml size exceeds custom max size', function(done) {
        tilelive.load({
            protocol: 'tm2z:',
            pathname: fixtureDir + '/xmlsize.tm2z',
            xmlsize: 300 * 1024
        }, function(err, source) {
            assert.equal(err instanceof RangeError, true);
            assert.equal(err.message, 'Unzipped project.xml size should not exceed 300KB.');
            done();
        });
    });
    it('errors out if not a directory', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/nodirectory.tm2z', function(err, source) {
            assert.equal(err.message.split(',')[0], 'EISDIR');
            done();
        });
    });
    it('errors out if missing project.xml', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/empty.tm2z', function(err, source) {
            assert.equal(err.message, 'project.xml not found in package');
            done();
        });
    });
    it('errors out on invalid project.xml', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/malformed.tm2z', function(err, source) {
            assert.equal(err.message.split(':')[0], 'XML document not well formed');
            done();
        });
    });
    it('errors out if style references a missing font', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/missing_font.tm2z', function(err, source) {
            assert.equal('EMAPNIK', err.code);
            assert.equal(err.message.split("'")[0], 'Failed to find font face ');
            done();
        });
    });
    it('does not error out if style references a registered font', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/project.tm2z', function(err, source) {
            assert.ifError(err);
            done();
        });
    });
    it('errors out if style references a missing image', function(done) {
        tilelive.load('tm2z://' + fixtureDir + '/missing_image.tm2z', function(err, source) {
            assert.equal('EMAPNIK', err.code);
            assert.equal(err.message.split(':')[0], 'file could not be found');
            done();
        });
    });
    it('profiles a tm2z file', function(done) {
        this.timeout(0);
        tilelive.load('tm2z://' + fixtureDir + '/project.tm2z', function(err, source) {
            assert.ifError(err);
            source.profile(function(err, profile) {
                assert.ifError(err);
                assert.deepEqual([
                    'tiles',
                    'xmltime',
                    'drawtime',
                    'loadtime',
                    'srcbytes',
                    'imgbytes'
                ], Object.keys(profile));
                assert.equal('number', typeof profile.xmltime);
                assert.deepEqual(['avg','min','max'], Object.keys(profile.drawtime));
                assert.deepEqual(['avg','min','max'], Object.keys(profile.loadtime));
                assert.deepEqual(['avg','min','max'], Object.keys(profile.srcbytes));
                assert.deepEqual(['avg','min','max'], Object.keys(profile.imgbytes));
                assert.deepEqual([
                    '0/0/0',
                    '1/1/0',
                    '2/2/1',
                    '3/4/3',
                    '4/9/7',
                    '5/19/14',
                    '6/39/29',
                    '7/79/58',
                    '8/159/117',
                    '9/319/235',
                    '10/638/470',
                    '11/1276/940',
                    '12/2553/1880',
                    '13/5106/3761',
                    '14/10212/7522',
                    '15/20424/15045',
                    '16/40849/30091',
                    '17/81699/60182',
                    '18/163398/120364',
                    '19/326797/240728',
                    '20/653594/481456',
                    '21/1307188/962913',
                    '22/2614376/1925826'
                ], profile.tiles.map(function(t) { return t.z + '/' + t.x + '/' + t.y }));
                done();
            });
        });
    });
});

describe('tm2z+http', function() {
    it('loads a tm2z+http url', function(done) {
        this.timeout(5000);
        tilelive.load('tm2z+' + remotePath, function(err, source) {
            assert.ifError(err);
            done();
        });
    });
    it('matches expected xml', function(done) {
        this.timeout(5000);
        tilelive.load('tm2z+' + remotePath, function(err, source) {
            assert.ifError(err);
            assert.equal(xml, source._xml);
            done();
        });
    });
    it('errors out on an invalid S3 url', function(done) {
        tilelive.load('tm2z+http://mapbox.s3.amazonaws.com/tilelive-vector/invalid.tm2z', function(err, source) {
            assert.equal('Z_DATA_ERROR', err.code);
            done();
        });
    });
});
<|endoftext|>


<commit_before>const ArgParser   = require('argparser');
const fs          = require('fs');
const Worker      = require('./lib/node-webworker');
const PC          = require('./pairgen.config');
const spawn       = require('child_process').spawn;
const FASTAReader = require('./lib/FASTAReader/FASTAReader');

function parseIntF(v) {
  var ret = parseInt(v);
  return (isNaN(ret)) ? 0 : ret;
}

function main() {
  const p = new ArgParser();
  p.defaults.valopts = undefined;
  p.addOptions([]).addValueOptions([
    'name','width','readlen', 'tlen',
    'dev','depth','save_dir','parallel','pair_id', 'exename',
    'p5', 'p7', 'adapter1', 'adapter2'
  ]).parse();

  function showUsage() {
    const cmd = p.getOptions('exename') || (process.argv[0] + ' ' + require('path').basename(process.argv[1]));
    console.error('[usage]');
    console.error('\t' + cmd + ' <fasta file> [<ranges bed file>]');
    console.error('[options]');
    console.error('\t' + '--name\tname of the sequence. default = basename(path)');
    console.error('\t' + '--readlen\tlength of the read. default = ' + PC.getDefault('readlen'));
    console.error('\t' + '--tlen\tlength of the template. default = ' + PC.getDefault('tlen'));
    console.error('\t' + '--dev\tstandard deviation of the total fragment length. default = ' + PC.getDefault('dev'));
    console.error('\t' + '--depth\tphysical read depth. default = ' + PC.getDefault('depth'));
    console.error('\t' + '--save_dir\tdirectory to save result. default = ' + PC.getDefault('save_dir'));
    console.error('\t' + '--pair_id <id_type>\tpair id type, put pair information explicitly. id_type is one of A, 1, F, F3.');
    console.error('\t' + '--parallel\tthe number of processes to run. default: ' + PC.getDefault('parallel'));
    console.error('\t' + '--p5\tIllumina P5 adapter. default: ' + PC.getDefault('p5'));
    console.error('\t' + '--p7\tIllumina P7 adapter. default: ' + PC.getDefault('p7'));
    console.error('\t' + '--adapter1\tIllumina Sequence Primer#1. default: ' + PC.getDefault('adapter1'));
    console.error('\t' + '--adapter2\tIllumina Sequence Primer#2. default: ' + PC.getDefault('adapter2'));
  }

  if (!p.getArgs(0)) {
    showUsage();
    return false;
  }

  const fastafile = p.getArgs(0);
  try {
    if (p.getOptions('width')) {
      throw new Error('"width" is the deprecated option. Use tlen, which stands for template length ( width + 2 * readlen).');
    }
    const config = new PC({
      path     : p.getArgs(0),
      rangebed : p.getArgs(1),
      name     : p.getOptions('name'),
      tlen     : p.getOptions('tlen'),
      readlen  : p.getOptions('readlen'),
      dev      : p.getOptions('dev'),
      depth    : p.getOptions('depth'),
      save_dir : p.getOptions('save_dir'),
      parallel : p.getOptions('parallel'),
      pair_id  : p.getOptions('pair_id'),
      p5       : p.getOptions('p5'),
      p7       : p.getOptions('p7'),
      adapter1 : p.getOptions('adapter1'),
      adapter2 : p.getOptions('adapter2')
    });
  }
  catch (e) {
    console.error(e.stack);
    return false;
  }

  const freader = new FASTAReader(fastafile);
  if (config.rangebed) {
    const read = require('./pairgen.input');
    var $j = read(config.rangebed, freader);
  }
  else {
    var $j = new (require('./lib/Junjo/Junjo'))();
    $j(function(){
      var ranges = [];
      Object.keys(freader.result).forEach(function(rname) {
        var fasta = freader.result[rname];
				console.log(fasta.getEndPos());
        ranges.push([
          rname,
          1,
          fasta.getEndPos(),
          config.depth
        ]);
      });
      this.out = ranges;
    });
  }

  $j.on('end', function(err, ranges) {
    if (err) {
      console.error("terminated with errors.");
      return;
    }
    config.ranges = ranges || [];

    fs.mkdirSync(config.tmp_dir, '0755');

    showinfo(config);

    var total_end_count = 0;
    Worker.sockdir = config.tmp_dir;

    var totalFailCounts = {};
    ranges.forEach(function(range, rangeId) {
      totalFailCounts[rangeId] = [0, 0];
    });

    for (var i=0; i<config.parallel; i++) {
      (function(i) {
        var worker = new Worker('pairgen.worker.js');

        // REDUCE
        worker.onmessage = function(msg) {
          var failcounts = msg.data;
          worker.terminate(0.1);
          total_end_count++;
          Object.keys(failcounts).forEach(function(rangeId) {
            totalFailCounts[rangeId][0] += failcounts[rangeId][0];
            totalFailCounts[rangeId][1] += failcounts[rangeId][1];
          });
          if (total_end_count < config.parallel) return;

          var lefts  = [];
          var rights = [];
          for (var i=0; i<config.parallel; i++) {
            lefts.push(config.tmp_dir + '/left_' + i);
            rights.push(config.tmp_dir + '/right_' + i);
          }

          function filepath(lr) {
            return config.save_dir + '/' +
                   config.name + '_' +
                   ((lr=='l')?1:2) + '.fastq';
          }

          var finflag = 0;
          function write_end() {
            finflag++;
            if (finflag < 2) return;
            spawn('rm', ['-rf', config.tmp_dir]); // TODO recursive rmdir.

            // report actual depth
            Object.keys(totalFailCounts).forEach(function(rangeId) {
              var range = ranges[rangeId];
              var depth = range[3], newdepth = depth;
              var numer = totalFailCounts[rangeId][0];
              var denom = totalFailCounts[rangeId][1];
              if (denom) {
                var newdepth = Math.floor(depth * (denom - numer) / denom);
              }
              range.push(newdepth);
              console.log(range.join('\t'));
            });
          }

          var catleft  = spawn('cat', lefts);
          var catright = spawn('cat', rights);
          var lwrite = fs.createWriteStream(filepath('l'));
          var rwrite = fs.createWriteStream(filepath('r'));

          catleft.stdout.pipe(lwrite);
          catright.stdout.pipe(rwrite);

          catright.stdout.on('end', write_end);

          catleft.stdout.on('end', write_end);

          lwrite.on('end', write_end);
          rwrite.on('end', write_end);
        }; // worker.onmessage

        var conf4worker = config.toObject(true);
        conf4worker.is_worker = true;
        conf4worker.para_id = i;

        // MAP
        worker.postMessage(conf4worker);
      })(i);
    }
  });

  $j.run();
}

function showinfo(config) {
  const hasRanges = config.rangebed;
  console.error('#############################');
  console.error('# INPUT INFORMATION');
  console.error('# FASTA FILE         : ' + config.path);
  console.error('# NAME               : ' + config.name);
  console.error('# REFERENCE NAME     : ' + ((hasRanges) ? '(ALL IN THE BED FILE)' :'(ALL IN THE FASTA FILE)'));
  console.error('# READ LENGTH        : ' + config.readlen);
  console.error('# TEMPLATE LENGTH    : ' + config.tlen);
  console.error('# STDDEV OF DISTANCE : ' + config.dev);
  console.error('# RANGES FILE        : ' + config.rangebed);
  console.error('# SAVE DIR           : ' + config.save_dir);
  console.error('# PARALLEL           : ' + config.parallel);
  console.error('# DEPTH OF COVERAGE  : ' + ((hasRanges) ? '(DEPENDS ON EACH RANGE DATA)': config.depth));
  console.error('# SUFFIX OF READS    : ' + "'" + config.pair_id[0] + "', '" + config.pair_id[1] + "'");
  console.error('# P5 ADAPTER         : ' + config.p5);
  console.error('# P7 ADAPTER         : ' + config.p7);
  console.error('# PRIMER SEQUENCE 1  : ' + config.adapter1);
  console.error('# PRIMER SEQUENCE 2  : ' + config.adapter2);
  console.error('#############################');
}

/* execution */
if (process.argv[1] == __filename) { main(); }
<commit_msg>show output information<commit_after>const ArgParser   = require('argparser');
const fs          = require('fs');
const Worker      = require('./lib/node-webworker');
const PC          = require('./pairgen.config');
const spawn       = require('child_process').spawn;
const FASTAReader = require('./lib/FASTAReader/FASTAReader');

function parseIntF(v) {
  var ret = parseInt(v);
  return (isNaN(ret)) ? 0 : ret;
}

function main() {
  const p = new ArgParser();
  p.defaults.valopts = undefined;
  p.addOptions([]).addValueOptions([
    'name','width','readlen', 'tlen',
    'dev','depth','save_dir','parallel','pair_id', 'exename',
    'p5', 'p7', 'adapter1', 'adapter2'
  ]).parse();

  function showUsage() {
    const cmd = p.getOptions('exename') || (process.argv[0] + ' ' + require('path').basename(process.argv[1]));
    console.error('[usage]');
    console.error('\t' + cmd + ' <fasta file> [<ranges bed file>]');
    console.error('[options]');
    console.error('\t' + '--name\tname of the sequence. default = basename(path)');
    console.error('\t' + '--readlen\tlength of the read. default = ' + PC.getDefault('readlen'));
    console.error('\t' + '--tlen\tlength of the template. default = ' + PC.getDefault('tlen'));
    console.error('\t' + '--dev\tstandard deviation of the total fragment length. default = ' + PC.getDefault('dev'));
    console.error('\t' + '--depth\tphysical read depth. default = ' + PC.getDefault('depth'));
    console.error('\t' + '--save_dir\tdirectory to save result. default = ' + PC.getDefault('save_dir'));
    console.error('\t' + '--pair_id <id_type>\tpair id type, put pair information explicitly. id_type is one of A, 1, F, F3.');
    console.error('\t' + '--parallel\tthe number of processes to run. default: ' + PC.getDefault('parallel'));
    console.error('\t' + '--p5\tIllumina P5 adapter. default: ' + PC.getDefault('p5'));
    console.error('\t' + '--p7\tIllumina P7 adapter. default: ' + PC.getDefault('p7'));
    console.error('\t' + '--adapter1\tIllumina Sequence Primer#1. default: ' + PC.getDefault('adapter1'));
    console.error('\t' + '--adapter2\tIllumina Sequence Primer#2. default: ' + PC.getDefault('adapter2'));
  }

  if (!p.getArgs(0)) {
    showUsage();
    return false;
  }

  const fastafile = p.getArgs(0);
  try {
    if (p.getOptions('width')) {
      throw new Error('"width" is the deprecated option. Use tlen, which stands for template length ( width + 2 * readlen).');
    }
    const config = new PC({
      path     : p.getArgs(0),
      rangebed : p.getArgs(1),
      name     : p.getOptions('name'),
      tlen     : p.getOptions('tlen'),
      readlen  : p.getOptions('readlen'),
      dev      : p.getOptions('dev'),
      depth    : p.getOptions('depth'),
      save_dir : p.getOptions('save_dir'),
      parallel : p.getOptions('parallel'),
      pair_id  : p.getOptions('pair_id'),
      p5       : p.getOptions('p5'),
      p7       : p.getOptions('p7'),
      adapter1 : p.getOptions('adapter1'),
      adapter2 : p.getOptions('adapter2')
    });
  }
  catch (e) {
    console.error(e.stack);
    return false;
  }

  const freader = new FASTAReader(fastafile);
  if (config.rangebed) {
    const read = require('./pairgen.input');
    var $j = read(config.rangebed, freader);
  }
  else {
    var $j = new (require('./lib/Junjo/Junjo'))();
    $j(function(){
      var ranges = [];
      Object.keys(freader.result).forEach(function(rname) {
        var fasta = freader.result[rname];
        ranges.push([
          rname,
          1,
          fasta.getEndPos(),
          config.depth
        ]);
      });
      this.out = ranges;
    });
  }

  $j.on('end', function(err, ranges) {
    if (err) {
      console.error("terminated with errors.");
      return;
    }
    config.ranges = ranges || [];

    fs.mkdirSync(config.tmp_dir, '0755');

    showinfo(config);

    var total_end_count = 0;
    Worker.sockdir = config.tmp_dir;

    var totalFailCounts = {};
    ranges.forEach(function(range, rangeId) {
      totalFailCounts[rangeId] = [0, 0];
    });

    for (var i=0; i<config.parallel; i++) {
      (function(i) {
        var worker = new Worker('pairgen.worker.js');

        // REDUCE
        worker.onmessage = function(msg) {
          var failcounts = msg.data;
          worker.terminate(0.1);
          total_end_count++;
          Object.keys(failcounts).forEach(function(rangeId) {
            totalFailCounts[rangeId][0] += failcounts[rangeId][0];
            totalFailCounts[rangeId][1] += failcounts[rangeId][1];
          });
          if (total_end_count < config.parallel) return;

          var lefts  = [];
          var rights = [];
          for (var i=0; i<config.parallel; i++) {
            lefts.push(config.tmp_dir + '/left_' + i);
            rights.push(config.tmp_dir + '/right_' + i);
          }

          function filepath(lr) {
            return config.save_dir + '/' +
                   config.name + '_' +
                   ((lr=='l')?1:2) + '.fastq';
          }

          var finflag = 0;
          function write_end() {
            finflag++;
            if (finflag < 2) return;
            spawn('rm', ['-rf', config.tmp_dir]); // TODO recursive rmdir.

            // report output information
            console.error('#############################');
            console.error('# OUTPUT INFORMATION');
            // actural depth
            Object.keys(totalFailCounts).forEach(function(rangeId) {
              var range = ranges[rangeId];
              var depth = range[3], newdepth = depth;
              var numer = totalFailCounts[rangeId][0];
              var denom = totalFailCounts[rangeId][1];
              if (denom) {
                var newdepth = Math.floor(depth * (denom - numer) / denom * 10) / 10;
              }
              range.push(newdepth);
              console.error('# ' + range.join('\t'));
            });
            console.error('#############################');
          }

          var catleft  = spawn('cat', lefts);
          var catright = spawn('cat', rights);
          var lwrite = fs.createWriteStream(filepath('l'));
          var rwrite = fs.createWriteStream(filepath('r'));

          catleft.stdout.pipe(lwrite);
          catright.stdout.pipe(rwrite);

          catright.stdout.on('end', write_end);

          catleft.stdout.on('end', write_end);

          lwrite.on('end', write_end);
          rwrite.on('end', write_end);
        }; // worker.onmessage

        var conf4worker = config.toObject(true);
        conf4worker.is_worker = true;
        conf4worker.para_id = i;

        // MAP
        worker.postMessage(conf4worker);
      })(i);
    }
  });

  $j.run();
}

function showinfo(config) {
  const hasRanges = config.rangebed;
  console.error('#############################');
  console.error('# INPUT INFORMATION');
  console.error('# FASTA FILE         : ' + config.path);
  console.error('# NAME               : ' + config.name);
  console.error('# REFERENCE NAME     : ' + ((hasRanges) ? '(ALL IN THE BED FILE)' :'(ALL IN THE FASTA FILE)'));
  console.error('# READ LENGTH        : ' + config.readlen);
  console.error('# TEMPLATE LENGTH    : ' + config.tlen);
  console.error('# STDDEV OF DISTANCE : ' + config.dev);
  console.error('# RANGES FILE        : ' + config.rangebed);
  console.error('# SAVE DIR           : ' + config.save_dir);
  console.error('# PARALLEL           : ' + config.parallel);
  console.error('# DEPTH OF COVERAGE  : ' + ((hasRanges) ? '(DEPENDS ON EACH RANGE DATA)': config.depth));
  console.error('# SUFFIX OF READS    : ' + "'" + config.pair_id[0] + "', '" + config.pair_id[1] + "'");
  console.error('# P5 ADAPTER         : ' + config.p5);
  console.error('# P7 ADAPTER         : ' + config.p7);
  console.error('# PRIMER SEQUENCE 1  : ' + config.adapter1);
  console.error('# PRIMER SEQUENCE 2  : ' + config.adapter2);
  console.error('#############################');
}

/* execution */
if (process.argv[1] == __filename) { main(); }
<|endoftext|>


<commit_before>/**
 * Copyright 2015 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {loadScript, validateData} from '../3p/3p';

/**
 * @param {!Window} global
 * @param {!Object} data
 */
export function videonow(global, data) {
  /*eslint "google-camelcase/google-camelcase": 0*/
  global._videonow_amp = {
    allowed_data: ['pid'],
    mandatory_data: ['pid'],
    data,
  };

  validateData(data,
      global._videonow_amp.mandatory_data, global._videonow_amp.allowed_data);

  const profileId = data.pid || 1;
  const src = data.src || 'prod';

  // production version by default
  let script = 'http://static.videonow.ru/dev/vn_init_module.js?profileId=' + profileId;

  if (src === 'local') {
    script = 'http://localhost:8085/vn_init.js?profileId=' +
        profileId + '&url=' + encodeURIComponent('http://localhost:8085/init');
  } else if (src === 'dev') {
    // this part can be changed late
    script = 'http://static.videonow.ru/dev/vn_init_module.js?profileId=' +
        profileId + '&url=' +
        encodeURIComponent('http://data.videonow.ru/?init');
  }

  loadScript(global, script);
}
<commit_msg>CORS fixed :fire: (#13736)<commit_after>/**
 * Copyright 2015 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {loadScript, validateData} from '../3p/3p';

/**
 * @param {!Window} global
 * @param {!Object} data
 */
export function videonow(global, data) {
  const mandatoryAttributes = ['pid', 'width', 'height'];
  const optionalAttributes = ['kind', 'src'];

  validateData(data, mandatoryAttributes, optionalAttributes);

  const profileId = data.pid || 1;
  const kind = data.type || 'prod';

  // production version by default
  let script = (data.src && decodeURI(data.src)) ||
      ('https://static.videonow.ru/dev/vn_init_module.js?profileId=' + profileId);

  if (kind === 'local') {
    script = 'https://localhost:8085/vn_init.js?profileId=' + profileId +
        '&url=' + encodeURIComponent('https://localhost:8085/init');
  } else if (kind === 'dev') {
    // this part can be changed late
    script = 'https://static.videonow.ru/dev/vn_init_module.js?profileId=' +
        profileId +
        '&url=' + encodeURIComponent('https://data.videonow.ru/?init');
  }

  loadScript(global, script);
}
<|endoftext|>


<commit_before>'use strict';

describe('util', function() {
  let util = Talkie({api:true}).util;

  it('toArray', function() {
    let args  = util.toArray(arguments);
    let nodes = util.toArray(document.querySelectorAll('*'));

    assert(Array.isArray(args))
    assert(Array.isArray(nodes))
  });

  it('compose', function() {
    let fn = util.compose(function(y) {
      return y * 2;
    }, function(x) {
      return x + 1;
    });

    assert(fn(1) === 4);
    assert(fn(3) === 8);
  });

  it('getById', function() {
    let el = document.createElement('div');
    el.id = 'test';
    document.body.appendChild(el);

    assert(util.getById('test') === el);

    el.parentNode.removeChild(el);
  });

});
<commit_msg>add test<commit_after>'use strict';

describe('util', function() {
  let util = Talkie({api:true}).util;

  it('toArray', function() {
    let args  = util.toArray(arguments);
    let nodes = util.toArray(document.querySelectorAll('*'));

    assert(Array.isArray(args));
    assert(Array.isArray(nodes));
  });

  it('compose', function() {
    let fn = util.compose(function(y) {
      return y * 2;
    }, function(x) {
      return x + 1;
    });

    assert(fn(1) === 4);
    assert(fn(3) === 8);
  });

  it('getById', function() {
    let el = document.createElement('div');
    el.id = 'test';
    document.body.appendChild(el);

    assert(util.getById('test') === el);

    el.parentNode.removeChild(el);
  });

  it('textAssignOf', function() {
    let el = document.createElement('div');
    el.textContent = 'hoge';

    assert(el.textContent === 'hoge');

    let assign = util.textAssignOf(el);
    assign('fuga');
    assert(el.textContent === 'fuga');

    el.parentNode.removeChild(el);
  });

  it('styleAssignOf', function() {
    let el = document.createElement('div');
    el.style.width = '100%';

    assert(el.style.width === '100%');

    let assign = util.styleAssignOf(el, 'width');
    assign('50%');
    assert(el.style.width === '50%');

    el.parentNode.removeChild(el);
  });

});
<|endoftext|>


<commit_before><<<<<<>>>>>> f3f9c2a8d5d834b154e6e9bdcab0908cd0977819
    render: function() {
        return (

Ja hallo wereld!

        );
    }
});

module.exports = HelloWorld;
<commit_msg>Fixing App.js<commit_after>let React = require('react');

let HelloWorld = React.createClass({
    render: function() {
        return (

Ja hallo wereld!

        );
    }
});

module.exports = HelloWorld;
<|endoftext|>


<commit_before>{
  angular
    .module('meganote.core')
    .constant('NOTES_URL', 'http://localhost:3030/notes/');
}
<commit_msg>Add API_BASE constant<commit_after>{
  angular
    .module('meganote.core')
    .constant('NOTES_URL', 'http://localhost:3030/notes/')
    .constant('API_BASE', 'http://localhost:3030/');
}
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { createStructuredSelector } from 'reselect'

import { Box, Grid, GridCell, Input, Text } from 'core/primitives'
import * as actions from './actions'
import selectors from './selectors'

export class Hunt extends Component {
  constructor() {
    super()

    this.onSetInput = this.onSetInput.bind(this)
  }

  onSetInput(clue, input) {
    const { actions } = this.props

    actions.setInput(clue, input)
  }

  render() {
    const { clues } = this.props
    const { onSetInput } = this

    return (
      <Grid style={{ height: '100%' }}>

          <Box align="middle" style={{ marginBottom: "16px" }}>
            <svg width="150px" style={{ margin: "auto" }} fill="#F1996B" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 0 841.89 595.28"><g><g><path d="M423.667,548.833c-7.5-0.5-9.833-9.667-9.833-9.667C410,526.166,398.667,517,398.667,517    C378.333,498.167,296,447.5,296,447.5C270.5,436,216,419,216,419c-12-6-15-20-15-20c5,0.5,26.5-8.5,26.5-8.5l-26.5-31l26-9.5    l-26-23.5l29-17.5c-19-145.5,109-180.5,109-180.5s-9.5,138,8,153.5c0,0,35.834-27.5,76.667-29"/><path d="M423,548.834c7.5-0.5,9.834-9.668,9.834-9.668C436.666,526.166,448,517,448,517c20.334-18.832,102.667-69.5,102.667-69.5    c25.5-11.5,80-28.5,80-28.5c12-6,15-20,15-20c-5,0.5-26.5-8.5-26.5-8.5l26.5-31l-26-9.5l26-23.5l-29-17.5    c19-145.5-109-180.5-109-180.5s9.5,138-8,153.5c0,0-35.834-27.5-76.667-29"/></g><path fill="#F1996B" d="M526.875,349c-0.504-0.579-1.638-0.396-2.262-0.281c-3.223,0.598-6.087,3.236-8.14,5.617   c-3.188,3.697-5.497,8.293-8.43,12.216c-2.324,3.109-5.007,5.961-7.836,8.615c-6.988,6.558-16.157,10.835-25.915,10.476   c-6.823-0.251-13.843-3.613-19.418-7.393c0,0,14.125-32.125,61-32C515.875,346.25,523.5,345.125,526.875,349z"/><path fill="#F1996B" d="M331.347,349.455c0.504-0.579,1.638-0.396,2.262-0.281c3.223,0.598,6.087,3.236,8.14,5.617   c3.188,3.697,5.497,8.293,8.43,12.216c2.324,3.109,5.007,5.961,7.836,8.615c6.988,6.558,16.157,10.835,25.915,10.476   c6.823-0.251,13.843-3.613,19.418-7.393c0,0-14.125-32.125-61-32C342.347,346.705,334.722,345.58,331.347,349.455z"/></g></svg>
          </Box>

          <Text fontSize="28px" textAlign="center" textTransform="uppercase">Foxy Hunt</Text>

          <Text lineHeight="1.2" textAlign="center" style={{ padding: "16px" }}>
            Foxy ladies of Leicester the challenge is set!

            Correctly solve the following 10 clues and you will be taken on a mind blowing tour of Leicester culminating in the discovery of hen house treasure.

            Fail to solve the clues and you will be left sinking Sour Cherry shots in Revolution!

            Remember to receive the full payload of treasure you must have photographic evidence of all solved clues.

            Game on!
          </Text>

          { clues.map( (c, i) => i > 0 && clues[i-1].correct || i === 0 ? (

              <Text fontWeight={700} lineHeight="25px" textAlign="center" style={{ margin: "0 auto", border: "1px solid", borderRadius: "50%", width: "30px", height: "30px" }}>
                { i + 1 }
              </Text>
              <Text textAlign="center" style={{ margin: "8px 0" }}>{ c.clue }</Text>
              <Input
                value={ c.input }
                type="text"
                placeholder="Answer goes here!"
                border="1px solid"
                borderColor={ c.correct ? "mint" : "pink" }
                width="90%"
                onChange={ ({ target }) => onSetInput(i, target.value) }
              />

          ) : null )}

        <GridCell gridRowStart={2} gridRowEnd={3} style={{ textAlign: 'center' }}>
          <svg width="50px" fill="#F1996B" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 0 50 50"><path d="M21.316,23.836c0,0-12.675,1.979-13.597-3.036c-0.922-5.015,7.126-8.977,7.126-8.977  c-7.525,1.717-10.033,7.392-10.033,9.637c0,2.243,0.665,11.613,17.292,11.35c16.627-0.264,18.741,1.582,22.699,4.755  C45.336,16.972,21.316,23.836,21.316,23.836z M26.897,31.61l0.059-0.251l2.143-1.849l-2.143-1.451l2.143-1.584  c0,0-1.154-1.229-2.264-2.4c0.918-0.089,1.906-0.149,2.937-0.149c4.801,0,8.413,1.212,10.747,3.604  c1.77,1.814,2.799,4.354,3.073,7.564C40.718,33.056,37.246,31.741,26.897,31.61z"/></svg>
        </GridCell>
      </Grid>
    )
  }
}

export default connect(
  createStructuredSelector({ ...selectors }),
  dispatch => ({
    actions: bindActionCreators({ ...actions }, dispatch)
  })
)(Hunt)
<commit_msg>add number of letters<commit_after>import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { createStructuredSelector } from 'reselect'

import { Box, Grid, GridCell, Input, Text } from 'core/primitives'
import * as actions from './actions'
import selectors from './selectors'

export class Hunt extends Component {
  constructor() {
    super()

    this.onSetInput = this.onSetInput.bind(this)
  }

  onSetInput(clue, input) {
    const { actions } = this.props

    actions.setInput(clue, input)
  }

  render() {
    const { clues } = this.props
    const { onSetInput } = this

    return (
      <Grid style={{ height: '100%' }}>

          <Box align="middle" style={{ marginBottom: "16px" }}>
            <svg width="150px" style={{ margin: "auto" }} fill="#F1996B" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 0 841.89 595.28"><g><g><path d="M423.667,548.833c-7.5-0.5-9.833-9.667-9.833-9.667C410,526.166,398.667,517,398.667,517    C378.333,498.167,296,447.5,296,447.5C270.5,436,216,419,216,419c-12-6-15-20-15-20c5,0.5,26.5-8.5,26.5-8.5l-26.5-31l26-9.5    l-26-23.5l29-17.5c-19-145.5,109-180.5,109-180.5s-9.5,138,8,153.5c0,0,35.834-27.5,76.667-29"/><path d="M423,548.834c7.5-0.5,9.834-9.668,9.834-9.668C436.666,526.166,448,517,448,517c20.334-18.832,102.667-69.5,102.667-69.5    c25.5-11.5,80-28.5,80-28.5c12-6,15-20,15-20c-5,0.5-26.5-8.5-26.5-8.5l26.5-31l-26-9.5l26-23.5l-29-17.5    c19-145.5-109-180.5-109-180.5s9.5,138-8,153.5c0,0-35.834-27.5-76.667-29"/></g><path fill="#F1996B" d="M526.875,349c-0.504-0.579-1.638-0.396-2.262-0.281c-3.223,0.598-6.087,3.236-8.14,5.617   c-3.188,3.697-5.497,8.293-8.43,12.216c-2.324,3.109-5.007,5.961-7.836,8.615c-6.988,6.558-16.157,10.835-25.915,10.476   c-6.823-0.251-13.843-3.613-19.418-7.393c0,0,14.125-32.125,61-32C515.875,346.25,523.5,345.125,526.875,349z"/><path fill="#F1996B" d="M331.347,349.455c0.504-0.579,1.638-0.396,2.262-0.281c3.223,0.598,6.087,3.236,8.14,5.617   c3.188,3.697,5.497,8.293,8.43,12.216c2.324,3.109,5.007,5.961,7.836,8.615c6.988,6.558,16.157,10.835,25.915,10.476   c6.823-0.251,13.843-3.613,19.418-7.393c0,0-14.125-32.125-61-32C342.347,346.705,334.722,345.58,331.347,349.455z"/></g></svg>
          </Box>

          <Text fontSize="28px" textAlign="center" textTransform="uppercase">Foxy Hunt</Text>

          <Text lineHeight="1.2" textAlign="center" style={{ padding: "16px" }}>
            Foxy ladies of Leicester the challenge is set!

            Correctly solve the following 10 clues and you will be taken on a mind blowing tour of Leicester culminating in the discovery of hen house treasure.

            Fail to solve the clues and you will be left sinking Sour Cherry shots in Revolution!

            Remember to receive the full payload of treasure you must have photographic evidence of all solved clues.

            Game on!
          </Text>

          { clues.map( (c, i) => i > 0 && clues[i-1].correct || i === 0 ? (

              <Text fontWeight={700} lineHeight="25px" textAlign="center" style={{ margin: "0 auto", border: "1px solid", borderRadius: "50%", width: "30px", height: "30px" }}>
                { i + 1 }
              </Text>
              <Text textAlign="center" style={{ margin: "8px 0 0" }}>{ c.clue }</Text>
              <Text color="foxy" fontSize="14px" textAlign="center" style={{ margin: "0 0 8px" }}>{ c.answer.length } letters</Text>
              <Input
                value={ c.input }
                type="text"
                placeholder="Answer goes here!"
                border="1px solid"
                borderColor={ c.correct ? "mint" : "pink" }
                width="90%"
                onChange={ ({ target }) => onSetInput(i, target.value) }
              />

          ) : null )}

        <GridCell gridRowStart={2} gridRowEnd={3} style={{ textAlign: 'center' }}>
          <svg width="50px" fill="#F1996B" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 0 50 50"><path d="M21.316,23.836c0,0-12.675,1.979-13.597-3.036c-0.922-5.015,7.126-8.977,7.126-8.977  c-7.525,1.717-10.033,7.392-10.033,9.637c0,2.243,0.665,11.613,17.292,11.35c16.627-0.264,18.741,1.582,22.699,4.755  C45.336,16.972,21.316,23.836,21.316,23.836z M26.897,31.61l0.059-0.251l2.143-1.849l-2.143-1.451l2.143-1.584  c0,0-1.154-1.229-2.264-2.4c0.918-0.089,1.906-0.149,2.937-0.149c4.801,0,8.413,1.212,10.747,3.604  c1.77,1.814,2.799,4.354,3.073,7.564C40.718,33.056,37.246,31.741,26.897,31.61z"/></svg>
        </GridCell>
      </Grid>
    )
  }
}

export default connect(
  createStructuredSelector({ ...selectors }),
  dispatch => ({
    actions: bindActionCreators({ ...actions }, dispatch)
  })
)(Hunt)
<|endoftext|>


<commit_before>"use strict";

module.exports = {

  init: () => {

    console.log('Configuring Angular app...');

    require('./app.config')();

    console.log('App initialized.');
  }
};
<commit_msg>Re-apply Node v5 hack<commit_after>"use strict";

module.exports = {

  init: () => {

    // Hack since Node v5
    window.jade = require('jade/runtime');

    console.log('Configuring Angular app...');

    require('./app.config')();

    console.log('App initialized.');
  }
};
<|endoftext|>


<commit_before>'use strict';

/* Controllers */

angular.module('myApp.controllers', [])
    .controller('LandingPageController', [function() {

    }])

.controller('WaitlistController', ['$scope', '$firebase', function($scope, $firebase) {

    // Connect $scope.parties to live Firebase data.
    var partiesRef = new Firebase('https://waitandeat-demo-ver1.firebaseio.com/parties');
    $scope.parties = $firebase(partiesRef);

    // Object to store data from the waitlist form.
    $scope.newParty = {
        name: '',
        phone: '',
        size: ''
    };

    // Function to save a new party to the waitlist.
    $scope.saveParty = function() {
        $scope.parties.$add($scope.newParty);
        $scope.newParty = {
            name: '',
            phone: '',
            size: ''
        };
    };

    // Function to sent a a text message
    $scope.sendTextMessage = function(party) {
        var textMessageRef = new Firebase('http://waitandeat-demo-ver1.firebaseio.com/textMessages');
        var textMessages = $firebase(textMessageRef);
        var newTextMessage = {
            phoneNumber: party.phone,
            size: party.size,
            name: party.name
        };
        textMessages.$add({ phoneNumber: phoneNumber });
    };
}])<commit_msg>debugged<commit_after>'use strict';

/* Controllers */

angular.module('myApp.controllers', [])
    .controller('LandingPageController', [function() {

    }])

.controller('WaitlistController', ['$scope', '$firebase', function($scope, $firebase) {

    // Connect $scope.parties to live Firebase data.
    var partiesRef = new Firebase('https://waitandeat-demo-ver1.firebaseio.com/parties');
    $scope.parties = $firebase(partiesRef);

    // Object to store data from the waitlist form.
    $scope.newParty = {
        name: '',
        phone: '',
        size: ''
    };

    // Function to save a new party to the waitlist.
    $scope.saveParty = function() {
        $scope.parties.$add($scope.newParty);
        $scope.newParty = {
            name: '',
            phone: '',
            size: ''
        };
    };

    // Function to sent a a text message
    $scope.sendTextMessage = function(party) {
        var textMessageRef = new Firebase('http://waitandeat-demo-ver1.firebaseio.com/textMessages');
        var textMessages = $firebase(textMessageRef);
        var newTextMessage = {
            phoneNumber: party.phone,
            size: party.size,
            name: party.name
        };
        textMessages.$add({newTextMessage});
    };
}]);<|endoftext|>


<commit_before>'use strict';

/* Controllers */

var phonecatApp = angular.module('phonecatApp', []);

phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
  $http.get('phones/phones.json').success(function(data) {
    $scope.phones = data;
  });

  $scope.orderProp = 'age';
});<commit_msg>Updated controllers.js to use inline annotation to prevent errors if the file is minified.<commit_after>'use strict';

/* Controllers */

var phonecatApp = angular.module('phonecatApp', []);

phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
  function ($scope, $http) {
    $http.get('phones/phones.json').success(function(data) {
      $scope.phones = data;
    });

    $scope.orderProp = 'age';
  }]);<|endoftext|>


<commit_before>'use strict';

/* Utilities */
var arrayRemove = function(ary, e) {
	if (typeof(ary) == 'undefined')
		alert("arrayRemove(): Error: typeof(ary) == 'undefined'.");
//	if (typeof(ary) != 'array')
//		throw "arrayRemove(): Error: typeof(ary) != 'array'.";
	var index = ary.indexOf(e);
	if (index > -1) {
	    ary.splice(index, 1);
	}

	if (typeof(ary) == 'undefined')
		alert("arrayRemove(): Error: return undefined");

	return ary;
};

var arrayMerge = function(a1, a2) {
	var a = a1.concat(a2);
	for(var i=0; i<a.length; ++i) {
		for(var j=i+1; j<a.length; ++j) {
			if(a[i] === a[j])
				a.splice(j--, 1);
		}
	}

	return a;
};
/* Utilities ^^ */

/* Go game logic */

/* TODO
map liberties to size of stone image
onHover, display 50% opacity stone
ko rule
suicide rule
move stack (board snapshots)
	undo, redo buttons

Later:
mark potential, false, and complete eyes
mark cutting points and protected points like tiger's mouth
mark forcing moves
onHover, show change in liberties, enemy liberties removed, territory gained (range)?
*/

var EMPTY = "e";
var BLACK = "b";
var WHITE = "w";

//wraps a place on the board which could have a stone
var Place = function(x, y) {
	this.x = x;
	this.y = y;
	this.color = EMPTY;
	this.group = new Group(this);
	this.adjacentPlaces = []; //populated by Board constructor
	this.size = 45; //used as width and height in pixels
};

Place.prototype.refreshSize = function() {
	var liberties = this.group.liberties.length;
	var mapLibToSize = [];
	mapLibToSize[1] = 30;
	mapLibToSize[2] = 35;
	mapLibToSize[3] = 40;
	mapLibToSize[4] = 45;
	mapLibToSize[5] = 50;
	mapLibToSize[6] = 55;
	mapLibToSize[7] = 60;
	if (liberties > 7)
		liberties = 7;

	this.size = mapLibToSize[liberties];
};

//Group wraps an array of places
var nextGroupId = 0;
var Group = function(place) {
	this.id = nextGroupId++;
	this.liberties = [];
	this.color = place.color;
	this.stones = (place != null)?[place]:[];

	if (this.color != EMPTY) {
		//add adjacent stones/liberties to this group recursively
		var checkAdjacentPlacesRecurs = function(center, group) {
			var a, i;
			for (i in center.adjacentPlaces) {
				a = center.adjacentPlaces[i];

				if (group.stones.indexOf(a) == -1) {
					//adjacent stone not already part of group

					//add place as liberty (if not already added)
					if (a.color == EMPTY) {
						if (group.liberties.indexOf(a) == -1)
							group.liberties.push(a);
					}
					//add place as stone (if not already added)
					if (a.color == group.color) {
						if (group.stones.indexOf(a) == -1)
							group.stones.push(a);
						a.group = group;
						checkAdjacentPlacesRecurs(a, group);
					}
				}
			}
		};
		checkAdjacentPlacesRecurs(place, this);
	}
};
//*/

var Board = function(size) {
	this.size = size;
	this.moves = ['emptyBoard'];
	this.places = [];
	var places = this.places;

	this.prisoners = [];
	this.prisoners['b'] = 0;
	this.prisoners['w'] = 0;

	//fill places[] = new Place
	var x, y;
	for (x = 0; x < size; x++) {
		for (y = 0; y < size; y++) {
			places[y*size+x] = new Place(x, y);
		}
	}

	//fill places[].adjacents[] = the (usually) 4 adjacent places
	for (var x = 0; x < size; x++) {
		for (var y = 0; y < size; y++) {
			var a, adjacents = [];
			if ((a = this.getPlaceByXY(x+1, y)) != null) adjacents.push(a);
			if ((a = this.getPlaceByXY(x-1, y)) != null) adjacents.push(a);
			if ((a = this.getPlaceByXY(x, y+1)) != null) adjacents.push(a);
			if ((a = this.getPlaceByXY(x, y-1)) != null) adjacents.push(a);

			this.getPlaceByXY(x, y).adjacentPlaces = adjacents;
		}
	}
};

Board.prototype.refreshGroups = function() {
	var i;
	var p;
	for (i in this.places) {
		p = this.places[i];
		p.group = null;
	}

	for (i in this.places) {
		p = this.places[i];
		if (p.group == null) {
			p.group = new Group(p);
		}
	}

	//update stone sizes to match liberties
	for (i in this.places) {
		p = this.places[i];
		if (p.color != EMPTY)
			p.refreshSize();
	}
};

Board.prototype.getPlaceByXY = function(x, y) {
	var size = this.size;
	var places = this.places;
	if (x >= 0 && x < size && y >= 0 && y < size)
		return places[y*size+x];
	else
		return null;
};

Board.prototype.getTurnColor = function() {
	var moves = this.moves;
	var isBlacksTurn = moves[moves.length - 1].indexOf("b") != 0;
	return (isBlacksTurn)?"b":"w";
};

Board.prototype.placeStone = function(place) {
	if (place.color == EMPTY) {
		var color = this.getTurnColor();

		place.color = color;

		this.moves.push(color + place.x + "," + place.y);

		//capture stones?
		var i;
		var a;
		for (i in place.adjacentPlaces) {
			a = place.adjacentPlaces[i];
			if (a.group.color != "e") {
				a.group = new Group(a); //refresh group info about the adjacent stone a
				if (a.group.liberties.length == 0) {
					//capture(a.group);
					var j;
					var p;
					for (j in a.group.stones) {
						p = a.group.stones[j];
						this.prisoners[p.color]++;
						p.color = "e";
					}
				}
			}
		}

		//refresh groups
		this.refreshGroups();
	}
};

Board.prototype.pass = function() {
	var color = this.getTurnColor();
	this.moves.push(color + "Pass");
};

Board.prototype.undo = function() {
	alert('TODO: implement board.undo()');
};
/* Go game logic ^^ */

/* Controllers */

var myGoApp = angular.module('myGoApp', []);

myGoApp.controller('testCtrl', function($scope) {
	$scope.test = 0;
});

myGoApp.controller('goBoardCtrl', function($scope) {
	var size = 13;
	var squareSize = 60;
	$scope.board = new Board(size);
	var board = $scope.board;
	$scope.size = size;
	$scope.squareSize = squareSize;
	$scope.placeStone = function(place) {
		board.placeStone(place);
		$scope.debugPlace = place;
	};
	$scope.places = board.places; //shortcut
	$scope.hoveredPlace = null; //TODO: change to = false?

	$scope.alert = function(s) {alert(s)};
});

<commit_msg>Added to TODO list comment<commit_after>'use strict';

/* Utilities */
var arrayRemove = function(ary, e) {
	if (typeof(ary) == 'undefined')
		alert("arrayRemove(): Error: typeof(ary) == 'undefined'.");
//	if (typeof(ary) != 'array')
//		throw "arrayRemove(): Error: typeof(ary) != 'array'.";
	var index = ary.indexOf(e);
	if (index > -1) {
	    ary.splice(index, 1);
	}

	if (typeof(ary) == 'undefined')
		alert("arrayRemove(): Error: return undefined");

	return ary;
};

var arrayMerge = function(a1, a2) {
	var a = a1.concat(a2);
	for(var i=0; i<a.length; ++i) {
		for(var j=i+1; j<a.length; ++j) {
			if(a[i] === a[j])
				a.splice(j--, 1);
		}
	}

	return a;
};
/* Utilities ^^ */

/* Go game logic */

/* TODO
map liberties to size of stone image
onHover, display 50% opacity stone
ko rule
suicide rule
move stack (board snapshots)
	undo, redo buttons

Later:
mark potential, false, and complete eyes
mark cutting points and protected points like tiger's mouth
mark forcing moves
onHover, show change in liberties, enemy liberties removed, territory gained (range)?
maybe show group's theoretical liberty (as if virtual connections were resolved)
for each place, add 30% opacity background (above board and below stone) where how black/white it is shows influence
	influence calculated as if each group emits up to 4 empty spaces away (black and white light cancel out)
		100% of liberties for closest empty space and 25% for 4th empty space away
*/

var EMPTY = "e";
var BLACK = "b";
var WHITE = "w";

//wraps a place on the board which could have a stone
var Place = function(x, y) {
	this.x = x;
	this.y = y;
	this.color = EMPTY;
	this.group = new Group(this);
	this.adjacentPlaces = []; //populated by Board constructor
	this.size = 45; //used as width and height in pixels
};

Place.prototype.refreshSize = function() {
	var liberties = this.group.liberties.length;
	var mapLibToSize = [];
	mapLibToSize[1] = 30;
	mapLibToSize[2] = 35;
	mapLibToSize[3] = 40;
	mapLibToSize[4] = 45;
	mapLibToSize[5] = 50;
	mapLibToSize[6] = 55;
	mapLibToSize[7] = 60;
	if (liberties > 7)
		liberties = 7;

	this.size = mapLibToSize[liberties];
};

//Group wraps an array of places
var nextGroupId = 0;
var Group = function(place) {
	this.id = nextGroupId++;
	this.liberties = [];
	this.color = place.color;
	this.stones = (place != null)?[place]:[];

	if (this.color != EMPTY) {
		//add adjacent stones/liberties to this group recursively
		var checkAdjacentPlacesRecurs = function(center, group) {
			var a, i;
			for (i in center.adjacentPlaces) {
				a = center.adjacentPlaces[i];

				if (group.stones.indexOf(a) == -1) {
					//adjacent stone not already part of group

					//add place as liberty (if not already added)
					if (a.color == EMPTY) {
						if (group.liberties.indexOf(a) == -1)
							group.liberties.push(a);
					}
					//add place as stone (if not already added)
					if (a.color == group.color) {
						if (group.stones.indexOf(a) == -1)
							group.stones.push(a);
						a.group = group;
						checkAdjacentPlacesRecurs(a, group);
					}
				}
			}
		};
		checkAdjacentPlacesRecurs(place, this);
	}
};
//*/

var Board = function(size) {
	this.size = size;
	this.moves = ['emptyBoard'];
	this.places = [];
	var places = this.places;

	this.prisoners = [];
	this.prisoners['b'] = 0;
	this.prisoners['w'] = 0;

	//fill places[] = new Place
	var x, y;
	for (x = 0; x < size; x++) {
		for (y = 0; y < size; y++) {
			places[y*size+x] = new Place(x, y);
		}
	}

	//fill places[].adjacents[] = the (usually) 4 adjacent places
	for (var x = 0; x < size; x++) {
		for (var y = 0; y < size; y++) {
			var a, adjacents = [];
			if ((a = this.getPlaceByXY(x+1, y)) != null) adjacents.push(a);
			if ((a = this.getPlaceByXY(x-1, y)) != null) adjacents.push(a);
			if ((a = this.getPlaceByXY(x, y+1)) != null) adjacents.push(a);
			if ((a = this.getPlaceByXY(x, y-1)) != null) adjacents.push(a);

			this.getPlaceByXY(x, y).adjacentPlaces = adjacents;
		}
	}
};

Board.prototype.refreshGroups = function() {
	var i;
	var p;
	for (i in this.places) {
		p = this.places[i];
		p.group = null;
	}

	for (i in this.places) {
		p = this.places[i];
		if (p.group == null) {
			p.group = new Group(p);
		}
	}

	//update stone sizes to match liberties
	for (i in this.places) {
		p = this.places[i];
		if (p.color != EMPTY)
			p.refreshSize();
	}
};

Board.prototype.getPlaceByXY = function(x, y) {
	var size = this.size;
	var places = this.places;
	if (x >= 0 && x < size && y >= 0 && y < size)
		return places[y*size+x];
	else
		return null;
};

Board.prototype.getTurnColor = function() {
	var moves = this.moves;
	var isBlacksTurn = moves[moves.length - 1].indexOf("b") != 0;
	return (isBlacksTurn)?"b":"w";
};

Board.prototype.placeStone = function(place) {
	if (place.color == EMPTY) {
		var color = this.getTurnColor();

		place.color = color;

		this.moves.push(color + place.x + "," + place.y);

		//capture stones?
		var i;
		var a;
		for (i in place.adjacentPlaces) {
			a = place.adjacentPlaces[i];
			if (a.group.color != "e") {
				a.group = new Group(a); //refresh group info about the adjacent stone a
				if (a.group.liberties.length == 0) {
					//capture(a.group);
					var j;
					var p;
					for (j in a.group.stones) {
						p = a.group.stones[j];
						this.prisoners[p.color]++;
						p.color = "e";
					}
				}
			}
		}

		//refresh groups
		this.refreshGroups();
	}
};

Board.prototype.pass = function() {
	var color = this.getTurnColor();
	this.moves.push(color + "Pass");
};

Board.prototype.undo = function() {
	alert('TODO: implement board.undo()');
};
/* Go game logic ^^ */

/* Controllers */

var myGoApp = angular.module('myGoApp', []);

myGoApp.controller('testCtrl', function($scope) {
	$scope.test = 0;
});

myGoApp.controller('goBoardCtrl', function($scope) {
	var size = 13;
	var squareSize = 60;
	$scope.board = new Board(size);
	var board = $scope.board;
	$scope.size = size;
	$scope.squareSize = squareSize;
	$scope.placeStone = function(place) {
		board.placeStone(place);
		$scope.debugPlace = place;
	};
	$scope.places = board.places; //shortcut
	$scope.hoveredPlace = null; //TODO: change to = false?

	$scope.alert = function(s) {alert(s)};
});

<|endoftext|>