<commit_before>Template.filter.events({
	'change .js-toggle-property-filter': function(event, instance) {
		event.preventDefault();

		var parentInstance = instance.parentInstance();

		parentInstance.filter.add('upcomingEvent', instance.$('#hasUpcomingEvent').prop('checked'));
		parentInstance.filter.add('needsHost', instance.$('#needsHost').prop('checked'));
		parentInstance.filter.add('needsMentor', instance.$('#needsMentor').prop('checked'));
		parentInstance.filter.done();

		parentInstance.updateUrl();
	},

	'mouseover .js-category-selection-label': function() {
		var category = this;
		courseFilterPreview(('.category-' + category), true);
		instance.$('.js-category-label.category-' + category).addClass('highlight');
	},

	'mouseout .js-category-selection-label': function() {
		var category = this;
		courseFilterPreview(('.category-' + category), false);
		instance.$('.js-category-label.category-' + category).removeClass('highlight');
	},

	'keyup .js-search-categories': function(event, instance) {
		var parentInstance = instance.parentInstance();
		var query = instance.$('.js-search-categories').val();

		parentInstance.updateCategorySearchDebounced(query);
	},

	'click .js-search-categories': function(event, instance) {
		instance.$('.dropdown-toggle').dropdown('toggle');
	},

	'click .js-toggle-subcategories': function(event, instance) {
		var category = this;

		instance.$(".js-sub-category" + "." + category).toggle();
		instance.$(".js-toggle-subcategories." + category + " span")
		        .toggleClass('fa-angle-down fa-angle-up');
		event.stopPropagation();
	},

	'click .js-category-selection-label': function(event, instance) {
		event.preventDefault();
		var parentInstance = instance.parentInstance();

		// Add to filter
		var category = this; // Event context is the category id
		parentInstance.filter.add('categories', "" + category).done();

		// Clear search field
		instance.$('.js-search-categories').val('');

		parentInstance.updateCategorySearch('');
		parentInstance.updateUrl();

		window.scrollTo(0, 0);
	},

	'click .js-remove-category-btn': function(event, instance) {
		var parentInstance = instance.parentInstance();
		var category = this;

		parentInstance.filter.remove('categories', '' + category).done();
		parentInstance.updateUrl();
		$('#find').select();
	},
});

Template.filter.helpers({
	'toggleChecked': function(name) {
		var parentInstance = Template.instance().parentInstance();
		return parentInstance.filter.get(name) ? 'checked' : '';
	},

	'categories': function() {
		var parentInstance = Template.instance().parentInstance();
		return parentInstance.filter.get('categories');
	},

	'availableCategories': function() {
		var parentInstance = Template.instance().parentInstance();
		return Object.keys(parentInstance.categorySearchResults.get('categorySearchResults'));
	},

	'availableSubcategories': function(mainCategory) {
		var parentInstance = Template.instance().parentInstance();
		return parentInstance.categorySearchResults.get()[mainCategory];
	},

	'categoryNameMarked': function() {
		Session.get('locale'); // Reactive dependency
		var parentInstance = Template.instance().parentInstance();
		var search = parentInstance.categorySearch.get();
		var name = mf('category.'+this);
		return markedName(search, name);
	},

	'isMobile': function() {
		var screenXS = SCSSVars.screenXS;
		return Session.get('viewportWidth') <= screenXS;
	}
});

Template.filter.onRendered(function() {
	var instance = this;

	instance.$('.js-filter-upcoming-events').on('mouseover mouseout', function(e) {
		var activate = e.type == 'mouseover';
		courseFilterPreview('.has-upcoming-events', activate);
	});

	instance.$('.js-filter-needs-host').on('mouseover mouseout', function(e) {
		var activate = e.type == 'mouseover';
		courseFilterPreview('.needs-host', activate);
	});

	instance.$('.js-filter-needs-mentor').on('mouseover mouseout', function(e) {
		var activate = e.type == 'mouseover';
		courseFilterPreview('.needs-mentor', activate);
	});

	instance.$('.filter-categories-select').on('show.bs.dropdown hide.bs.dropdown', function(e) {
		var dropdownMenu = $(e.target).find('.dropdown-menu').first();

		dropdownMenu.stop(true, true).slideToggle(200);
		instance.$('.dropdown-toggle').fadeToggle(200);
	});

	instance.$('.filter-categories-select').on('shown.bs.dropdown', function(e) {
		instance.$('.js-search-categories').select();
	});
});
<commit_msg>pass missing instance parameter<commit_after>Template.filter.events({
	'change .js-toggle-property-filter': function(event, instance) {
		event.preventDefault();

		var parentInstance = instance.parentInstance();

		parentInstance.filter.add('upcomingEvent', instance.$('#hasUpcomingEvent').prop('checked'));
		parentInstance.filter.add('needsHost', instance.$('#needsHost').prop('checked'));
		parentInstance.filter.add('needsMentor', instance.$('#needsMentor').prop('checked'));
		parentInstance.filter.done();

		parentInstance.updateUrl();
	},

	'mouseover .js-category-selection-label': function(event, instance) {
		var category = this;
		courseFilterPreview(('.category-' + category), true);
		instance.$('.js-category-label.category-' + category).addClass('highlight');
	},

	'mouseout .js-category-selection-label': function(event, instance) {
		var category = this;
		courseFilterPreview(('.category-' + category), false);
		instance.$('.js-category-label.category-' + category).removeClass('highlight');
	},

	'keyup .js-search-categories': function(event, instance) {
		var parentInstance = instance.parentInstance();
		var query = instance.$('.js-search-categories').val();

		parentInstance.updateCategorySearchDebounced(query);
	},

	'click .js-search-categories': function(event, instance) {
		instance.$('.dropdown-toggle').dropdown('toggle');
	},

	'click .js-toggle-subcategories': function(event, instance) {
		var category = this;

		instance.$(".js-sub-category" + "." + category).toggle();
		instance.$(".js-toggle-subcategories." + category + " span")
		        .toggleClass('fa-angle-down fa-angle-up');
		event.stopPropagation();
	},

	'click .js-category-selection-label': function(event, instance) {
		event.preventDefault();
		var parentInstance = instance.parentInstance();

		// Add to filter
		var category = this; // Event context is the category id
		parentInstance.filter.add('categories', "" + category).done();

		// Clear search field
		instance.$('.js-search-categories').val('');

		parentInstance.updateCategorySearch('');
		parentInstance.updateUrl();

		window.scrollTo(0, 0);
	},

	'click .js-remove-category-btn': function(event, instance) {
		var parentInstance = instance.parentInstance();
		var category = this;

		parentInstance.filter.remove('categories', '' + category).done();
		parentInstance.updateUrl();
		$('#find').select();
	},
});

Template.filter.helpers({
	'toggleChecked': function(name) {
		var parentInstance = Template.instance().parentInstance();
		return parentInstance.filter.get(name) ? 'checked' : '';
	},

	'categories': function() {
		var parentInstance = Template.instance().parentInstance();
		return parentInstance.filter.get('categories');
	},

	'availableCategories': function() {
		var parentInstance = Template.instance().parentInstance();
		return Object.keys(parentInstance.categorySearchResults.get('categorySearchResults'));
	},

	'availableSubcategories': function(mainCategory) {
		var parentInstance = Template.instance().parentInstance();
		return parentInstance.categorySearchResults.get()[mainCategory];
	},

	'categoryNameMarked': function() {
		Session.get('locale'); // Reactive dependency
		var parentInstance = Template.instance().parentInstance();
		var search = parentInstance.categorySearch.get();
		var name = mf('category.'+this);
		return markedName(search, name);
	},

	'isMobile': function() {
		var screenXS = SCSSVars.screenXS;
		return Session.get('viewportWidth') <= screenXS;
	}
});

Template.filter.onRendered(function() {
	var instance = this;

	instance.$('.js-filter-upcoming-events').on('mouseover mouseout', function(e) {
		var activate = e.type == 'mouseover';
		courseFilterPreview('.has-upcoming-events', activate);
	});

	instance.$('.js-filter-needs-host').on('mouseover mouseout', function(e) {
		var activate = e.type == 'mouseover';
		courseFilterPreview('.needs-host', activate);
	});

	instance.$('.js-filter-needs-mentor').on('mouseover mouseout', function(e) {
		var activate = e.type == 'mouseover';
		courseFilterPreview('.needs-mentor', activate);
	});

	instance.$('.filter-categories-select').on('show.bs.dropdown hide.bs.dropdown', function(e) {
		var dropdownMenu = $(e.target).find('.dropdown-menu').first();

		dropdownMenu.stop(true, true).slideToggle(200);
		instance.$('.dropdown-toggle').fadeToggle(200);
	});

	instance.$('.filter-categories-select').on('shown.bs.dropdown', function(e) {
		instance.$('.js-search-categories').select();
	});
});
<|endoftext|>


<commit_before>/*
  Note that the main run() loop will be executed once per frame as specified by WORLD_UPDATE_INTERVAL in worker.js.
  Behind the scenes, the engine just keeps on building up a cellData tree of all different
  state objects that are present within our current grid cell.
  The tree is a simple JSON object and needs to be in the format:

    {
      // player is a state type.
      player: {
        // ...
      },
      // You can add other custom state types.
      someType: {
        // Use the state id as the key (in the place of someId).
        // It is recommended that you use a random uuid as the state id.
        someId: {
          // All properties listed here are required.
          // You can add additional ones.
          id: someId,
          type: theValueOfSomeType,
          x: someXCoordinateWithinOurCurrentCell,
          y: someYCoordinateWithinOurCurrentCell,
        },
        anotherId: {
          // ...
        }
      }
    }

  You can add new type subtrees, new states and new properties to the cellData
  as you like. So long as you follow the structure above, the items will show
  up on the front end in the relevant cell in our world (see the handleCellData function in index.html).

  Adding new items to the cell is easy.
  For example, to add a new coin to the cell, you just need to add a state object to the coin subtree (E.g):
  cellData.coin[coin.id] = coin;

  See how coinManager.addCoin is used below (and how it's implemented) for more details.

  Note that states which are close to our current cell (based on WORLD_CELL_OVERLAP_DISTANCE)
  but not exactly inside it will still be visible within this cell (they will have an additional
  'external' property set to true).

  External states should not be modified because they belong to a different cell and the change will be ignored.
*/

var _ = require('lodash');
var rbush = require('rbush');
var SAT = require('sat');
var config = require('./config');
var BotManager = require('./bot-manager').BotManager;
var CoinManager = require('./coin-manager').CoinManager;

// This controller will be instantiated once for each
// cell in our world grid.

var CellController = function (options, util) {
  var self = this;

  this.options = options;
  this.cellIndex = options.cellIndex;
  this.util = util;

  this.worldColCount = Math.ceil(config.WORLD_WIDTH / config.WORLD_CELL_WIDTH);
  this.worldRowCount = Math.ceil(config.WORLD_HEIGHT / config.WORLD_CELL_HEIGHT);
  this.worldCellCount = this.worldColCount * this.worldRowCount;
  this.workerCount = options.worker.options.workers;

  this.coinMaxCount = Math.round(config.COIN_MAX_COUNT / this.worldCellCount);
  this.coinDropInterval = config.COIN_DROP_INTERVAL * this.worldCellCount;
  this.botCount = Math.round(config.BOT_COUNT / this.worldCellCount);

  var cellData = options.cellData;

  this.botManager = new BotManager({
    worldWidth: config.WORLD_WIDTH,
    worldHeight: config.WORLD_HEIGHT,
    botDefaultDiameter: config.BOT_DEFAULT_DIAMETER,
    botMoveSpeed: config.BOT_MOVE_SPEED,
    botMass: config.BOT_MASS,
    botChangeDirectionProbability: config.BOT_CHANGE_DIRECTION_PROBABILITY
  });

  if (!cellData.player) {
    cellData.player = {};
  }

  for (var b = 0; b < this.botCount; b++) {
    var bot = this.botManager.addBot();
    cellData.player[bot.id] = bot;
  }

  this.botMoves = [
    {u: 1},
    {d: 1},
    {r: 1},
    {l: 1}
  ];

  this.coinManager = new CoinManager({
    cellData: options.cellData,
    cellBounds: options.cellBounds,
    playerNoDropRadius: config.COIN_PLAYER_NO_DROP_RADIUS,
    coinMaxCount: this.coinMaxCount,
    coinDropInterval: this.coinDropInterval
  });

  this.lastCoinDrop = 0;

  config.COIN_TYPES.sort(function (a, b) {
    if (a.probability < b.probability) {
      return -1;
    }
    if (a.probability > b.probability) {
      return 1;
    }
    return 0;
  });

  this.coinTypes = [];
  var probRangeStart = 0;
  config.COIN_TYPES.forEach(function (coinType) {
    var coinTypeClone = _.cloneDeep(coinType);
    coinTypeClone.prob = probRangeStart;
    self.coinTypes.push(coinTypeClone);
    probRangeStart += coinType.probability;
  });

  this.playerCompareFn = function (a, b) {
    if (a.id < b.id) {
      return -1;
    }
    if (a.id > b.id) {
      return 1;
    }
    return 0;
  };

  this.diagonalSpeedFactor = Math.sqrt(1 / 2);
};

/*
  The main run loop for our cell controller.
*/
CellController.prototype.run = function (cellData) {
  if (!cellData.player) {
    cellData.player = {};
  }
  if (!cellData.coin) {
    cellData.coin = {};
  }
  var players = cellData.player;
  var coins = cellData.coin;

  // Sorting is important to achieve consistency across cells.
  var playerIds = Object.keys(players).sort(this.playerCompareFn);

  this.findPlayerOverlaps(playerIds, players, coins);
  this.dropCoins(coins);
  this.generateBotOps(playerIds, players);
  this.applyPlayerOps(playerIds, players, coins);
};

CellController.prototype.dropCoins = function (coins) {
  var now = Date.now();

  if (now - this.lastCoinDrop >= this.coinManager.coinDropInterval &&
    this.coinManager.coinCount < this.coinManager.coinMaxCount) {

    this.lastCoinDrop = now;

    var rand = Math.random();
    var chosenCoinType;

    var numTypes = this.coinTypes.length;
    for (var i = numTypes - 1; i >= 0; i--) {
      var curCoinType = this.coinTypes[i];
      if (rand >= curCoinType.prob) {
        chosenCoinType = curCoinType;
        break;
      }
    }

    if (!chosenCoinType) {
      throw new Error('There is something wrong with the coin probability distribution. ' +
        'Check that probabilities add up to 1 in COIN_TYPES config option.');
    }

    var coin = this.coinManager.addCoin(chosenCoinType.value, chosenCoinType.type, chosenCoinType.radius);
    if (coin) {
      coins[coin.id] = coin;
    }
  }
};

CellController.prototype.generateBotOps = function (playerIds, players, coins) {
  var self = this;

  playerIds.forEach(function (playerId) {
    var player = players[playerId];
    // States which are external are managed by a different cell, therefore changes made to these
    // states are not saved unless they are grouped with one or more internal states from the current cell.
    // See util.groupStates() method near the bottom of this file for details.
    if (player.subtype == 'bot' && !player.external) {
      var radius = Math.round(player.diam / 2);
      var isBotOnEdge = player.x <= radius || player.x >= config.WORLD_WIDTH - radius ||
        player.y <= radius || player.y >= config.WORLD_HEIGHT - radius;

      if (Math.random() <= player.changeDirProb || isBotOnEdge) {
        var randIndex = Math.floor(Math.random() * self.botMoves.length);
        player.repeatOp = self.botMoves[randIndex];
      }
      if (player.repeatOp) {
        player.op = player.repeatOp;
      }
    }
  });
};

CellController.prototype.keepPlayerOnGrid = function (player) {
  var radius = Math.round(player.diam / 2);

  var leftX = player.x - radius;
  var rightX = player.x + radius;
  var topY = player.y - radius;
  var bottomY = player.y + radius;

  if (leftX < 0) {
    player.x = radius;
  } else if (rightX > config.WORLD_WIDTH) {
    player.x = config.WORLD_WIDTH - radius;
  }
  if (topY < 0) {
    player.y = radius;
  } else if (bottomY > config.WORLD_HEIGHT) {
    player.y = config.WORLD_HEIGHT - radius;
  }
};

CellController.prototype.applyPlayerOps = function (playerIds, players, coins) {
  var self = this;

  playerIds.forEach(function (playerId) {
    var player = players[playerId];

    var playerOp = player.op;
    var moveSpeed;
    if (player.subtype == 'bot') {
      moveSpeed = player.speed;
    } else {
      moveSpeed = config.PLAYER_DEFAULT_MOVE_SPEED;
    }

    if (playerOp) {
      var movementVector = {x: 0, y: 0};
      var movedHorizontally = false;
      var movedVertically = false;

      if (playerOp.u) {
        movementVector.y = -moveSpeed;
        player.direction = 'up';
        movedVertically = true;
      }
      if (playerOp.d) {
        movementVector.y = moveSpeed;
        player.direction = 'down';
        movedVertically = true;
      }
      if (playerOp.r) {
        movementVector.x = moveSpeed;
        player.direction = 'right';
        movedHorizontally = true;
      }
      if (playerOp.l) {
        movementVector.x = -moveSpeed;
        player.direction = 'left';
        movedHorizontally = true;
      }

      if (movedHorizontally && movedVertically) {
        movementVector.x *= self.diagonalSpeedFactor;
        movementVector.y *= self.diagonalSpeedFactor;
      }

      player.x += movementVector.x;
      player.y += movementVector.y;
    }

    if (player.playerOverlaps) {
      player.playerOverlaps.forEach(function (otherPlayer) {
        self.resolvePlayerCollision(player, otherPlayer);
        self.keepPlayerOnGrid(otherPlayer);
      });
      delete player.playerOverlaps;
    }

    if (player.coinOverlaps) {
      player.coinOverlaps.forEach(function (coin) {
        if (self.testCircleCollision(player, coin).collided) {
          player.score += coin.v;
          self.coinManager.removeCoin(coin.id);
        }
      });
      delete player.coinOverlaps;
    }

    self.keepPlayerOnGrid(player);
  });
};

CellController.prototype.findPlayerOverlaps = function (playerIds, players, coins) {
  var self = this;

  var playerTree = new rbush();
  var hitAreaList = [];

  playerIds.forEach(function (playerId) {
    var player = players[playerId];
    player.hitArea = self.generateHitArea(player);
    hitAreaList.push(player.hitArea);
  });

  playerTree.load(hitAreaList);

  playerIds.forEach(function (playerId) {
    var player = players[playerId];
    playerTree.remove(player.hitArea);
    var hitList = playerTree.search(player.hitArea);
    playerTree.insert(player.hitArea);

    hitList.forEach(function (hit) {
      if (!player.playerOverlaps) {
        player.playerOverlaps = [];
      }
      player.playerOverlaps.push(hit.target);
    });
  });

  var coinIds = Object.keys(coins);
  coinIds.forEach(function (coinId) {
    var coin = coins[coinId];
    var coinHitArea = self.generateHitArea(coin);
    var hitList = playerTree.search(coinHitArea);

    if (hitList.length) {
      // If multiple players hit the coin, give it to a random one.
      var randomIndex = Math.floor(Math.random() * hitList.length);
      var coinWinner = hitList[randomIndex].target;

      if (!coinWinner.coinOverlaps) {
        coinWinner.coinOverlaps = [];
      }
      coinWinner.coinOverlaps.push(coin);
    }
  });

  playerIds.forEach(function (playerId) {
    delete players[playerId].hitArea;
  });
};

CellController.prototype.generateHitArea = function (target) {
  var targetRadius = target.r || Math.round(target.diam / 2);
  return {
    target: target,
    minX: target.x - targetRadius,
    minY: target.y - targetRadius,
    maxX: target.x + targetRadius,
    maxY: target.y + targetRadius
  };
};

CellController.prototype.testCircleCollision = function (a, b) {
  var radiusA = a.r || Math.round(a.diam / 2);
  var radiusB = b.r || Math.round(b.diam / 2);

  var circleA = new SAT.Circle(new SAT.Vector(a.x, a.y), radiusA);
  var circleB = new SAT.Circle(new SAT.Vector(b.x, b.y), radiusB);

  var response = new SAT.Response();
  var collided = SAT.testCircleCircle(circleA, circleB, response);

  return {
    collided: collided,
    overlapV: response.overlapV
  };
};

CellController.prototype.resolvePlayerCollision = function (player, otherPlayer) {
  var result = this.testCircleCollision(player, otherPlayer);

  if (result.collided) {
    var olv = result.overlapV;

    var totalMass = player.mass + otherPlayer.mass;
    var playerBuff = player.mass / totalMass;
    var otherPlayerBuff = otherPlayer.mass / totalMass;

    player.x -= olv.x * otherPlayerBuff;
    player.y -= olv.y * otherPlayerBuff;
    otherPlayer.x += olv.x * playerBuff;
    otherPlayer.y += olv.y * playerBuff;

    /*
      Whenever we have one state affecting the (x, y) coordinates of
      another state, we should group them together using the util.groupStates() function.
      Otherwise we will may get flicker when the two states interact across
      a cell boundary.
      In this case, if we don't use groupStates(), there will be flickering when you
      try to push another player across to a different cell.
    */
    this.util.groupStates([player, otherPlayer]);
  }
};

module.exports = CellController;
<commit_msg>Improved comment<commit_after>/*
  Note that the main run() loop will be executed once per frame as specified by WORLD_UPDATE_INTERVAL in worker.js.
  Behind the scenes, the engine just keeps on building up a cellData tree of all different
  state objects that are present within our current grid cell.
  The tree is a simple JSON object and needs to be in the format:

    {
      // player is a state type.
      player: {
        // ...
      },
      // You can add other custom state types.
      someType: {
        // Use the id as the key (in the place of someId).
        // It is recommended that you use a random uuid as the state id. See https://www.npmjs.com/package/uuid
        someId: {
          // All properties listed here are required.
          // You can add additional ones.
          id: someId,
          type: someType,
          x: someXCoordinate,
          y: someYCoordinate,
        },
        anotherId: {
          // ...
        }
      }
    }

  You can add new type subtrees, new states and new properties to the cellData
  as you like. So long as you follow the structure above, the items will show
  up on the front end in the relevant cell in our world (see the handleCellData function in index.html).

  Adding new items to the cell is easy.
  For example, to add a new coin to the cell, you just need to add a state object to the coin subtree (E.g):
  cellData.coin[coin.id] = coin;

  See how coinManager.addCoin is used below (and how it's implemented) for more details.

  Note that states which are close to our current cell (based on WORLD_CELL_OVERLAP_DISTANCE)
  but not exactly inside it will still be visible within this cell (they will have an additional
  'external' property set to true).

  External states should not be modified because they belong to a different cell and the change will be ignored.
*/

var _ = require('lodash');
var rbush = require('rbush');
var SAT = require('sat');
var config = require('./config');
var BotManager = require('./bot-manager').BotManager;
var CoinManager = require('./coin-manager').CoinManager;

// This controller will be instantiated once for each
// cell in our world grid.

var CellController = function (options, util) {
  var self = this;

  this.options = options;
  this.cellIndex = options.cellIndex;
  this.util = util;

  this.worldColCount = Math.ceil(config.WORLD_WIDTH / config.WORLD_CELL_WIDTH);
  this.worldRowCount = Math.ceil(config.WORLD_HEIGHT / config.WORLD_CELL_HEIGHT);
  this.worldCellCount = this.worldColCount * this.worldRowCount;
  this.workerCount = options.worker.options.workers;

  this.coinMaxCount = Math.round(config.COIN_MAX_COUNT / this.worldCellCount);
  this.coinDropInterval = config.COIN_DROP_INTERVAL * this.worldCellCount;
  this.botCount = Math.round(config.BOT_COUNT / this.worldCellCount);

  var cellData = options.cellData;

  this.botManager = new BotManager({
    worldWidth: config.WORLD_WIDTH,
    worldHeight: config.WORLD_HEIGHT,
    botDefaultDiameter: config.BOT_DEFAULT_DIAMETER,
    botMoveSpeed: config.BOT_MOVE_SPEED,
    botMass: config.BOT_MASS,
    botChangeDirectionProbability: config.BOT_CHANGE_DIRECTION_PROBABILITY
  });

  if (!cellData.player) {
    cellData.player = {};
  }

  for (var b = 0; b < this.botCount; b++) {
    var bot = this.botManager.addBot();
    cellData.player[bot.id] = bot;
  }

  this.botMoves = [
    {u: 1},
    {d: 1},
    {r: 1},
    {l: 1}
  ];

  this.coinManager = new CoinManager({
    cellData: options.cellData,
    cellBounds: options.cellBounds,
    playerNoDropRadius: config.COIN_PLAYER_NO_DROP_RADIUS,
    coinMaxCount: this.coinMaxCount,
    coinDropInterval: this.coinDropInterval
  });

  this.lastCoinDrop = 0;

  config.COIN_TYPES.sort(function (a, b) {
    if (a.probability < b.probability) {
      return -1;
    }
    if (a.probability > b.probability) {
      return 1;
    }
    return 0;
  });

  this.coinTypes = [];
  var probRangeStart = 0;
  config.COIN_TYPES.forEach(function (coinType) {
    var coinTypeClone = _.cloneDeep(coinType);
    coinTypeClone.prob = probRangeStart;
    self.coinTypes.push(coinTypeClone);
    probRangeStart += coinType.probability;
  });

  this.playerCompareFn = function (a, b) {
    if (a.id < b.id) {
      return -1;
    }
    if (a.id > b.id) {
      return 1;
    }
    return 0;
  };

  this.diagonalSpeedFactor = Math.sqrt(1 / 2);
};

/*
  The main run loop for our cell controller.
*/
CellController.prototype.run = function (cellData) {
  if (!cellData.player) {
    cellData.player = {};
  }
  if (!cellData.coin) {
    cellData.coin = {};
  }
  var players = cellData.player;
  var coins = cellData.coin;

  // Sorting is important to achieve consistency across cells.
  var playerIds = Object.keys(players).sort(this.playerCompareFn);

  this.findPlayerOverlaps(playerIds, players, coins);
  this.dropCoins(coins);
  this.generateBotOps(playerIds, players);
  this.applyPlayerOps(playerIds, players, coins);
};

CellController.prototype.dropCoins = function (coins) {
  var now = Date.now();

  if (now - this.lastCoinDrop >= this.coinManager.coinDropInterval &&
    this.coinManager.coinCount < this.coinManager.coinMaxCount) {

    this.lastCoinDrop = now;

    var rand = Math.random();
    var chosenCoinType;

    var numTypes = this.coinTypes.length;
    for (var i = numTypes - 1; i >= 0; i--) {
      var curCoinType = this.coinTypes[i];
      if (rand >= curCoinType.prob) {
        chosenCoinType = curCoinType;
        break;
      }
    }

    if (!chosenCoinType) {
      throw new Error('There is something wrong with the coin probability distribution. ' +
        'Check that probabilities add up to 1 in COIN_TYPES config option.');
    }

    var coin = this.coinManager.addCoin(chosenCoinType.value, chosenCoinType.type, chosenCoinType.radius);
    if (coin) {
      coins[coin.id] = coin;
    }
  }
};

CellController.prototype.generateBotOps = function (playerIds, players, coins) {
  var self = this;

  playerIds.forEach(function (playerId) {
    var player = players[playerId];
    // States which are external are managed by a different cell, therefore changes made to these
    // states are not saved unless they are grouped with one or more internal states from the current cell.
    // See util.groupStates() method near the bottom of this file for details.
    if (player.subtype == 'bot' && !player.external) {
      var radius = Math.round(player.diam / 2);
      var isBotOnEdge = player.x <= radius || player.x >= config.WORLD_WIDTH - radius ||
        player.y <= radius || player.y >= config.WORLD_HEIGHT - radius;

      if (Math.random() <= player.changeDirProb || isBotOnEdge) {
        var randIndex = Math.floor(Math.random() * self.botMoves.length);
        player.repeatOp = self.botMoves[randIndex];
      }
      if (player.repeatOp) {
        player.op = player.repeatOp;
      }
    }
  });
};

CellController.prototype.keepPlayerOnGrid = function (player) {
  var radius = Math.round(player.diam / 2);

  var leftX = player.x - radius;
  var rightX = player.x + radius;
  var topY = player.y - radius;
  var bottomY = player.y + radius;

  if (leftX < 0) {
    player.x = radius;
  } else if (rightX > config.WORLD_WIDTH) {
    player.x = config.WORLD_WIDTH - radius;
  }
  if (topY < 0) {
    player.y = radius;
  } else if (bottomY > config.WORLD_HEIGHT) {
    player.y = config.WORLD_HEIGHT - radius;
  }
};

CellController.prototype.applyPlayerOps = function (playerIds, players, coins) {
  var self = this;

  playerIds.forEach(function (playerId) {
    var player = players[playerId];

    var playerOp = player.op;
    var moveSpeed;
    if (player.subtype == 'bot') {
      moveSpeed = player.speed;
    } else {
      moveSpeed = config.PLAYER_DEFAULT_MOVE_SPEED;
    }

    if (playerOp) {
      var movementVector = {x: 0, y: 0};
      var movedHorizontally = false;
      var movedVertically = false;

      if (playerOp.u) {
        movementVector.y = -moveSpeed;
        player.direction = 'up';
        movedVertically = true;
      }
      if (playerOp.d) {
        movementVector.y = moveSpeed;
        player.direction = 'down';
        movedVertically = true;
      }
      if (playerOp.r) {
        movementVector.x = moveSpeed;
        player.direction = 'right';
        movedHorizontally = true;
      }
      if (playerOp.l) {
        movementVector.x = -moveSpeed;
        player.direction = 'left';
        movedHorizontally = true;
      }

      if (movedHorizontally && movedVertically) {
        movementVector.x *= self.diagonalSpeedFactor;
        movementVector.y *= self.diagonalSpeedFactor;
      }

      player.x += movementVector.x;
      player.y += movementVector.y;
    }

    if (player.playerOverlaps) {
      player.playerOverlaps.forEach(function (otherPlayer) {
        self.resolvePlayerCollision(player, otherPlayer);
        self.keepPlayerOnGrid(otherPlayer);
      });
      delete player.playerOverlaps;
    }

    if (player.coinOverlaps) {
      player.coinOverlaps.forEach(function (coin) {
        if (self.testCircleCollision(player, coin).collided) {
          player.score += coin.v;
          self.coinManager.removeCoin(coin.id);
        }
      });
      delete player.coinOverlaps;
    }

    self.keepPlayerOnGrid(player);
  });
};

CellController.prototype.findPlayerOverlaps = function (playerIds, players, coins) {
  var self = this;

  var playerTree = new rbush();
  var hitAreaList = [];

  playerIds.forEach(function (playerId) {
    var player = players[playerId];
    player.hitArea = self.generateHitArea(player);
    hitAreaList.push(player.hitArea);
  });

  playerTree.load(hitAreaList);

  playerIds.forEach(function (playerId) {
    var player = players[playerId];
    playerTree.remove(player.hitArea);
    var hitList = playerTree.search(player.hitArea);
    playerTree.insert(player.hitArea);

    hitList.forEach(function (hit) {
      if (!player.playerOverlaps) {
        player.playerOverlaps = [];
      }
      player.playerOverlaps.push(hit.target);
    });
  });

  var coinIds = Object.keys(coins);
  coinIds.forEach(function (coinId) {
    var coin = coins[coinId];
    var coinHitArea = self.generateHitArea(coin);
    var hitList = playerTree.search(coinHitArea);

    if (hitList.length) {
      // If multiple players hit the coin, give it to a random one.
      var randomIndex = Math.floor(Math.random() * hitList.length);
      var coinWinner = hitList[randomIndex].target;

      if (!coinWinner.coinOverlaps) {
        coinWinner.coinOverlaps = [];
      }
      coinWinner.coinOverlaps.push(coin);
    }
  });

  playerIds.forEach(function (playerId) {
    delete players[playerId].hitArea;
  });
};

CellController.prototype.generateHitArea = function (target) {
  var targetRadius = target.r || Math.round(target.diam / 2);
  return {
    target: target,
    minX: target.x - targetRadius,
    minY: target.y - targetRadius,
    maxX: target.x + targetRadius,
    maxY: target.y + targetRadius
  };
};

CellController.prototype.testCircleCollision = function (a, b) {
  var radiusA = a.r || Math.round(a.diam / 2);
  var radiusB = b.r || Math.round(b.diam / 2);

  var circleA = new SAT.Circle(new SAT.Vector(a.x, a.y), radiusA);
  var circleB = new SAT.Circle(new SAT.Vector(b.x, b.y), radiusB);

  var response = new SAT.Response();
  var collided = SAT.testCircleCircle(circleA, circleB, response);

  return {
    collided: collided,
    overlapV: response.overlapV
  };
};

CellController.prototype.resolvePlayerCollision = function (player, otherPlayer) {
  var result = this.testCircleCollision(player, otherPlayer);

  if (result.collided) {
    var olv = result.overlapV;

    var totalMass = player.mass + otherPlayer.mass;
    var playerBuff = player.mass / totalMass;
    var otherPlayerBuff = otherPlayer.mass / totalMass;

    player.x -= olv.x * otherPlayerBuff;
    player.y -= olv.y * otherPlayerBuff;
    otherPlayer.x += olv.x * playerBuff;
    otherPlayer.y += olv.y * playerBuff;

    /*
      Whenever we have one state affecting the (x, y) coordinates of
      another state, we should group them together using the util.groupStates() function.
      Otherwise we will may get flicker when the two states interact across
      a cell boundary.
      In this case, if we don't use groupStates(), there will be flickering when you
      try to push another player across to a different cell.
    */
    this.util.groupStates([player, otherPlayer]);
  }
};

module.exports = CellController;
<|endoftext|>


<commit_before>"use strict";

// A number of log probability density functions (PDF). Naming and parameterization
// should match R's, except for that each fucnction starts with ld as in
// "log density".
// Most of the code below is directly taken from the great Jstat project
// (https://github.com/jstat/) which includes PDF for many common probaility
// distributions. What I have done is only to convert these to log PDFs.

/*
Original work Copyright (c) 2013 jStat
Modified work Copyright (c) 2012 Rasmus Bååth

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

////////// Helper functions //////////

var gammaln = function(x) {
  var j = 0;
  var cof = [
    76.18009172947146, -86.50532032941677, 24.01409824083091,
    -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5
  ];
  var ser = 1.000000000190015;
  var xx, y, tmp;
  tmp = (y = xx = x) + 5.5;
  tmp -= (xx + 0.5) * log(tmp);
  for (; j < 6; j++)
    ser += cof[j] / ++y;
  return log(2.5066282746310005 * ser / xx) - tmp;
};

var factorialln = function(n) {
  return n < 0 ? NaN : gammaln(n + 1);
};

var combinationln = function(n, m){
  return factorialln(n) - factorialln(m) - factorialln(n - m);
};

var betaln = function(x, y) {
  return gammaln(x) + gammaln(y) - gammaln(x + y);
};

var log = Math.log;
var pow = Math.pow;
var sqrt = Math.sqrt;
var pi = Math.PI;

////////// Continous distributions //////////
var ldbeta = function(x, shape1, shape2) {
  if (x > 1 || x < 0) {
      return -Infinity;
  }
  if(shape1 === 1 && shape2 === 1) {
    return 0;
  } else {
    return (shape1 - 1) * log(x) + (shape2 - 1) * log(1 - x) - betaln(shape1, shape2);
  }
};

var ldcauchy = function(x, location, scale) {
  return log(scale) - log(pow(x - location, 2) + pow(scale, 2))  - log(pi);
};

var ldnorm = function(x, mean, sd) {
    return -0.5 * log(2 * pi) -log(sd) - pow(x - mean, 2) / (2 * sd * sd);
};

var ldgamma = function(x, shape, scale) {
  if (x < 0) {
    return -Infinity;
  }
  if((x === 0 && shape === 1) ) {
    return -log(scale);
  } else {
    return (shape - 1) * log(x) - x / scale - gammaln(shape) - shape * log(scale);
  }
};

var ldinvgamma = function(x, shape, scale) {
    if (x <= 0) {
      return -Infinity;
    }
    return -(shape + 1) * log(x) - scale / x - gammaln(shape) + shape * log(scale);
  };

var ldlnorm =  function(x, meanlog, sdlog) {
  if (x <= 0) {
    return -Infinity;
  }
  return -log(x) - 0.5 * log(2 * pi) - log(sdlog) -
          pow(log(x) - meanlog, 2) / (2 * sdlog * sdlog);
};

var ldpareto = function(x, scale, shape) {
  if (x < scale) {
    return -Infinity;
  }
  return log(shape) + shape * log(scale) - (shape + 1) * log(x);
};

var ldt  =  function(x, mu, sigma, nu) {
  nu = nu > 1e100 ? 1e100 : nu;
  return gammaln((nu + 1)/2) - gammaln(nu/2) - log(sqrt(pi * nu) * sigma) +
         log(pow(1 + (1/nu) * pow((x - mu)/sigma, 2), -(nu + 1)/2));
};

// This doesn't give the same answers as the R version dweibull,
// for example, when x = 0.0
var ldweibull = function(x, shape, scale) {
  if (x < 0)
    return -Infinity;
  return log(shape) - log(scale) + (shape - 1) * log((x / scale)) - pow(x / scale, shape);
};

var ldexp = function(x, rate) {
    return x < 0 ? -Infinity : log(rate) -rate * x;
};

var ldunif = function(x, min, max) {
    return (x < min || x > max) ? -Infinity : log(1 / (max - min));
};

////////// Discrete distributions //////////

var ldbern = function(x, prob) {
    return !(x === 0 || x === 1) ? -Infinity : log(x * prob + (1 - x) * (1 - prob));
};

var ldbinom = function(x, size, prob) {
  if(x > size || x < 0) {
    return -Infinity;
  }
  if(prob === 0 || prob === 1) {
    return (size * prob) === x ? 0 : -Infinity;
  }
  return combinationln(size, x) + x * log(prob) + (size - x) * log(1 - prob);
};

var ldnbinom = function(x, size, prob) {
  if(x < 0) {
    return -Infinity;
  }
  return combinationln(x + size - 1, size - 1) + x * log(1 - prob) + size * log(prob);
};

var ldpois = function(x, lambda) {
    return x < 0 ? -Infinity : log(lambda) * x - lambda - factorialln(x);
};

<commit_msg>Commented out ldweibull as it is currently not working correctly.<commit_after>"use strict";

// A number of log probability density functions (PDF). Naming and parameterization
// should match R's, except for that each function starts with ld as in
// "log density".
// Most of the code below is directly taken from the great Jstat project
// (https://github.com/jstat/) which includes PDF for many common probaility
// distributions. What I have done is only to convert these to log PDFs.

/*
Original work Copyright (c) 2013 jStat
Modified work Copyright (c) 2012 Rasmus Bååth

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

////////// Helper functions //////////

var gammaln = function(x) {
  var j = 0;
  var cof = [
    76.18009172947146, -86.50532032941677, 24.01409824083091,
    -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5
  ];
  var ser = 1.000000000190015;
  var xx, y, tmp;
  tmp = (y = xx = x) + 5.5;
  tmp -= (xx + 0.5) * log(tmp);
  for (; j < 6; j++)
    ser += cof[j] / ++y;
  return log(2.5066282746310005 * ser / xx) - tmp;
};

var factorialln = function(n) {
  return n < 0 ? NaN : gammaln(n + 1);
};

var combinationln = function(n, m){
  return factorialln(n) - factorialln(m) - factorialln(n - m);
};

var betaln = function(x, y) {
  return gammaln(x) + gammaln(y) - gammaln(x + y);
};

var log = Math.log;
var pow = Math.pow;
var sqrt = Math.sqrt;
var pi = Math.PI;

////////// Continous distributions //////////
var ldbeta = function(x, shape1, shape2) {
  if (x > 1 || x < 0) {
      return -Infinity;
  }
  if(shape1 === 1 && shape2 === 1) {
    return 0;
  } else {
    return (shape1 - 1) * log(x) + (shape2 - 1) * log(1 - x) - betaln(shape1, shape2);
  }
};

var ldcauchy = function(x, location, scale) {
  return log(scale) - log(pow(x - location, 2) + pow(scale, 2))  - log(pi);
};

var ldnorm = function(x, mean, sd) {
    return -0.5 * log(2 * pi) -log(sd) - pow(x - mean, 2) / (2 * sd * sd);
};

var ldgamma = function(x, shape, scale) {
  if (x < 0) {
    return -Infinity;
  }
  if((x === 0 && shape === 1) ) {
    return -log(scale);
  } else {
    return (shape - 1) * log(x) - x / scale - gammaln(shape) - shape * log(scale);
  }
};

var ldinvgamma = function(x, shape, scale) {
    if (x <= 0) {
      return -Infinity;
    }
    return -(shape + 1) * log(x) - scale / x - gammaln(shape) + shape * log(scale);
  };

var ldlnorm =  function(x, meanlog, sdlog) {
  if (x <= 0) {
    return -Infinity;
  }
  return -log(x) - 0.5 * log(2 * pi) - log(sdlog) -
          pow(log(x) - meanlog, 2) / (2 * sdlog * sdlog);
};

var ldpareto = function(x, scale, shape) {
  if (x < scale) {
    return -Infinity;
  }
  return log(shape) + shape * log(scale) - (shape + 1) * log(x);
};

var ldt  =  function(x, mu, sigma, nu) {
  nu = nu > 1e100 ? 1e100 : nu;
  return gammaln((nu + 1)/2) - gammaln(nu/2) - log(sqrt(pi * nu) * sigma) +
         log(pow(1 + (1/nu) * pow((x - mu)/sigma, 2), -(nu + 1)/2));
};

// Commented out for now as this doesn't give the same answers
// as the R version dweibull, for example, when x = 0.0 .
/*
var ldweibull = function(x, shape, scale) {
  if (x < 0)
    return -Infinity;
  return log(shape) - log(scale) + (shape - 1) * log((x / scale)) - pow(x / scale, shape);
};
*/

var ldexp = function(x, rate) {
    return x < 0 ? -Infinity : log(rate) -rate * x;
};

var ldunif = function(x, min, max) {
    return (x < min || x > max) ? -Infinity : log(1 / (max - min));
};

////////// Discrete distributions //////////

var ldbern = function(x, prob) {
    return !(x === 0 || x === 1) ? -Infinity : log(x * prob + (1 - x) * (1 - prob));
};

var ldbinom = function(x, size, prob) {
  if(x > size || x < 0) {
    return -Infinity;
  }
  if(prob === 0 || prob === 1) {
    return (size * prob) === x ? 0 : -Infinity;
  }
  return combinationln(size, x) + x * log(prob) + (size - x) * log(1 - prob);
};

var ldnbinom = function(x, size, prob) {
  if(x < 0) {
    return -Infinity;
  }
  return combinationln(x + size - 1, size - 1) + x * log(1 - prob) + size * log(prob);
};

var ldpois = function(x, lambda) {
    return x < 0 ? -Infinity : log(lambda) * x - lambda - factorialln(x);
};

<|endoftext|>


<commit_before>
var commandListEN = [
  // Motion commands. Can be used alone, or used with operator.
  { type:'motion', commands:[
    { keys:['h'],       help:'to left' },
    { keys:['j'],       help:'to down' },
    { keys:['k'],       help:'to up' },
    { keys:['l'],       help:'to right' },
    { keys:['<Left>'],  help:'to left' },
    { keys:['<Down>'],  help:'to down' },
    { keys:['<Up>'],    help:'to up' },
    { keys:['<Right>'], help:'to right' },

    { keys:['w'],       help:'a word' },
    { keys:['b'],       help:'a word backward' },

    { keys:['0'],       help:'to start of line' },
    { keys:['$'],       help:'to end of line' },

    { keys:['g','g'],   help:'to start of file' },
    { keys:['f','char'],help:'to given character', key_display:['f','target'] },
  ]},
  // Operator commands. Always used with either (i) motion,
  // (ii) a modifier + text-object (iii) itself, meaning linewise operation.
  { type:'operator', commands:[
    { keys:['d'],     help:'Delete' },
    { keys:['y'],     help:'Yank (copy)' },
    { keys:['c'],     help:'Change' },
    { keys:['p'],     help:'Paste' },
    { keys:['>'],     help:'Indent' },
    { keys:['<'],     help:'Unindent' },
  ]},
  // Action commands. Always used alone. Each one is complete as itself.
  { type:'action', commands:[
    { keys:['i'],     help:'Switch to insert mode' },
    { keys:['v'],     help:'Switch to visual mode' },
    { keys:['x'],     help:'Delete a character' },
    { keys:['u'],     help:'Undo' },
    { keys:['<C-r>'], help:'Redo' },
  ]},
  // Modifiers. Used before text object.
  { type:'modifier', commands:[
    { keys:['a'],     help:'Around' },
    { keys:['i'],     help:'Inside' },
  ]},
  // Text objects. Used after a modifier.
  { type:'textobj', commands:[
    { keys:['w'],     help:'Word' },
    { keys:['"'],     help:'Dobule quote' },
    { keys:['\''],    help:'Single quote' },
    { keys:['('],     help:'Parenthesis' },
    { keys:[')'],     help:'Parenthesis' },
    { keys:['{'],     help:'Braces' },
    { keys:['}'],     help:'Braces' },
    { keys:['['],     help:'Brackets' },
    { keys:[']'],     help:'Brackets' },
  ]},
  // Search commands.
  { type:'search', commands:[
    { keys:['/'],     help:'Search forward' },
  ]},
  // Ex commands.
  { type:'ex', commands:[
    { keys:[':'],     help:'Use ex command' },
  ]},
  // Esc is treated specially.
  { type:'done', commands:[
    { keys:['<Esc>'], help:'Cancel command' },
  ]},
];

// Interface to manipulate current command help at the bottom.
function HelpViewer(context) {

  var display = $("#helps-display", context);

  var getKeyObject = function(keys, help) {
    var div = $("

")
    var kbd = $("

");
    for (var i=0; i<keys.length; i++) {
      var key = keys[i];
      key = key.replace("<","<");
      key = key.replace(">",">");
      kbd.append($(""+key+""));
    }
    var txt = $("
"+help+"
");

    div.append(kbd).append(txt);
    return div;
  }

  var append = function(keys, help) {
    display.append(getKeyObject(keys, help));
  };

  var updateLast = function(keys, help) {
    var children = display.children();
    var last = $(children[children.length - 1])
    last.html(getKeyObject(keys, help));
  };

  var clear = function() {
    display.html("");
  };

  return {
    append: append,
    updateLast: updateLast,
    clear: clear,
  };
}

// Interface to suggested keys help at the right.
function KeysViewer(context) {
  var display = $("#keys-display");

  var appendType = function(bundle) {
    var div = $("

").addClass(bundle.type);
    var title = $("
"+bundle.type+"
");
    div.append(title);
    display.append(div);
    return div;
  };

  var getKeyObject = function(keys, help) {
    var div = $("

")
    for (var i=0; i<keys.length; i++) {
      var key = keys[i];
      key = key.replace("<","<");
      key = key.replace(">",">");
      div.append($(""+key+""));
    }
    var txt = $("  "+help+"");
    div.append(txt);

    return div;
  }

  var appendCommand = function(container, cmd) {
    container.append(getKeyObject(cmd.keys, cmd.help));
  };

  var init = function(commandList) {

    for (var i=0; i<commandList.length; i++) {
      var bundle = commandList[i];
      var container = appendType(bundle);

      for (var j=0; j<bundle.commands.length; j++) {
        var cmd = bundle.commands[j];
        appendCommand(container, cmd);
      }
    }
  };

  var update = function(filter) {
    for (var type in filter) {
      if (filter[type])
        $("."+type, display).show();
      else
        $("."+type, display).hide();
    }
  };

  return {
    init: init,
    update: update,
  };
}

// Simple model for vim command grammar.
function VimFSM(context) {
  var fsm = StateMachine.create({
    initial:'_none',
    events: [
      { name:'motion',   from:'_none',       to:'_simpleMotion'    },
      { name:'motion',   from:'_noneRepeat', to:'_simpleMotion'    },
      { name:'motion',   from:'_operator',   to:'_operatorsMotion' },
      { name:'motion',   from:'_opRepeat',   to:'_operatorsMotion' },

      { name:'operator', from:'_none',       to:'_operator'        },
      { name:'operator', from:'_noneRepeat', to:'_operator'        },
      { name:'operator', from:'_operator',   to:'_operatorLinewise'},

      { name:'action',   from:'_none',       to:'_action'          },
      { name:'action',   from:'_noneRepeat', to:'_action'          },

      { name:'modifier', from:'_operator',   to:'_modifier'        },
      { name:'textobj',  from:'_modifier',   to:'_textobj'         },

      { name:'search',   from:'_none',       to:'_search'          },
      { name:'ex',       from:'_none',       to:'_ex'              },

      { name:'done',     from:'*',           to:'_none'            },

      { name:'nonzero',  from:'_none',       to:'_noneRepeat'      },
      { name:'nonzero',  from:'_noneRepeat', to:'_noneRepeat'      },
      { name:'zero',     from:'_noneRepeat', to:'_noneRepeat'      },

      { name:'nonzero',  from:'_operator',   to:'_opRepeat'        },
      { name:'nonzero',  from:'_opRepeat',   to:'_opRepeat'        },
      { name:'zero',     from:'_opRepeat',   to:'_opRepeat'        },
  ]});
  fsm.events = ['motion','operator','action','modifier','textobj','search','ex'];
  return fsm;
}

// Outermost interface to overall help functionality.
function CommandHelper (commandList_, context) {

  var commandList = commandList_;

  var helpViewer = HelpViewer(context);
  var keysViewer = KeysViewer(context);

  var fsm = VimFSM(context);

  // Returns a default event handler for FSM.
  // The argument is optional function that decorates
  // the printing format of command.help.
  var helpFormat = function(f) {
    f = (typeof f !== 'undefined')? f : function(s) { return s; };
    return function(e, from, to, cmd) {
      helpViewer.append(cmd.keys, f(cmd.help));
    };
  };

  // Leaving _none state implies we start a new command.
  fsm.onleave_none = function() {
    helpViewer.clear();
  }

  // Describe the current command on state change.
  fsm.on_simpleMotion = helpFormat(function(s) { return "Move "+s});
  fsm.on_operatorsMotion = helpFormat();

  fsm.on_operator = helpFormat();
  fsm.on_operatorLinewise = helpFormat(function(s) { return "This line"});

  fsm.on_action = helpFormat();
  fsm.on_modifier = helpFormat();
  fsm.on_textobj = helpFormat();

  fsm.on_search = helpFormat();
  fsm.on_ex = helpFormat();

  fsm.onnonzero = function() {
    if (numBuf.length == 1)
      helpViewer.append(numBuf, "Repeat "+numBuf.join('')+" times.");
    else
      helpViewer.updateLast(numBuf, "Repeat "+numBuf.join('')+" times.");
  };
  fsm.onzero = fsm.onnonzero;

  var keyBuf = [];
  var numBuf = [];
  var matchCommand = function () {
    var match;
    for (var i=0; i<commandList.length; i++) {
      var bundle = commandList[i];

      if (fsm.can(bundle.type)) {
        for (var j=0; j<bundle.commands.length; j++) {
          var cmd = bundle.commands[j];

          if (compareKeys(cmd.keys, keyBuf)) {
            if (match)
              throw ("Duplicate command: " + keys.join());
            match = { type:bundle.type, keys:cmd.keys, help:cmd.help };
          }
        }
      }
    }
    return match;
  };

  var compareKeys = function (a, b) {
    if (a.length != b.length)
      return false;
    for (var i=0; i<a.length; i++) {
      if (a[i] != b[i] && a[i] != 'char')
        return false;
    }
    return true;
  };

  var matchPartial = function () {
    var matches = [];
    for (var i=0; i<commandList.length; i++) {
      var bundle = commandList[i];

      if (fsm.can(bundle.type)) {
        for (var j=0; j<bundle.commands.length; j++) {
          var cmd = bundle.commands[j];

          if (comparePartial(cmd.keys, keyBuf)) {
            matches.push({ type:bundle.type, keys:cmd.keys, help:cmd.help });
          }
        }
      }
    }
    return matches;
  };

  var comparePartial = function(a, b) {
    var minLength = (a.length < b.length)? a.length : b.length;
    if (minLength == 0)
      return false;
    for (var i=0; i<minLength; i++) {
      if (a[i] != b[i] && a[i] != 'char')
        return false;
    }
    return true;
  };

  var showKeys = function() {
    filter = [];
    for (var i=0; i<fsm.events.length; i++) {
      var e = fsm.events[i];
      if (fsm.can(e))
        filter[e] = true;
      else
        filter[e] = false;
    }
    keysViewer.update(filter);
  };

  var onKey = function(key) {
    keyBuf.push(key);
    var match;

    // '0' key is special case since it belongs
    // in both command and repeat.
    if (key != '0' || numBuf.length == 0)
      match = matchCommand();

    if (match) {
      keyBuf = [];
      numBuf = [];
      fsm[match.type](match);
      showKeys();
    }
    else {
      if (isNonzero(key) && fsm.can('nonzero') ||
          key == '0' && fsm.can('zero')) {

        // keyBuf is for command sequences, not repeat counts.
        numBuf.push(keyBuf.pop());
        if (key == '0') fsm.zero();
        else fsm.nonzero();
        showKeys();
      }
      else {
        var matches = matchPartial();
        if (matches.length == 0) {
          console.log("unknown command: " + keyBuf.join());
          keyBuf = [];
          numBuf = [];
          fsm.done();
          helpViewer.clear();
        }
        else {
          console.log("partial command: " + keyBuf.join());
        }
      }
    }
  };

  var isNonzero = function(key) {
    return (key.charCodeAt(0) >= '1'.charCodeAt(0)
         && key.charCodeAt(0) <= '9'.charCodeAt(0));
  };

  var init = function() {
    keysViewer.init(commandList);
  };

  var done = function() {
    fsm.done();
    showKeys();
  };

  return {
    onKey: onKey,
    init: init,
    done: done,
  };
}

var commandHelper = CommandHelper(commandListEN, window.body);

<commit_msg>[help] Display partial key input.<commit_after>
var commandListEN = [
  // Motion commands. Can be used alone, or used with operator.
  { type:'motion', commands:[
    { keys:['h'],       help:'to left' },
    { keys:['j'],       help:'to down' },
    { keys:['k'],       help:'to up' },
    { keys:['l'],       help:'to right' },
    { keys:['<Left>'],  help:'to left' },
    { keys:['<Down>'],  help:'to down' },
    { keys:['<Up>'],    help:'to up' },
    { keys:['<Right>'], help:'to right' },

    { keys:['w'],       help:'a word' },
    { keys:['b'],       help:'a word backward' },

    { keys:['0'],       help:'to start of line' },
    { keys:['$'],       help:'to end of line' },

    { keys:['g','g'],   help:'to start of file' },
    { keys:['f','char'],help:'to given character', key_display:['f','target'] },
  ]},
  // Operator commands. Always used with either (i) motion,
  // (ii) a modifier + text-object (iii) itself, meaning linewise operation.
  { type:'operator', commands:[
    { keys:['d'],     help:'Delete' },
    { keys:['y'],     help:'Yank (copy)' },
    { keys:['c'],     help:'Change' },
    { keys:['p'],     help:'Paste' },
    { keys:['>'],     help:'Indent' },
    { keys:['<'],     help:'Unindent' },
  ]},
  // Action commands. Always used alone. Each one is complete as itself.
  { type:'action', commands:[
    { keys:['i'],     help:'Switch to insert mode' },
    { keys:['v'],     help:'Switch to visual mode' },
    { keys:['x'],     help:'Delete a character' },
    { keys:['u'],     help:'Undo' },
    { keys:['<C-r>'], help:'Redo' },
  ]},
  // Modifiers. Used before text object.
  { type:'modifier', commands:[
    { keys:['a'],     help:'Around' },
    { keys:['i'],     help:'Inside' },
  ]},
  // Text objects. Used after a modifier.
  { type:'textobj', commands:[
    { keys:['w'],     help:'Word' },
    { keys:['"'],     help:'Dobule quote' },
    { keys:['\''],    help:'Single quote' },
    { keys:['('],     help:'Parenthesis' },
    { keys:[')'],     help:'Parenthesis' },
    { keys:['{'],     help:'Braces' },
    { keys:['}'],     help:'Braces' },
    { keys:['['],     help:'Brackets' },
    { keys:[']'],     help:'Brackets' },
  ]},
  // Search commands.
  { type:'search', commands:[
    { keys:['/'],     help:'Search forward' },
  ]},
  // Ex commands.
  { type:'ex', commands:[
    { keys:[':'],     help:'Use ex command' },
  ]},
  // Esc is treated specially.
  { type:'done', commands:[
    { keys:['<Esc>'], help:'Cancel command' },
  ]},
];

// Interface to manipulate current command help at the bottom.
function HelpViewer(context) {

  var display = $("#helps-display", context);

  var getKeyObject = function(keys, help) {
    var div = $("

")
    var kbd = $("

");
    for (var i=0; i<keys.length; i++) {
      var key = keys[i];
      key = key.replace("<","<");
      key = key.replace(">",">");
      kbd.append($(""+key+""));
    }
    var txt = $("
"+help+"
");

    div.append(kbd).append(txt);
    return div;
  }

  var append = function(keys, help) {
    display.append(getKeyObject(keys, help));
  };

  var updateLast = function(keys, help) {
    var children = display.children();
    var last = $(children[children.length - 1])
    last.html(getKeyObject(keys, help));
  };

  var clear = function() {
    display.html("");
  };

  return {
    append: append,
    updateLast: updateLast,
    clear: clear,
  };
}

// Interface to suggested keys help at the right.
function KeysViewer(context) {
  var display = $("#keys-display");

  var appendType = function(bundle) {
    var div = $("

").addClass(bundle.type);
    var title = $("
"+bundle.type+"
");
    div.append(title);
    display.append(div);
    return div;
  };

  var getKeyObject = function(keys, help) {
    var div = $("

")
    for (var i=0; i<keys.length; i++) {
      var key = keys[i];
      key = key.replace("<","<");
      key = key.replace(">",">");
      div.append($(""+key+""));
    }
    var txt = $("  "+help+"");
    div.append(txt);

    return div;
  }

  var appendCommand = function(container, cmd) {
    container.append(getKeyObject(cmd.keys, cmd.help));
  };

  var init = function(commandList) {

    for (var i=0; i<commandList.length; i++) {
      var bundle = commandList[i];
      var container = appendType(bundle);

      for (var j=0; j<bundle.commands.length; j++) {
        var cmd = bundle.commands[j];
        appendCommand(container, cmd);
      }
    }
  };

  var update = function(filter) {
    for (var type in filter) {
      if (filter[type])
        $("."+type, display).show();
      else
        $("."+type, display).hide();
    }
  };

  return {
    init: init,
    update: update,
  };
}

// Simple model for vim command grammar.
function VimFSM(context) {
  var fsm = StateMachine.create({
    initial:'_none',
    events: [
      { name:'motion',   from:'_none',       to:'_simpleMotion'    },
      { name:'motion',   from:'_noneRepeat', to:'_simpleMotion'    },
      { name:'motion',   from:'_operator',   to:'_operatorsMotion' },
      { name:'motion',   from:'_opRepeat',   to:'_operatorsMotion' },
      { name:'motion',   from:'_nonePartial',to:'_simpleMotion'    },

      { name:'operator', from:'_none',       to:'_operator'        },
      { name:'operator', from:'_noneRepeat', to:'_operator'        },
      { name:'operator', from:'_operator',   to:'_operatorLinewise'},

      { name:'action',   from:'_none',       to:'_action'          },
      { name:'action',   from:'_noneRepeat', to:'_action'          },

      { name:'modifier', from:'_operator',   to:'_modifier'        },
      { name:'textobj',  from:'_modifier',   to:'_textobj'         },

      { name:'search',   from:'_none',       to:'_search'          },
      { name:'ex',       from:'_none',       to:'_ex'              },

      { name:'done',     from:'*',           to:'_none'            },

      { name:'nonzero',  from:'_none',       to:'_noneRepeat'      },
      { name:'nonzero',  from:'_noneRepeat', to:'_noneRepeat'      },
      { name:'zero',     from:'_noneRepeat', to:'_noneRepeat'      },

      { name:'nonzero',  from:'_operator',   to:'_opRepeat'        },
      { name:'nonzero',  from:'_opRepeat',   to:'_opRepeat'        },
      { name:'zero',     from:'_opRepeat',   to:'_opRepeat'        },

      { name:'partial',  from:'_none',       to:'_nonePartial'     },
      { name:'partial',  from:'_nonePartial',to:'_nonePartial'     },
  ]});
  fsm.events = ['motion','operator','action','modifier','textobj','search','ex'];
  return fsm;
}

// Outermost interface to overall help functionality.
function CommandHelper (commandList_, context) {

  var commandList = commandList_;

  var helpViewer = HelpViewer(context);
  var keysViewer = KeysViewer(context);

  var fsm = VimFSM(context);

  // Returns a default event handler for FSM.
  // The argument is optional function that decorates
  // the printing format of command.help.
  var helpFormat = function(f) {
    f = (typeof f !== 'undefined')? f : function(s) { return s; };
    return function(e, from, to, cmd) {
      helpViewer.append(cmd.keys, f(cmd.help));
    };
  };

  // Leaving _none state implies we start a new command.
  fsm.onleave_none = function() {
    helpViewer.clear();
  }

  // Describe the current command on state change.
  fsm.on_simpleMotion = helpFormat(function(s) { return "Move "+s});
  fsm.on_operatorsMotion = helpFormat();

  fsm.on_operator = helpFormat();
  fsm.on_operatorLinewise = helpFormat(function(s) { return "This line"});

  fsm.on_action = helpFormat();
  fsm.on_modifier = helpFormat();
  fsm.on_textobj = helpFormat();

  fsm.on_search = helpFormat();
  fsm.on_ex = helpFormat();

  fsm.onnonzero = function() {
    if (numBuf.length == 1)
      helpViewer.append(numBuf, "Repeat "+numBuf.join('')+" times.");
    else
      helpViewer.updateLast(numBuf, "Repeat "+numBuf.join('')+" times.");
  };
  fsm.onzero = fsm.onnonzero;

  fsm.on_nonePartial = helpFormat();
  fsm.onleave_nonePartial = function() {
    helpViewer.clear();
  };

  var keyBuf = [];
  var numBuf = [];
  var matchCommand = function () {
    var match;
    for (var i=0; i<commandList.length; i++) {
      var bundle = commandList[i];

      if (fsm.can(bundle.type)) {
        for (var j=0; j<bundle.commands.length; j++) {
          var cmd = bundle.commands[j];

          if (compareKeys(cmd.keys, keyBuf)) {
            if (match)
              throw ("Duplicate command: " + keys.join());
            match = { type:bundle.type, keys:cmd.keys, help:cmd.help };
          }
        }
      }
    }
    return match;
  };

  var compareKeys = function (a, b) {
    if (a.length != b.length)
      return false;
    for (var i=0; i<a.length; i++) {
      if (a[i] != b[i] && a[i] != 'char')
        return false;
    }
    return true;
  };

  var matchPartial = function () {
    var matches = [];
    for (var i=0; i<commandList.length; i++) {
      var bundle = commandList[i];

      if (fsm.can(bundle.type)) {
        for (var j=0; j<bundle.commands.length; j++) {
          var cmd = bundle.commands[j];

          if (comparePartial(cmd.keys, keyBuf)) {
            matches.push({ type:bundle.type, keys:cmd.keys, help:cmd.help });
          }
        }
      }
    }
    return matches;
  };

  var comparePartial = function(a, b) {
    var minLength = (a.length < b.length)? a.length : b.length;
    if (minLength == 0)
      return false;
    for (var i=0; i<minLength; i++) {
      if (a[i] != b[i] && a[i] != 'char')
        return false;
    }
    return true;
  };

  var showKeys = function() {
    filter = [];
    for (var i=0; i<fsm.events.length; i++) {
      var e = fsm.events[i];
      if (fsm.can(e))
        filter[e] = true;
      else
        filter[e] = false;
    }
    keysViewer.update(filter);
  };

  var onKey = function(key) {
    keyBuf.push(key);
    var match;

    // '0' key is special case since it belongs
    // in both command and repeat.
    if (key != '0' || numBuf.length == 0)
      match = matchCommand();

    if (match) {
      keyBuf = [];
      numBuf = [];
      fsm[match.type](match);
      showKeys();
    }
    else {
      if (isNonzero(key) && fsm.can('nonzero') ||
          key == '0' && fsm.can('zero')) {

        // keyBuf is for command sequences, not repeat counts.
        numBuf.push(keyBuf.pop());
        if (key == '0') fsm.zero();
        else fsm.nonzero();
        showKeys();
      }
      else {
        var matches = matchPartial();
        if (matches.length == 0) {
          console.log("unknown command: " + keyBuf.join());
          keyBuf = [];
          numBuf = [];
          fsm.done();
          helpViewer.clear();
        }
        else {
          fsm.partial({ keys:keyBuf, help:'Finish this command.' });
        }
      }
    }
  };

  var isNonzero = function(key) {
    return (key.charCodeAt(0) >= '1'.charCodeAt(0)
         && key.charCodeAt(0) <= '9'.charCodeAt(0));
  };

  var init = function() {
    keysViewer.init(commandList);
  };

  var done = function() {
    fsm.done();
    showKeys();
  };

  return {
    onKey: onKey,
    init: init,
    done: done,
  };
}

var commandHelper = CommandHelper(commandListEN, window.body);

<|endoftext|>


<commit_before>;(function (root) {

  var STATE_SEPARATOR = /\s*>\s*/g,
      STATE_JOINER = ' > ',
      ACTION_VARIABLE_PATTERN = /=>\s*\w+\s*$/,
      SERVICE_KEY = '____service____',
      INITIAL_EVENT = '____init____',
      CONCLUDING_EVENT = '____conclude____',
      SPACE_NAME_PREFIX = '^',
      SERVICE_NAME_PREFIX = '@';

  var allSpaces = {};
  var allServices = {};
  var activeSpaces = {}; // TODO

  var slice = [].slice;

  var noop = function () {};

  var extend = function (extending, object) {
    for (var key in object) {
      extending[key] = object[key];
    }
    return extending;
  };

  var joinNames = function () {
    return slice.call(arguments).join('.');
  };

  var throwError = function () {
    throw 'error';
  };

  var evalString = function (string, variables) {
    return (new Function('v','with (v) return ' + string))(variables);
  };

  var buildStateTree = function (stateNames) {
    return stateNames.reduce(function (tree, stateName) {
      stateName.split(STATE_SEPARATOR).reduce(function (node, stateStep) {
        node = node[stateStep] || (node[stateStep] = {});
        node._events = {};
        return node;
      }, tree);
      return tree;
    }, { _events: {} });
  };

  var buildState = function (spaceName, stateName) {
    var space = allSpaces[spaceName];
    var node = space._stateTree;
    return stateName.split(STATE_SEPARATOR).reduce(function (state, step) {
      node = node[step];
      if (!node) debugger;
      for (var eventName in node._events) {
        state[eventName] = node._events[eventName];
      }
      return state;
    }, {});
  };

  var getStateNode = function (spaceName, stateName) {
    return stateName.split(STATE_SEPARATOR).reduce(function (node, step) {
      return node[step];
    }, allSpaces[spaceName]._stateTree);
  };

  var getValueKey = function (actionName) {
    var match = actionName.match(ACTION_VARIABLE_PATTERN);
    if (match) return match[0].replace(/\W+/g, '');
  };

  var extractVariableSections = function (string) {
    if (!(/\[/).test(string)) return [];
    // TODO fix this regexp for scenario like "abc [def: [[],[]]]"
    return (string.split(/(?:^.*?\[)|(?:\][^\]]*?\[)|(?:\][^\]]*?$)/g) || []).filter(identity);
  };

  var extractVariableAssignments = function (string) {
    return extractVariableSections(string).filter(function (section) {
      return (/:/).test(section);
    });
  };

  var extractVariableNames = function (string) {
    return extractVariableSections(string).map(function (section) {
      return section.split(':')[0].trim();
    });
  };

  var extractVariables = function (string, object) {
    object || (object = {});
    return extractVariableNames(string).reduce(function (variables, key) {
      variables[key] = object[key];
      return variables;
    }, {});
  };

  var getActionString = function (actionName) {
    actionName = actionName.replace(ACTION_VARIABLE_PATTERN, '').trim();
    var sections = extractVariableSections(actionName);
    var names = extractVariableNames(actionName);
    sections.forEach(function (section, i) {
      actionName = actionName.replace(section, names[i]);
    });
    return actionName;
  };

  var registerServiceCommand = function (spaceName, stateName, noticerName, eventName, serviceName, actionName) {
    var stateNode = getStateNode(spaceName, stateName);
    var eventKey = joinNames(noticerName, eventName);
    var previousFunction = stateNode._events[eventKey] || noop;
    var valueKey = getValueKey(actionName);
    var variableAssignments = extractVariableAssignments(actionName);
    actionName = getActionString(actionName);
    var variableKeys = extractVariableNames(actionName);
    stateNode._events[eventKey] = function (variables) {
      previousFunction(variables);
      variables[SERVICE_KEY] = allSpaces[spaceName]._services[serviceName];
      if (variableAssignments.length) extend(variables, evalString('{' + variableAssignments.join(',') + '}', variables));
      var value = evalString('/* ' + serviceName + ' */' + SERVICE_KEY + '["' + actionName + '"](' + variableKeys.join(',') + ')', variables);
      if (valueKey) variables[valueKey] = value;
      return value;
    };
  };

  var registerStateChange = function (spaceName, stateName, noticerName, eventName, nextStateName, variableString) {
    variableString || (variableString = '{}');
    var stateNode = getStateNode(spaceName, stateName);
    var eventKey = joinNames(noticerName, eventName);
    var previousFunction = stateNode._events[eventKey] || noop;
    stateNode._events[eventKey] = function (variables) {
      var value = previousFunction(variables);
      var additionalVariables = evalString(variableString, variables);
      variables = extend(extend({}, variables), additionalVariables);
      allSpaces[spaceName]._changeState(nextStateName, variables);
      return value;
    };
  };

  var registerPeerNotification = function (spaceName, stateName, noticerName, eventName, peerName, differentEventName, variableString) {
    differentEventName || (differentEventName = eventName);
    variableString || (variableString = '{}');
    var stateNode = getStateNode(spaceName, stateName);
    var eventKey = joinNames(noticerName, eventName);
    var previousFunction = stateNode._events[eventKey] || noop;
    stateNode._events[eventKey] = function (variables) {
      var value = previousFunction(variables);
      var additionalVariables = evalString(variableString, variables);
      variables = extend(extend({}, variables), additionalVariables);
      allSpaces[spaceName]._notify(peerName, differentEventName, variables);
      return value;
    };
  };

  var registerNoop = function (spaceName, stateName, noticerName, eventName) {
    var stateNode = getStateNode(spaceName, stateName);
    var eventKey = joinNames(noticerName, eventName);
    stateNode._events[eventKey] = noop;
  };

  var registerNote = function (spaceName, stateName, noticerName, eventName, noteString) {
    var stateNode = getStateNode(spaceName, stateName);
    var eventKey = joinNames(noticerName, eventName);
    var previousFunction = stateNode._events[eventKey] || noop;
    stateNode._events[eventKey] = function (variables) {
      previousFunction(variables);
      var additionalVariables = evalString('{' + noteString + '}', variables);
      extend(variables, additionalVariables);
    };
  };

  function Controller(spaceName, stateName) {
    var t = this;
    t._spaceName = spaceName;
    t._stateName = stateName;
    t._commands = [];
    t._accepting = true;
  }

  var ControllerPrototype = Controller.prototype;

  ControllerPrototype.when = function(serviceOrPeerName) {
    var t = this;
    if (!t._accepting) throw '"when" cannot be called here';
    t._noticerName = serviceOrPeerName;
    t._previousMethod = 'when';
    t._accepting = false;
    return t;
  };

  ControllerPrototype.notices = function(eventName) {
    var t = this;
    if (t._previousMethod != 'when') throw '"notices" cannot be called here';
    t._eventName = eventName;
    t._previousMethod = 'notices';
    t._accepting = true;
    return t;
  };

  ControllerPrototype.begins = function() {
    var t = this;
    if (t._previousMethod != 'when') throw '"begins" cannot be called here';
    t._eventName = INITIAL_EVENT;
    t._accepting = true;
    return t;
  };

  ControllerPrototype.ends = function() {
    var t = this;
    if (t._previousMethod != 'when') throw '"ends" cannot be called here';
    t._eventName = CONCLUDING_EVENT;
    t._accepting = true;
    return t;
  };

  ControllerPrototype.tell = function(serviceOrPeerName) {
    var t = this;
    if (!t._accepting) throw '"tell" cannot be called here';
    t._serviceOrPeerName = serviceOrPeerName;
    t._previousMethod = 'tell';
    t._accepting = false;
    return t;
  };

  ControllerPrototype.enter = function(stateName, variableString) {
    var t = this;
    if (!t._accepting) throw '"enter" cannot be called here';
    t._nextStateName = stateName;
    t._variableString = variableString;
    t._registerStateChange();
    return t;
  };

  ControllerPrototype.note = function(noteString) {
    var t = this;
    if (!t._accepting) throw '"note" cannot be called here';
    t._noteString = noteString;
    t._registerNote();
    return t;
  };

  ControllerPrototype.notify = function(peerName) {
    var t = this;
    if (!t._accepting) throw '"notify" cannot be called here';
    t._serviceOrPeerName = peerName;
    t._differentEventName = t._eventName;
    t._registerPeerNotification();
    return t;
  };

  ControllerPrototype.ignore = function() {
    var t = this;
    if (!t._accepting) throw '"ignore" cannot be called here';
    t._registerNoop();
  };

  ControllerPrototype.end = function() {
    // TODO
  };

  ControllerPrototype.to = function(actionName) {
    var t = this;
    if (t._accepting) throw '"to" cannot be called here';
    t._actionName = actionName;
    t._registerServiceCommand();
    t._accepting = true;
    return t;
  };

  ControllerPrototype.that = function(eventName, variableString) {
    var t = this;
    if (t._accepting) throw '"that" cannot be called here';
    t._differentEventName = eventName;
    t._variableString = variableString;
    t._registerPeerNotification();
    t._accepting = true;
    return t;
  };

  ControllerPrototype._registerServiceCommand = function () {
    var t = this;
    registerServiceCommand(t._spaceName, t._stateName, t._noticerName, t._eventName, t._serviceOrPeerName, t._actionName);
  };

  ControllerPrototype._registerPeerNotification = function () {
    var t = this;
    registerPeerNotification(t._spaceName, t._stateName, t._noticerName, t._eventName, t._serviceOrPeerName, t._differentEventName, t._variableString);
  };

  ControllerPrototype._registerStateChange = function () {
    var t = this;
    registerStateChange(t._spaceName, t._stateName, t._noticerName, t._eventName, t._nextStateName, t._variableString);
  };

  ControllerPrototype._registerNoop = function () {
    var t = this;
    registerNoop(t._spaceName, t._stateName, t._noticerName, t._eventName);
  };

  ControllerPrototype._registerNote = function () {
    var t = this;
    registerNote(t._spaceName, t._stateName, t._noticerName, t._eventName, t._noteString);
  };

  ControllerPrototype._registerConcludingEvent = function () {
    // TODO
    // var t = this;
    // registerConcludingEvent(t._spaceName, t._stateName, t._noticerName, t._eventName);
  };

  var stateDefiner = function (spaceName, stateName) {
    return {
      define: function (definition) {
        allSpaces[spaceName]._definitions.push(function () {
          new definition(new Controller(spaceName, stateName));
        });
        return allSpaces[spaceName];
      }
    };
  };

  var newNoticeFunction = function (spaceName, serviceName) {
    var space = allSpaces[spaceName];
    var notice = function (eventName, variables) {
      if (space._ended) return;
      var callback = space._currentState[joinNames(serviceName, eventName)];
      if (callback) {
        variables = extend(extend({}, space._stateVariables), extractVariables(eventName, variables));
        callback(variables || {});
      }
    };
    notice.cleanup = function (callback) {
      allSpaces[spaceName]._cleanupCallbacks.push(callback);
    }
    return notice;
  };

  var serviceDefiner = function (spaceName, serviceName) {
    serviceName = SERVICE_NAME_PREFIX + serviceName;
    return {
      define: function (definition) {
        allSpaces[spaceName]._definitions.push(function () {
          allSpaces[spaceName]._services[serviceName] = new definition(newNoticeFunction(spaceName, serviceName));
        });
        return allSpaces[spaceName];
      }
    };
  };

  var setContents = function (spaceName, contents) {
    contents = extend({}, contents);
    contents.events = contents.events.slice();
    contents.states = contents.states.slice();
    contents.services = contents.services.slice();
    contents.peers = contents.peers.slice();

    var space = allSpaces[spaceName];
    space._contents = contents;
    space._definitions = [];

    space.state = function (stateName) {
      return stateDefiner(spaceName, stateName);
    };
    space.service = function (serviceName) {
      return serviceDefiner(spaceName, serviceName);
    };
    var begin = function () {
      space._eventNames = contents.events.slice();
      space._unusedEventNames = contents.events.slice();
      space._serviceNames = contents.services.slice();
      space._unusedServiceNames = contents.services.slice();
      space._peerNames = contents.peers.slice();
      space._unusedPeerNames = contents.peers.slice();
      space._stateNames = contents.states.slice();
      space._unusedStateNames = contents.states.slice();
      space._stateTree = buildStateTree(contents.states);
      space._services = {};
      space._currentStateName = space._stateNames[0];
      space._currentState = null;
      space._stateVariables = {};
      space._cleanupCallbacks = [];
      space._begun = true;
      space._ended = false;
      space._changeState = function (nextStateName, variables) {
        space._stateVariables = extractVariables(nextStateName, variables);
        space._currentStateName = nextStateName;
        space._currentState = buildState(spaceName, nextStateName);
      };
      space._notify = function (peerName, eventName, variables) {
        newNoticeFunction(peerName, spaceName)(eventName, variables);
      };
      space._definitions.forEach(function (definition) { definition(); });
      space._currentState = buildState(spaceName, space._currentStateName);
      space.begin = throwError;
      space.end = function () {
        newNoticeFunction(spaceName, spaceName)(CONCLUDING_EVENT);
        space._cleanupCallbacks.forEach(function (callback) { callback(); });
        activeSpaces[spaceName] = null;
        var keepers = ['_contents','_definitions'];
        for (var key in space) {
          if (keepers.indexOf(key) == -1) {
            delete space[key];
          }
        }
        space._ended = true;
        space.end = throwError;
        space.begin = begin;
      };
      newNoticeFunction(spaceName, spaceName)(INITIAL_EVENT);
      return space;
    };
    space.begin = begin;
  };

  var newSpace = function (spaceName) {
    return {
      contents: function (contents) {
        setContents(spaceName, contents);
        this.contents = throwError;
        return this;
      }
    };
  };

  var newService = function (serviceName) {
    return {
      contents: function (contents) {
        // methods and events
        this.define = function (definition) {
          this._api = {
            notice: newNoticeFunction(null, serviceName)
          };
          definition.call(this._api, this._api);
        }
      }
    };
  }

  var namespace = function (name) {
    return {
      namespace: function (name2) {
        return namespace(joinNames(name, name2));
      },
      controller: function (controllerName) {
        var spaceName = SPACE_NAME_PREFIX + joinNames(name, controllerName);
        return allSpaces[spaceName] || (allSpaces[spaceName] = newSpace(spaceName));
      },
      service: function (serviceName) {
        var serviceName = joinNames(name, serviceName);
        return allServices[serviceName] || (allServices[serviceName] = newService(serviceName));
      },
      begin: function () {
        var nameDot = name + '.';
        Object.keys(allSpaces).forEach(function (key) {
          if (key.indexOf(nameDot) == (0 + SPACE_NAME_PREFIX.length)) {
            allSpaces[key].begin();
          }
        });
      },
      end: function () {
        var nameDot = name + '.';
        Object.keys(allSpaces).forEach(function (key) {
          if (key.indexOf(nameDot) == (0 + SPACE_NAME_PREFIX.length)) {
            allSpaces[key].end();
          }
        });
      }
    };
  };

  root.amnesiac = {
    namespace: namespace
  };

})(this);
<commit_msg>Include identity function<commit_after>;(function (root) {

  var STATE_SEPARATOR = /\s*>\s*/g,
      STATE_JOINER = ' > ',
      ACTION_VARIABLE_PATTERN = /=>\s*\w+\s*$/,
      SERVICE_KEY = '____service____',
      INITIAL_EVENT = '____init____',
      CONCLUDING_EVENT = '____conclude____',
      SPACE_NAME_PREFIX = '^',
      SERVICE_NAME_PREFIX = '@';

  var allSpaces = {};
  var allServices = {};
  var activeSpaces = {}; // TODO

  var slice = [].slice;

  var noop = function () {};

  var identity = function (value) { return value; };

  var extend = function (extending, object) {
    for (var key in object) {
      extending[key] = object[key];
    }
    return extending;
  };

  var joinNames = function () {
    return slice.call(arguments).join('.');
  };

  var throwError = function () {
    throw 'error';
  };

  var evalString = function (string, variables) {
    return (new Function('v','with (v) return ' + string))(variables);
  };

  var buildStateTree = function (stateNames) {
    return stateNames.reduce(function (tree, stateName) {
      stateName.split(STATE_SEPARATOR).reduce(function (node, stateStep) {
        node = node[stateStep] || (node[stateStep] = {});
        node._events = {};
        return node;
      }, tree);
      return tree;
    }, { _events: {} });
  };

  var buildState = function (spaceName, stateName) {
    var space = allSpaces[spaceName];
    var node = space._stateTree;
    return stateName.split(STATE_SEPARATOR).reduce(function (state, step) {
      node = node[step];
      if (!node) debugger;
      for (var eventName in node._events) {
        state[eventName] = node._events[eventName];
      }
      return state;
    }, {});
  };

  var getStateNode = function (spaceName, stateName) {
    return stateName.split(STATE_SEPARATOR).reduce(function (node, step) {
      return node[step];
    }, allSpaces[spaceName]._stateTree);
  };

  var getValueKey = function (actionName) {
    var match = actionName.match(ACTION_VARIABLE_PATTERN);
    if (match) return match[0].replace(/\W+/g, '');
  };

  var extractVariableSections = function (string) {
    if (!(/\[/).test(string)) return [];
    // TODO fix this regexp for scenario like "abc [def: [[],[]]]"
    return (string.split(/(?:^.*?\[)|(?:\][^\]]*?\[)|(?:\][^\]]*?$)/g) || []).filter(identity);
  };

  var extractVariableAssignments = function (string) {
    return extractVariableSections(string).filter(function (section) {
      return (/:/).test(section);
    });
  };

  var extractVariableNames = function (string) {
    return extractVariableSections(string).map(function (section) {
      return section.split(':')[0].trim();
    });
  };

  var extractVariables = function (string, object) {
    object || (object = {});
    return extractVariableNames(string).reduce(function (variables, key) {
      variables[key] = object[key];
      return variables;
    }, {});
  };

  var getActionString = function (actionName) {
    actionName = actionName.replace(ACTION_VARIABLE_PATTERN, '').trim();
    var sections = extractVariableSections(actionName);
    var names = extractVariableNames(actionName);
    sections.forEach(function (section, i) {
      actionName = actionName.replace(section, names[i]);
    });
    return actionName;
  };

  var registerServiceCommand = function (spaceName, stateName, noticerName, eventName, serviceName, actionName) {
    var stateNode = getStateNode(spaceName, stateName);
    var eventKey = joinNames(noticerName, eventName);
    var previousFunction = stateNode._events[eventKey] || noop;
    var valueKey = getValueKey(actionName);
    var variableAssignments = extractVariableAssignments(actionName);
    actionName = getActionString(actionName);
    var variableKeys = extractVariableNames(actionName);
    stateNode._events[eventKey] = function (variables) {
      previousFunction(variables);
      variables[SERVICE_KEY] = allSpaces[spaceName]._services[serviceName];
      if (variableAssignments.length) extend(variables, evalString('{' + variableAssignments.join(',') + '}', variables));
      var value = evalString('/* ' + serviceName + ' */' + SERVICE_KEY + '["' + actionName + '"](' + variableKeys.join(',') + ')', variables);
      if (valueKey) variables[valueKey] = value;
      return value;
    };
  };

  var registerStateChange = function (spaceName, stateName, noticerName, eventName, nextStateName, variableString) {
    variableString || (variableString = '{}');
    var stateNode = getStateNode(spaceName, stateName);
    var eventKey = joinNames(noticerName, eventName);
    var previousFunction = stateNode._events[eventKey] || noop;
    stateNode._events[eventKey] = function (variables) {
      var value = previousFunction(variables);
      var additionalVariables = evalString(variableString, variables);
      variables = extend(extend({}, variables), additionalVariables);
      allSpaces[spaceName]._changeState(nextStateName, variables);
      return value;
    };
  };

  var registerPeerNotification = function (spaceName, stateName, noticerName, eventName, peerName, differentEventName, variableString) {
    differentEventName || (differentEventName = eventName);
    variableString || (variableString = '{}');
    var stateNode = getStateNode(spaceName, stateName);
    var eventKey = joinNames(noticerName, eventName);
    var previousFunction = stateNode._events[eventKey] || noop;
    stateNode._events[eventKey] = function (variables) {
      var value = previousFunction(variables);
      var additionalVariables = evalString(variableString, variables);
      variables = extend(extend({}, variables), additionalVariables);
      allSpaces[spaceName]._notify(peerName, differentEventName, variables);
      return value;
    };
  };

  var registerNoop = function (spaceName, stateName, noticerName, eventName) {
    var stateNode = getStateNode(spaceName, stateName);
    var eventKey = joinNames(noticerName, eventName);
    stateNode._events[eventKey] = noop;
  };

  var registerNote = function (spaceName, stateName, noticerName, eventName, noteString) {
    var stateNode = getStateNode(spaceName, stateName);
    var eventKey = joinNames(noticerName, eventName);
    var previousFunction = stateNode._events[eventKey] || noop;
    stateNode._events[eventKey] = function (variables) {
      previousFunction(variables);
      var additionalVariables = evalString('{' + noteString + '}', variables);
      extend(variables, additionalVariables);
    };
  };

  function Controller(spaceName, stateName) {
    var t = this;
    t._spaceName = spaceName;
    t._stateName = stateName;
    t._commands = [];
    t._accepting = true;
  }

  var ControllerPrototype = Controller.prototype;

  ControllerPrototype.when = function(serviceOrPeerName) {
    var t = this;
    if (!t._accepting) throw '"when" cannot be called here';
    t._noticerName = serviceOrPeerName;
    t._previousMethod = 'when';
    t._accepting = false;
    return t;
  };

  ControllerPrototype.notices = function(eventName) {
    var t = this;
    if (t._previousMethod != 'when') throw '"notices" cannot be called here';
    t._eventName = eventName;
    t._previousMethod = 'notices';
    t._accepting = true;
    return t;
  };

  ControllerPrototype.begins = function() {
    var t = this;
    if (t._previousMethod != 'when') throw '"begins" cannot be called here';
    t._eventName = INITIAL_EVENT;
    t._accepting = true;
    return t;
  };

  ControllerPrototype.ends = function() {
    var t = this;
    if (t._previousMethod != 'when') throw '"ends" cannot be called here';
    t._eventName = CONCLUDING_EVENT;
    t._accepting = true;
    return t;
  };

  ControllerPrototype.tell = function(serviceOrPeerName) {
    var t = this;
    if (!t._accepting) throw '"tell" cannot be called here';
    t._serviceOrPeerName = serviceOrPeerName;
    t._previousMethod = 'tell';
    t._accepting = false;
    return t;
  };

  ControllerPrototype.enter = function(stateName, variableString) {
    var t = this;
    if (!t._accepting) throw '"enter" cannot be called here';
    t._nextStateName = stateName;
    t._variableString = variableString;
    t._registerStateChange();
    return t;
  };

  ControllerPrototype.note = function(noteString) {
    var t = this;
    if (!t._accepting) throw '"note" cannot be called here';
    t._noteString = noteString;
    t._registerNote();
    return t;
  };

  ControllerPrototype.notify = function(peerName) {
    var t = this;
    if (!t._accepting) throw '"notify" cannot be called here';
    t._serviceOrPeerName = peerName;
    t._differentEventName = t._eventName;
    t._registerPeerNotification();
    return t;
  };

  ControllerPrototype.ignore = function() {
    var t = this;
    if (!t._accepting) throw '"ignore" cannot be called here';
    t._registerNoop();
  };

  ControllerPrototype.end = function() {
    // TODO
  };

  ControllerPrototype.to = function(actionName) {
    var t = this;
    if (t._accepting) throw '"to" cannot be called here';
    t._actionName = actionName;
    t._registerServiceCommand();
    t._accepting = true;
    return t;
  };

  ControllerPrototype.that = function(eventName, variableString) {
    var t = this;
    if (t._accepting) throw '"that" cannot be called here';
    t._differentEventName = eventName;
    t._variableString = variableString;
    t._registerPeerNotification();
    t._accepting = true;
    return t;
  };

  ControllerPrototype._registerServiceCommand = function () {
    var t = this;
    registerServiceCommand(t._spaceName, t._stateName, t._noticerName, t._eventName, t._serviceOrPeerName, t._actionName);
  };

  ControllerPrototype._registerPeerNotification = function () {
    var t = this;
    registerPeerNotification(t._spaceName, t._stateName, t._noticerName, t._eventName, t._serviceOrPeerName, t._differentEventName, t._variableString);
  };

  ControllerPrototype._registerStateChange = function () {
    var t = this;
    registerStateChange(t._spaceName, t._stateName, t._noticerName, t._eventName, t._nextStateName, t._variableString);
  };

  ControllerPrototype._registerNoop = function () {
    var t = this;
    registerNoop(t._spaceName, t._stateName, t._noticerName, t._eventName);
  };

  ControllerPrototype._registerNote = function () {
    var t = this;
    registerNote(t._spaceName, t._stateName, t._noticerName, t._eventName, t._noteString);
  };

  ControllerPrototype._registerConcludingEvent = function () {
    // TODO
    // var t = this;
    // registerConcludingEvent(t._spaceName, t._stateName, t._noticerName, t._eventName);
  };

  var stateDefiner = function (spaceName, stateName) {
    return {
      define: function (definition) {
        allSpaces[spaceName]._definitions.push(function () {
          new definition(new Controller(spaceName, stateName));
        });
        return allSpaces[spaceName];
      }
    };
  };

  var newNoticeFunction = function (spaceName, serviceName) {
    var space = allSpaces[spaceName];
    var notice = function (eventName, variables) {
      if (space._ended) return;
      var callback = space._currentState[joinNames(serviceName, eventName)];
      if (callback) {
        variables = extend(extend({}, space._stateVariables), extractVariables(eventName, variables));
        callback(variables || {});
      }
    };
    notice.cleanup = function (callback) {
      allSpaces[spaceName]._cleanupCallbacks.push(callback);
    }
    return notice;
  };

  var serviceDefiner = function (spaceName, serviceName) {
    serviceName = SERVICE_NAME_PREFIX + serviceName;
    return {
      define: function (definition) {
        allSpaces[spaceName]._definitions.push(function () {
          allSpaces[spaceName]._services[serviceName] = new definition(newNoticeFunction(spaceName, serviceName));
        });
        return allSpaces[spaceName];
      }
    };
  };

  var setContents = function (spaceName, contents) {
    contents = extend({}, contents);
    contents.events = contents.events.slice();
    contents.states = contents.states.slice();
    contents.services = contents.services.slice();
    contents.peers = contents.peers.slice();

    var space = allSpaces[spaceName];
    space._contents = contents;
    space._definitions = [];

    space.state = function (stateName) {
      return stateDefiner(spaceName, stateName);
    };
    space.service = function (serviceName) {
      return serviceDefiner(spaceName, serviceName);
    };
    var begin = function () {
      space._eventNames = contents.events.slice();
      space._unusedEventNames = contents.events.slice();
      space._serviceNames = contents.services.slice();
      space._unusedServiceNames = contents.services.slice();
      space._peerNames = contents.peers.slice();
      space._unusedPeerNames = contents.peers.slice();
      space._stateNames = contents.states.slice();
      space._unusedStateNames = contents.states.slice();
      space._stateTree = buildStateTree(contents.states);
      space._services = {};
      space._currentStateName = space._stateNames[0];
      space._currentState = null;
      space._stateVariables = {};
      space._cleanupCallbacks = [];
      space._begun = true;
      space._ended = false;
      space._changeState = function (nextStateName, variables) {
        space._stateVariables = extractVariables(nextStateName, variables);
        space._currentStateName = nextStateName;
        space._currentState = buildState(spaceName, nextStateName);
      };
      space._notify = function (peerName, eventName, variables) {
        newNoticeFunction(peerName, spaceName)(eventName, variables);
      };
      space._definitions.forEach(function (definition) { definition(); });
      space._currentState = buildState(spaceName, space._currentStateName);
      space.begin = throwError;
      space.end = function () {
        newNoticeFunction(spaceName, spaceName)(CONCLUDING_EVENT);
        space._cleanupCallbacks.forEach(function (callback) { callback(); });
        activeSpaces[spaceName] = null;
        var keepers = ['_contents','_definitions'];
        for (var key in space) {
          if (keepers.indexOf(key) == -1) {
            delete space[key];
          }
        }
        space._ended = true;
        space.end = throwError;
        space.begin = begin;
      };
      newNoticeFunction(spaceName, spaceName)(INITIAL_EVENT);
      return space;
    };
    space.begin = begin;
  };

  var newSpace = function (spaceName) {
    return {
      contents: function (contents) {
        setContents(spaceName, contents);
        this.contents = throwError;
        return this;
      }
    };
  };

  var newService = function (serviceName) {
    return {
      contents: function (contents) {
        // methods and events
        this.define = function (definition) {
          this._api = {
            notice: newNoticeFunction(null, serviceName)
          };
          definition.call(this._api, this._api);
        }
      }
    };
  }

  var namespace = function (name) {
    return {
      namespace: function (name2) {
        return namespace(joinNames(name, name2));
      },
      controller: function (controllerName) {
        var spaceName = SPACE_NAME_PREFIX + joinNames(name, controllerName);
        return allSpaces[spaceName] || (allSpaces[spaceName] = newSpace(spaceName));
      },
      service: function (serviceName) {
        var serviceName = joinNames(name, serviceName);
        return allServices[serviceName] || (allServices[serviceName] = newService(serviceName));
      },
      begin: function () {
        var nameDot = name + '.';
        Object.keys(allSpaces).forEach(function (key) {
          if (key.indexOf(nameDot) == (0 + SPACE_NAME_PREFIX.length)) {
            allSpaces[key].begin();
          }
        });
      },
      end: function () {
        var nameDot = name + '.';
        Object.keys(allSpaces).forEach(function (key) {
          if (key.indexOf(nameDot) == (0 + SPACE_NAME_PREFIX.length)) {
            allSpaces[key].end();
          }
        });
      }
    };
  };

  root.amnesiac = {
    namespace: namespace
  };

})(this);
<|endoftext|>