<commit_before>'use strict';

window.Darwinator.GeneticAlgorithm = window.Darwinator.GeneticAlgorithm || {

  POPULATION_SIZE:          10,
  CROSSOVER_PROBABILITY:    0.8,
  MUTATION_PROBABILITY:     0.025,
  TOURNAMENT_PARAMETER:     0.75,
  NUMBER_OF_GENERATIONS:    100,
  NUMBER_OF_VARIABLES:      3,
  NUMBER_OF_GENES:          3,
  TOURNAMENT_SIZE:          4,
  ELITISM_DEGREE:           2,
  PLAYER_ADVANTAGE:         10, // increase to make enemies weaker

  // depends on player attributes
  VARIABLE_RANGE:           undefined, // max sum of enemy attributes

  // depends on population success
  MUTATION_RATE:            undefined,
  POOR_MAX_FITNESS:         0.1,  // used to set mutation rate

  /**
  * Generates a population of individuals from a given population. The new population is likely to be
  * more adapted to find a solution to the enemyScore() function.
  *
  * @method Darwinator.GeneticAlgorithm#generatePopulation
  * @param {Phaser.Game} [game] - The game that uses the algorithm.
  * @param {Phaser.Sprite} [target] - The sprite to be attacked by the enemies.
  * @param {Phaser.Group} [enemyGroup] - Optional. If not provided, a group with default values will be set.
  * @param {Boolean} [singleGeneration] - If true, only one iteration will be run, else, this.NUMBER_OF_GENERATIONS is used.
  * @param {Array} [spawnPositions] - The positions on which the enemies are allowed to spawn.
  * @return {Array} The last population generated.
  */
  generatePopulation: function(game, target, enemyGroup, singleGeneration, spawnPositions) {
    var attributes      = target.attributes;
    this.VARIABLE_RANGE = attributes.strength + attributes.agility + attributes.intellect - this.PLAYER_ADVANTAGE;
    spawnPositions      = Darwinator.Helpers.shuffle(spawnPositions);

    if(!enemyGroup){
      enemyGroup = game.add.group();
      console.log('GA: Enemy group not provided. Initializing with default values.');
      enemyGroup = this.initPopulation(enemyGroup, game, target, spawnPositions);
      return enemyGroup;
    }
    console.log('GA: Starting GA run..');
    var translatedEnemies = this.translateEnemyWave(enemyGroup);
    var population        = translatedEnemies[0];
    var fitnessLevels     = translatedEnemies[1];
    var bestIndex         = translatedEnemies[2];
    var bestIndividual    = population[bestIndex];

    // to eliminate weak populations
    if(fitnessLevels[bestIndex] <= this.POOR_MAX_FITNESS){
      this.MUTATION_RATE = 20;
    }else{
      this.MUTATION_RATE = 1;
    }

    // algorithm main loop
    for (var i = 0; i < (singleGeneration ? 1 : this.NUMBER_OF_GENERATIONS); i++) {

      // clone population
      var tmpPopulation = population.slice(0);

      // selection
      for(var l = 0; l < population.length; l += 2) {
        var ind1 = this.selection(fitnessLevels);
        var ind2 = this.selection(fitnessLevels);
        // crossover
        if (Math.random() < this.CROSSOVER_PROBABILITY) {
          var chromePair        = this.cross(population[ind1], population[ind2]);
          tmpPopulation[l]      = chromePair[0];
          tmpPopulation[l + 1]  = chromePair[1];
        } else {
          tmpPopulation[l]      = population[ind1];
          tmpPopulation[l + 1]  = population[ind2];
        }
        // mutation
        tmpPopulation[l]    = this.mutate(tmpPopulation[l]);
        tmpPopulation[l+1]  = this.mutate(tmpPopulation[l+1]);
      }

      if(fitnessLevels[bestIndex] > this.POOR_MAX_FITNESS){
        // elitism
        for(l = 0; l < this.ELITISM_DEGREE; l++) {
          tmpPopulation[l] = bestIndividual;
        }
      }

      // replace old population
      population = tmpPopulation;
    } //endof algorithm main loop

    var nextGeneration = this.translatePopulation(population, enemyGroup, game, target, spawnPositions);
    console.log('GA: Returning the new generation!');
    return nextGeneration;
  },

  /**
  * Generates a group of enemy sprites with some default values.
  *
  * @method Darwinator.GeneticAlgorithm#initPopulation
  * @param {Phaser.Group} [enemyGroup] - The enemy group to fill with enemies.
  * @param {Phaser.Game} [game] - The game that uses the algorithm.
  * @param {Phaser.Sprite} [target] - The sprite to be attacked by the enemies.
  * @param {Array} [spawnPositions] - The positions on which the enemies are allowed to spawn.
  * @return {Array} An enemy population with default attribute values.
  */
  initPopulation: function(enemyGroup, game, target, spawnPositions) {
    // 4 different enemy initial set of attributes - just an example, not permanent!
    var enemiesPerType  = this.POPULATION_SIZE / 5;
    var pos;
    var strength, agility, intellect;
    for(var i = 0; i < this.POPULATION_SIZE; i++){
      pos = spawnPositions[i % spawnPositions.length];
      if(i < enemiesPerType){
        // smart but slow and weak
        strength  = 0;
        agility   = 0;
        intellect = this.VARIABLE_RANGE;
      }else if(i < enemiesPerType*2){
        // strong but slow and stupid
        strength  = this.VARIABLE_RANGE;
        agility   = 0;
        intellect = 0;
      }else if(i < enemiesPerType*3){
        // fast but weak and stupid
        strength  = 0;
        agility   = this.VARIABLE_RANGE;
        intellect = 0;
      }else{
        // hybrid
        strength  = agility = intellect = Math.round(this.VARIABLE_RANGE / 3);
      }
      enemyGroup.add(new Darwinator.Enemy(game, target, pos.x, pos.y, undefined, strength, agility, intellect));
    }
    return enemyGroup;
  },

  /**
  * Cross two individuals to create two new ones by performing a tradeoff between
  * two randomly selected genes in each individual.
  *
  * Example: Select index of x and y.
  * First individual: decrease x and increase y
  * Second individual: decrease y and increase x
  *
  * @method Darwinator.GeneticAlgorithm#cross
  * @param {Array} [firstInd] - The first individual to be crossed
  * @param {Array} [secondInd] - The second individual to be crossed
  * @return {Array} - A tuple containing the two new individuals.
  */
  cross: function(firstInd, secondInd) {
    var crossPoint1 = Math.round(Math.random()*(this.NUMBER_OF_GENES - 1));
    var crossPoint2 = Math.round(Math.random()*(this.NUMBER_OF_GENES - 1));

    var tradeOff        = Math.random();
    var tradeOffAmount1 = Math.round(tradeOff * firstInd[crossPoint1]);
    var tradeOffAmount2 = Math.round(tradeOff * secondInd[crossPoint2]);

    var newInd1 = firstInd.slice(0);
    var newInd2 = secondInd.slice(0);

    newInd1[crossPoint1] -= tradeOffAmount1;
    newInd1[crossPoint2] += tradeOffAmount1;

    newInd2[crossPoint2] -= tradeOffAmount2;
    newInd2[crossPoint1] += tradeOffAmount2;

    return [newInd1, newInd2];
  },

  /**
  * Select a individual based on the fitness levels of the entire population.
  * this.TOURNAMENT_SIZE many individuals will be selected at random, and one of them will
  * be returned. The tournament works in such a way that a individual with a high fitness level
  * is more likely to be returned from the tournament, but is not more likely to participate.
  *
  * @method Darwinator.GeneticAlgorithm#selection
  * @param {Array} - The fitness levels of the entire population
  * @return {Number} - The index of the selected individual.
  */
  selection: function(fitnessLevels) {
    var tournamentParticipants = new Array(this.TOURNAMENT_SIZE);

    /* Select individuals at random, and represent them as a touple containing their indexes and their
    * fitness levels. */
    for (var i = 0; i < this.TOURNAMENT_SIZE; i++) {
      tournamentParticipants[i]    = new Array(2);
      tournamentParticipants[i][0] = Math.round(Math.random() * (this.POPULATION_SIZE - 1));
      tournamentParticipants[i][1] = fitnessLevels[tournamentParticipants[i][0]];
    }

    /* Sort by fitness levels */
    tournamentParticipants.sort(
      function(a,b) {return b[1]-a[1];}
    );

    for(i = 0; i < this.TOURNAMENT_SIZE; i++) {
      if (Math.random() < this.TOURNAMENT_PARAMETER) {
        return tournamentParticipants[i][0];
      }
    }

    return tournamentParticipants[i - 1][0];
  },

  /**
  * Mutate a given individual by performing a tradeoff between two randomly selected genes.
  *
  * @method Darwinator.GeneticAlgorithm#mutate
  * @param {Array} - The individual to be mutated
  * @return {Array} - The mutated individual.
  */
  mutate: function(individual) {
    if (Math.random() < this.MUTATION_PROBABILITY * this.MUTATION_RATE) {
      var gene1 = Math.round(Math.random()*(this.NUMBER_OF_GENES - 1));
      var gene2 = Math.round(Math.random()*(this.NUMBER_OF_GENES - 1));
      var tradeOffAmount = Math.round(Math.random() * individual[gene1]);

      individual[gene1] -= tradeOffAmount;
      individual[gene2] += tradeOffAmount;
    }
    return individual;
  },

  /**
  * Evaluates an enemy sprite based on its score.
  *
  * @method Darwinator.GeneticAlgorithm#evaluateInd
  * @param {Phaser.Sprite} - The individual to be evaluated
  * @return {Number} - The fitness level of the individual
  */
  evaluateInd: function(enemy) {
    var fitness = 1 - (1 / this.enemyScore(enemy));
    return fitness == -Infinity ? 0 : fitness;// maximize fitness based on enemy score
  },

  /**
  * Calculates the score of an enemy based on statistical attributes.
  *
  * @method Darwinator.GeneticAlgorithm#enemyScore
  * @param {Phaser.Sprite} - An enemy sprite
  * @return {Number} - The score of the enemy for a given game round.
  */
  enemyScore: function(enemy) {
    var score = enemy.alive ? enemy.damageDone*2 : enemy.damageDone;
    return score;
  },

  /**
  *
  * Use the attributes of an enemy sprite as genes in a real-value chromosome.
  *
  * @method Darwinator.GeneticAlgorithm#enemyToChromosome
  * @param {Phaser.Sprite} - An enemy sprite
  * @return {Number} - The enemy represented as a real-value chromosome
  */
  enemyToChromosome: function (enemy){
    return [enemy.attributes.strength, enemy.attributes.agility, enemy.attributes.intellect];
  },

  /**
  * Translates an enemy group of sprites to chromosomes and fitness levels.
  * The index of the most fit individual is also provided.
  *
  * @method Darwinator.GeneticAlgorithm#translateEnemyWave
  * @param {Phaser.Group} - A group of enemy sprites
  * @return {Array} - Chromosomes, fitnessLevels and the index of the fittest individual.
  *                     as [chromosomes, fitnessLevels, fittestIndex]
  */
  translateEnemyWave: function(enemyGroup){
    var currentSize = enemyGroup.length; //could add an extra param for this.
    var population  = new Array(currentSize);
    var fitness     = new Array(currentSize);
    var bestIndex   = 0;
    for(var i = 0; i < currentSize; i++){
      var enemy     = enemyGroup.getAt(i);
      population[i] = this.enemyToChromosome(enemy);
      fitness[i]    = this.evaluateInd(enemy);
      if(fitness[i] > fitness[bestIndex]){
        bestIndex = i;
      }
    }
    return [population, fitness, bestIndex];
  },

  /**
  * Replaces the given group of enemy sprites to the next generation of sprites.
  *
  * @method Darwinator.GeneticAlgorithm#translatePopulation
  * @param {Array} [population] - The chromosomes of the new generation.
  * @param {Phaser.Group} [enemyGroup] - The enemy sprite group to replace.
  * @param {Phaser.Game} [game] - The game that uses the algorithm.
  * @param {Phaser.Sprite} [target] - The sprite to be attacked by the enemies.
  * @param {Array} [spawnPositions] - The positions on which the enemies are allowed to spawn.
  * @return {Phaser.Group} The new group of enemies.
  */
  translatePopulation: function(population, enemyGroup, game, target, spawnPositions){
    // FIXME possible memory leak, should call destroy but it crashes for some reason..
    //game.enemies.destroy(true);
    enemyGroup = game.add.group();
    for(var i = 0; i < population.length; i++){
      var pos         = spawnPositions[i % spawnPositions.length];
      var strength    = population[i][0];
      var agility     = population[i][1];
      var intellect   = population[i][2];
      var enemy       = new Darwinator.Enemy(game, target, pos.x, pos.y, undefined, strength, agility, intellect);
      enemyGroup.add(enemy);
    }
    return enemyGroup;
  },

};
<commit_msg>make sure enemy attribute sum is equal to variable range in the real-value version of the GA<commit_after>'use strict';

window.Darwinator.GeneticAlgorithm = window.Darwinator.GeneticAlgorithm || {

  POPULATION_SIZE:          10,
  CROSSOVER_PROBABILITY:    0.8,
  MUTATION_PROBABILITY:     0.025,
  TOURNAMENT_PARAMETER:     0.75,
  NUMBER_OF_GENERATIONS:    100,
  NUMBER_OF_VARIABLES:      3,
  NUMBER_OF_GENES:          3,
  TOURNAMENT_SIZE:          4,
  ELITISM_DEGREE:           2,
  PLAYER_ADVANTAGE:         10, // increase to make enemies weaker

  // depends on player attributes
  VARIABLE_RANGE:           undefined, // max sum of enemy attributes

  // depends on population success
  MUTATION_RATE:            undefined,
  POOR_MAX_FITNESS:         0.1,  // used to set mutation rate

  /**
  * Generates a population of individuals from a given population. The new population is likely to be
  * more adapted to find a solution to the enemyScore() function.
  *
  * @method Darwinator.GeneticAlgorithm#generatePopulation
  * @param {Phaser.Game} [game] - The game that uses the algorithm.
  * @param {Phaser.Sprite} [target] - The sprite to be attacked by the enemies.
  * @param {Phaser.Group} [enemyGroup] - Optional. If not provided, a group with default values will be set.
  * @param {Boolean} [singleGeneration] - If true, only one iteration will be run, else, this.NUMBER_OF_GENERATIONS is used.
  * @param {Array} [spawnPositions] - The positions on which the enemies are allowed to spawn.
  * @return {Array} The last population generated.
  */
  generatePopulation: function(game, target, enemyGroup, singleGeneration, spawnPositions) {
    var attributes      = target.attributes;
    this.VARIABLE_RANGE = attributes.strength + attributes.agility + attributes.intellect - this.PLAYER_ADVANTAGE;
    spawnPositions      = Darwinator.Helpers.shuffle(spawnPositions);

    if(!enemyGroup){
      enemyGroup = game.add.group();
      console.log('GA: Enemy group not provided. Initializing with default values.');
      enemyGroup = this.initPopulation(enemyGroup, game, target, spawnPositions);
      return enemyGroup;
    }
    console.log('GA: Starting GA run..');
    var translatedEnemies = this.translateEnemyWave(enemyGroup);
    var population        = translatedEnemies[0];
    var fitnessLevels     = translatedEnemies[1];
    var bestIndex         = translatedEnemies[2];
    var bestIndividual    = population[bestIndex];

    // to eliminate weak populations
    if(fitnessLevels[bestIndex] <= this.POOR_MAX_FITNESS){
      this.MUTATION_RATE = 20;
    }else{
      this.MUTATION_RATE = 1;
    }

    // algorithm main loop
    for (var i = 0; i < (singleGeneration ? 1 : this.NUMBER_OF_GENERATIONS); i++) {

      // clone population
      var tmpPopulation = population.slice(0);

      // selection
      for(var l = 0; l < population.length; l += 2) {
        var ind1 = this.selection(fitnessLevels);
        var ind2 = this.selection(fitnessLevels);
        // crossover
        if (Math.random() < this.CROSSOVER_PROBABILITY) {
          var chromePair        = this.cross(population[ind1], population[ind2]);
          tmpPopulation[l]      = chromePair[0];
          tmpPopulation[l + 1]  = chromePair[1];
        } else {
          tmpPopulation[l]      = population[ind1];
          tmpPopulation[l + 1]  = population[ind2];
        }
        // mutation
        tmpPopulation[l]    = this.mutate(tmpPopulation[l]);
        tmpPopulation[l+1]  = this.mutate(tmpPopulation[l+1]);
      }

      if(fitnessLevels[bestIndex] > this.POOR_MAX_FITNESS){
        // elitism
        for(l = 0; l < this.ELITISM_DEGREE; l++) {
          tmpPopulation[l] = bestIndividual;
        }
      }

      // replace old population
      population = tmpPopulation;
    } //endof algorithm main loop

    var nextGeneration = this.translatePopulation(population, enemyGroup, game, target, spawnPositions);
    console.log('GA: Returning the new generation!');
    return nextGeneration;
  },

  /**
  * Generates a group of enemy sprites with some default values.
  *
  * @method Darwinator.GeneticAlgorithm#initPopulation
  * @param {Phaser.Group} [enemyGroup] - The enemy group to fill with enemies.
  * @param {Phaser.Game} [game] - The game that uses the algorithm.
  * @param {Phaser.Sprite} [target] - The sprite to be attacked by the enemies.
  * @param {Array} [spawnPositions] - The positions on which the enemies are allowed to spawn.
  * @return {Array} An enemy population with default attribute values.
  */
  initPopulation: function(enemyGroup, game, target, spawnPositions) {
    // 4 different enemy initial set of attributes - just an example, not permanent!
    var enemiesPerType  = this.POPULATION_SIZE / 5;
    var pos;
    var strength, agility, intellect;
    for(var i = 0; i < this.POPULATION_SIZE; i++){
      pos = spawnPositions[i % spawnPositions.length];
      if(i < enemiesPerType){
        // smart but slow and weak
        strength  = 0;
        agility   = 0;
        intellect = this.VARIABLE_RANGE;
      }else if(i < enemiesPerType*2){
        // strong but slow and stupid
        strength  = this.VARIABLE_RANGE;
        agility   = 0;
        intellect = 0;
      }else if(i < enemiesPerType*3){
        // fast but weak and stupid
        strength  = 0;
        agility   = this.VARIABLE_RANGE;
        intellect = 0;
      }else{
        // hybrid
        strength  = agility = intellect = Math.round(this.VARIABLE_RANGE / 3);
      }
      enemyGroup.add(new Darwinator.Enemy(game, target, pos.x, pos.y, undefined, strength, agility, intellect));
    }
    return enemyGroup;
  },

  /**
  * Cross two individuals to create two new ones by performing a tradeoff between
  * two randomly selected genes in each individual.
  *
  * Example: Select index of x and y.
  * First individual: decrease x and increase y
  * Second individual: decrease y and increase x
  *
  * @method Darwinator.GeneticAlgorithm#cross
  * @param {Array} [firstInd] - The first individual to be crossed
  * @param {Array} [secondInd] - The second individual to be crossed
  * @return {Array} - A tuple containing the two new individuals.
  */
  cross: function(firstInd, secondInd) {
    var crossPoint1 = Math.round(Math.random()*(this.NUMBER_OF_GENES - 1));
    var crossPoint2 = Math.round(Math.random()*(this.NUMBER_OF_GENES - 1));

    var tradeOff        = Math.random();
    var tradeOffAmount1 = Math.round(tradeOff * firstInd[crossPoint1]);
    var tradeOffAmount2 = Math.round(tradeOff * secondInd[crossPoint2]);

    var newInd1 = firstInd.slice(0);
    var newInd2 = secondInd.slice(0);

    newInd1[crossPoint1] -= tradeOffAmount1;
    newInd1[crossPoint2] += tradeOffAmount1;

    newInd2[crossPoint2] -= tradeOffAmount2;
    newInd2[crossPoint1] += tradeOffAmount2;

    return [newInd1, newInd2];
  },

  /**
  * Select a individual based on the fitness levels of the entire population.
  * this.TOURNAMENT_SIZE many individuals will be selected at random, and one of them will
  * be returned. The tournament works in such a way that a individual with a high fitness level
  * is more likely to be returned from the tournament, but is not more likely to participate.
  *
  * @method Darwinator.GeneticAlgorithm#selection
  * @param {Array} - The fitness levels of the entire population
  * @return {Number} - The index of the selected individual.
  */
  selection: function(fitnessLevels) {
    var tournamentParticipants = new Array(this.TOURNAMENT_SIZE);

    /* Select individuals at random, and represent them as a touple containing their indexes and their
    * fitness levels. */
    for (var i = 0; i < this.TOURNAMENT_SIZE; i++) {
      tournamentParticipants[i]    = new Array(2);
      tournamentParticipants[i][0] = Math.round(Math.random() * (this.POPULATION_SIZE - 1));
      tournamentParticipants[i][1] = fitnessLevels[tournamentParticipants[i][0]];
    }

    /* Sort by fitness levels */
    tournamentParticipants.sort(
      function(a,b) {return b[1]-a[1];}
    );

    for(i = 0; i < this.TOURNAMENT_SIZE; i++) {
      if (Math.random() < this.TOURNAMENT_PARAMETER) {
        return tournamentParticipants[i][0];
      }
    }

    return tournamentParticipants[i - 1][0];
  },

  /**
  * Mutate a given individual by performing a tradeoff between two randomly selected genes.
  *
  * @method Darwinator.GeneticAlgorithm#mutate
  * @param {Array} - The individual to be mutated
  * @return {Array} - The mutated individual.
  */
  mutate: function(individual) {
    if (Math.random() < this.MUTATION_PROBABILITY * this.MUTATION_RATE) {
      var gene1 = Math.round(Math.random()*(this.NUMBER_OF_GENES - 1));
      var gene2 = Math.round(Math.random()*(this.NUMBER_OF_GENES - 1));
      var tradeOffAmount = Math.round(Math.random() * individual[gene1]);

      individual[gene1] -= tradeOffAmount;
      individual[gene2] += tradeOffAmount;
    }
    return individual;
  },

  /**
  * Evaluates an enemy sprite based on its score.
  *
  * @method Darwinator.GeneticAlgorithm#evaluateInd
  * @param {Phaser.Sprite} - The individual to be evaluated
  * @return {Number} - The fitness level of the individual
  */
  evaluateInd: function(enemy) {
    var fitness = 1 - (1 / this.enemyScore(enemy));
    return fitness == -Infinity ? 0 : fitness;// maximize fitness based on enemy score
  },

  /**
  * Calculates the score of an enemy based on statistical attributes.
  *
  * @method Darwinator.GeneticAlgorithm#enemyScore
  * @param {Phaser.Sprite} - An enemy sprite
  * @return {Number} - The score of the enemy for a given game round.
  */
  enemyScore: function(enemy) {
    var score = enemy.alive ? enemy.damageDone*2 : enemy.damageDone;
    return score;
  },

  /**
  *
  * Use the attributes of an enemy sprite as genes in a real-value chromosome.
  *
  * @method Darwinator.GeneticAlgorithm#enemyToChromosome
  * @param {Phaser.Sprite} - An enemy sprite
  * @return {Number} - The enemy represented as a real-value chromosome
  */
  enemyToChromosome: function (enemy){
    var attrSum = enemy.attributes.strength + enemy.attributes.agility + enemy.attributes.intellect;
    var chrom = [enemy.attributes.strength, enemy.attributes.agility, enemy.attributes.intellect];

    // distribute remaining points
    var i = 0;
    while(attrSum++ < this.VARIABLE_RANGE)
      chrom[i++ % this.NUMBER_OF_GENES]++;

    return chrom;
  },

  /**
  * Translates an enemy group of sprites to chromosomes and fitness levels.
  * The index of the most fit individual is also provided.
  *
  * @method Darwinator.GeneticAlgorithm#translateEnemyWave
  * @param {Phaser.Group} - A group of enemy sprites
  * @return {Array} - Chromosomes, fitnessLevels and the index of the fittest individual.
  *                     as [chromosomes, fitnessLevels, fittestIndex]
  */
  translateEnemyWave: function(enemyGroup){
    var currentSize = enemyGroup.length; //could add an extra param for this.
    var population  = new Array(currentSize);
    var fitness     = new Array(currentSize);
    var bestIndex   = 0;
    for(var i = 0; i < currentSize; i++){
      var enemy     = enemyGroup.getAt(i);
      population[i] = this.enemyToChromosome(enemy);
      fitness[i]    = this.evaluateInd(enemy);
      if(fitness[i] > fitness[bestIndex]){
        bestIndex = i;
      }
    }
    return [population, fitness, bestIndex];
  },

  /**
  * Replaces the given group of enemy sprites to the next generation of sprites.
  *
  * @method Darwinator.GeneticAlgorithm#translatePopulation
  * @param {Array} [population] - The chromosomes of the new generation.
  * @param {Phaser.Group} [enemyGroup] - The enemy sprite group to replace.
  * @param {Phaser.Game} [game] - The game that uses the algorithm.
  * @param {Phaser.Sprite} [target] - The sprite to be attacked by the enemies.
  * @param {Array} [spawnPositions] - The positions on which the enemies are allowed to spawn.
  * @return {Phaser.Group} The new group of enemies.
  */
  translatePopulation: function(population, enemyGroup, game, target, spawnPositions){
    // FIXME possible memory leak, should call destroy but it crashes for some reason..
    //game.enemies.destroy(true);
    enemyGroup = game.add.group();
    for(var i = 0; i < population.length; i++){
      var pos         = spawnPositions[i % spawnPositions.length];
      var strength    = population[i][0];
      var agility     = population[i][1];
      var intellect   = population[i][2];
      var enemy       = new Darwinator.Enemy(game, target, pos.x, pos.y, undefined, strength, agility, intellect);
      enemyGroup.add(enemy);
    }
    return enemyGroup;
  },

};
<|endoftext|>


<commit_before>(function($) {
  "use strict";
  var namespace = 'animsition';
  var methods = {
    init: function(options){
      options = $.extend({
        inClass               :   'fade-in',
        outClass              :   'fade-out',
        inDuration            :    1500,
        outDuration           :    800,
        linkElement           :   '.animsition-link',
        // e.g. linkElement   :   'a:not([target="_blank"]):not([href^=#])'
        touchSupport          :    true,
        loading               :    true,
        loadingParentElement  :   'body', //animsition wrapper element
        loadingClass          :   'animsition-loading',
        unSupportCss          : [ 'animation-duration',
                                  '-webkit-animation-duration',
                                  '-o-animation-duration'
                                ],
        overlayMode           :   false,
        overlayClass          :   'animsition-overlay-slide',
        overlayParentElement  :   'body'
      }, options);

      // Remove the "Animsition" in a browser
      // that does not support the "animsition-in-duration".
      var support = methods.supportCheck.call(this, options);
      if(support === false){
        // If do not have a console object to object window
        if (!('console' in window)) {
          window.console = {};
          window.console.log = function(str){return str};
        }
        console.log("Animsition does not support this browser.");
        return methods.destroy.call( this );
      }

      // Bind to touchend event listener as well, if touchSupport enabled
      var bindEvts = "click." + namespace;
      if (options.touchSupport) {
        bindEvts += " touchend." + namespace;
      }

      if(options.overlayMode === true) {
        methods.addOverlay.call(this, options);
      }

      if(options.loading === true) {
        methods.addLoading.call(this, options);
      }

      return this.each(function(){
        var _this = this;
        var $this = $(this);
        var $window = $(window);
        var data = $this.data(namespace);

        if (!data) {
          options = $.extend({}, options);

          $this.data(namespace, {
            options: options
          });

          $window.on("load." + namespace, function() {
            methods.pageIn.call( _this );
          });

          // Firefox back button issue #4
          $window.on("unload." + namespace, function() { });

          $(options.linkElement).on(bindEvts, function(event) {
            event.preventDefault();
            var $self = $(this);
            methods.pageOut.call( _this, $self);
          });
        }
      }); // end each
    },

    supportCheck: function(options){
      var $this = $(this);
      var props = options.unSupportCss;
      var propsNum = props.length;
      var support  = false;

      if (propsNum === 0) {
        support = true;
      }
      for (var i = 0; i < propsNum; i++) {
        if (typeof $this.css(props[i]) === "string") {
          support = true;
          break;
        }
      }
      return support;
    },

    addOverlay: function(options){
      $(options.overlayParentElement)
        .prepend('

');
    },

    addLoading: function(options){
      $(options.loadingParentElement)
        .append('

');
    },

    removeLoading: function(){
      var $this     = $(this);
      var options   = $this.data(namespace).options;
      var $loading  = $(options.loadingParentElement).children("."+options.loadingClass);
      $loading.remove();
    },

    pageInClass : function(){
      var $this = $(this);
      var options = $this.data(namespace).options;
      var thisInClass = $this.data('animsition-in');
      var inClass;

      if(typeof thisInClass === "string"){
        inClass = thisInClass;
      } else {
        inClass = options.inClass;
      }
      return inClass;
    },

    pageInDuration : function(){
      var $this = $(this);
      var options = $this.data(namespace).options;
      var thisInDuration = $this.data('animsition-in-duration');
      var inDuration;

      if(typeof thisInDuration === "number"){
        inDuration = thisInDuration;
      } else {
        inDuration = options.inDuration;
      }
      return inDuration;
    },

    pageIn: function(){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var inClass = methods.pageInClass.call(_this);
      var inDuration = methods.pageInDuration.call(_this);

      if(options.loading === true) {
        methods.removeLoading.call(_this);
      }

      if(options.overlayMode === true) {
        methods.pageInOverlay.call(_this,inClass,inDuration);
      } else {
        methods.pageInBasic.call(_this,inClass,inDuration);
      }
    },

    pageInBasic: function(inClass,inDuration){
      var $this = $(this);

      $this
        .css({ "animation-duration" : (inDuration / 1000) + "s" })
        .addClass(inClass)
        .animateCallback(function(){
          $this
            .removeClass(inClass)
            .css({ "opacity" : 1 });
        });
    },

    pageInOverlay: function(inClass,inDuration){
      var $this = $(this);
      var options = $this.data(namespace).options;

      $(options.overlayParentElement).children('.' + options.overlayClass)
        .css({ "animation-duration" : (inDuration / 1000) + "s" })
        .addClass(inClass);
    },

    pageOutClass : function($self){
      var $this = $(this);
      var options = $this.data(namespace).options;
      var selfOutClass = $self.data('animsition-out');
      var thisOutClass = $this.data('animsition-out');
      var outClass;

      if(typeof selfOutClass === "string"){
        outClass = selfOutClass;
      } else if(typeof thisOutClass === "string") {
        outClass = thisOutClass;
      } else {
        outClass = options.outClass;
      }
      return outClass;
    },

    pageOutDuration : function($self){
      var $this = $(this);
      var options = $this.data(namespace).options;
      var selfOutDuration = $self.data('animsition-out-duration');
      var thisOutDuration = $this.data('animsition-out-duration');
      var outDuration;

      if(typeof selfOutDuration === "number"){
        outDuration = selfOutDuration;
      } else if(typeof thisOutDuration === "number") {
        outDuration = thisOutDuration;
      } else {
        outDuration = options.outDuration;
      }
      return outDuration;
    },

    pageOut: function($self){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var outClass = methods.pageOutClass.call(_this, $self);
      var outDuration = methods.pageOutDuration.call(_this, $self);
      var url = $self.attr('href');

      if(options.overlayMode === true) {
        methods.pageOutOverlay.call(_this,outClass,outDuration,url);
      } else {
        methods.pageOutBasic.call(_this,outClass,outDuration,url);
      }
    },

    pageOutBasic: function(outClass,outDuration,url){
      var $this = $(this);

      $this
        .css({ "animation-duration" : (outDuration / 1000) + "s" })
        .addClass(outClass)
        .animateCallback(function(){
          location.href = url
        });
    },

    pageOutOverlay: function(outClass,outDuration,url){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var inClass = methods.pageInClass.call(_this);

      $(options.overlayParentElement).children('.' + options.overlayClass)
        .css({ "animation-duration" : (outDuration / 1000) + "s" })
        .removeClass(inClass)
        .addClass(outClass)
        .animateCallback(function(){
          location.href = url
        });
    },

    destroy: function(){
      return this.each(function(){
        var $this = $(this);
        $(window).unbind('.'+namespace);
        $this
          .removeClass(namespace)
          .removeData(namespace);
      });
    }

  };

  $.fn.animateCallback = function(callback){
    var animationEnd = "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd"
      return this.each(function() {
        $(this).bind(animationEnd, function(){
          $(this).unbind(animationEnd);
          return callback.call(this);
        });
    });
  };

  $.fn.animsition = function(method){
    if ( methods[method] ) {
      return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return methods.init.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.'+namespace);
    }
  };

})(jQuery);<commit_msg>overlay option<commit_after>(function($) {
  "use strict";
  var namespace = 'animsition';
  var methods = {
    init: function(options){
      options = $.extend({
        inClass               :   'fade-in',
        outClass              :   'fade-out',
        inDuration            :    1500,
        outDuration           :    800,
        linkElement           :   '.animsition-link',
        // e.g. linkElement   :   'a:not([target="_blank"]):not([href^=#])'
        touchSupport          :    true,
        loading               :    true,
        loadingParentElement  :   'body', //animsition wrapper element
        loadingClass          :   'animsition-loading',
        unSupportCss          : [ 'animation-duration',
                                  '-webkit-animation-duration',
                                  '-o-animation-duration'
                                ],
        overlay               :   false,
        overlayClass          :   'animsition-overlay-slide',
        overlayParentElement  :   'body'
      }, options);

      // Remove the "Animsition" in a browser
      // that does not support the "animsition-in-duration".
      var support = methods.supportCheck.call(this, options);
      if(support === false){
        // If do not have a console object to object window
        if (!('console' in window)) {
          window.console = {};
          window.console.log = function(str){return str};
        }
        console.log("Animsition does not support this browser.");
        return methods.destroy.call( this );
      }

      // Bind to touchend event listener as well, if touchSupport enabled
      var bindEvts = "click." + namespace;
      if (options.touchSupport) {
        bindEvts += " touchend." + namespace;
      }

      if(options.overlayMode === true) {
        methods.addOverlay.call(this, options);
      }

      if(options.loading === true) {
        methods.addLoading.call(this, options);
      }

      return this.each(function(){
        var _this = this;
        var $this = $(this);
        var $window = $(window);
        var data = $this.data(namespace);

        if (!data) {
          options = $.extend({}, options);

          $this.data(namespace, {
            options: options
          });

          $window.on("load." + namespace, function() {
            methods.pageIn.call( _this );
          });

          // Firefox back button issue #4
          $window.on("unload." + namespace, function() { });

          $(options.linkElement).on(bindEvts, function(event) {
            event.preventDefault();
            var $self = $(this);
            methods.pageOut.call( _this, $self);
          });
        }
      }); // end each
    },

    supportCheck: function(options){
      var $this = $(this);
      var props = options.unSupportCss;
      var propsNum = props.length;
      var support  = false;

      if (propsNum === 0) {
        support = true;
      }
      for (var i = 0; i < propsNum; i++) {
        if (typeof $this.css(props[i]) === "string") {
          support = true;
          break;
        }
      }
      return support;
    },

    addOverlay: function(options){
      $(options.overlayParentElement)
        .prepend('

');
    },

    addLoading: function(options){
      $(options.loadingParentElement)
        .append('

');
    },

    removeLoading: function(){
      var $this     = $(this);
      var options   = $this.data(namespace).options;
      var $loading  = $(options.loadingParentElement).children("."+options.loadingClass);
      $loading.remove();
    },

    pageInClass : function(){
      var $this = $(this);
      var options = $this.data(namespace).options;
      var thisInClass = $this.data('animsition-in');
      var inClass;

      if(typeof thisInClass === "string"){
        inClass = thisInClass;
      } else {
        inClass = options.inClass;
      }
      return inClass;
    },

    pageInDuration : function(){
      var $this = $(this);
      var options = $this.data(namespace).options;
      var thisInDuration = $this.data('animsition-in-duration');
      var inDuration;

      if(typeof thisInDuration === "number"){
        inDuration = thisInDuration;
      } else {
        inDuration = options.inDuration;
      }
      return inDuration;
    },

    pageIn: function(){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var inClass = methods.pageInClass.call(_this);
      var inDuration = methods.pageInDuration.call(_this);

      if(options.loading === true) {
        methods.removeLoading.call(_this);
      }

      if(options.overlayMode === true) {
        methods.pageInOverlay.call(_this,inClass,inDuration);
      } else {
        methods.pageInBasic.call(_this,inClass,inDuration);
      }
    },

    pageInBasic: function(inClass,inDuration){
      var $this = $(this);

      $this
        .css({ "animation-duration" : (inDuration / 1000) + "s" })
        .addClass(inClass)
        .animateCallback(function(){
          $this
            .removeClass(inClass)
            .css({ "opacity" : 1 });
        });
    },

    pageInOverlay: function(inClass,inDuration){
      var $this = $(this);
      var options = $this.data(namespace).options;

      $(options.overlayParentElement).children('.' + options.overlayClass)
        .css({ "animation-duration" : (inDuration / 1000) + "s" })
        .addClass(inClass);
    },

    pageOutClass : function($self){
      var $this = $(this);
      var options = $this.data(namespace).options;
      var selfOutClass = $self.data('animsition-out');
      var thisOutClass = $this.data('animsition-out');
      var outClass;

      if(typeof selfOutClass === "string"){
        outClass = selfOutClass;
      } else if(typeof thisOutClass === "string") {
        outClass = thisOutClass;
      } else {
        outClass = options.outClass;
      }
      return outClass;
    },

    pageOutDuration : function($self){
      var $this = $(this);
      var options = $this.data(namespace).options;
      var selfOutDuration = $self.data('animsition-out-duration');
      var thisOutDuration = $this.data('animsition-out-duration');
      var outDuration;

      if(typeof selfOutDuration === "number"){
        outDuration = selfOutDuration;
      } else if(typeof thisOutDuration === "number") {
        outDuration = thisOutDuration;
      } else {
        outDuration = options.outDuration;
      }
      return outDuration;
    },

    pageOut: function($self){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var outClass = methods.pageOutClass.call(_this, $self);
      var outDuration = methods.pageOutDuration.call(_this, $self);
      var url = $self.attr('href');

      if(options.overlayMode === true) {
        methods.pageOutOverlay.call(_this,outClass,outDuration,url);
      } else {
        methods.pageOutBasic.call(_this,outClass,outDuration,url);
      }
    },

    pageOutBasic: function(outClass,outDuration,url){
      var $this = $(this);

      $this
        .css({ "animation-duration" : (outDuration / 1000) + "s" })
        .addClass(outClass)
        .animateCallback(function(){
          location.href = url
        });
    },

    pageOutOverlay: function(outClass,outDuration,url){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var inClass = methods.pageInClass.call(_this);

      $(options.overlayParentElement).children('.' + options.overlayClass)
        .css({ "animation-duration" : (outDuration / 1000) + "s" })
        .removeClass(inClass)
        .addClass(outClass)
        .animateCallback(function(){
          location.href = url
        });
    },

    destroy: function(){
      return this.each(function(){
        var $this = $(this);
        $(window).unbind('.'+namespace);
        $this
          .removeClass(namespace)
          .removeData(namespace);
      });
    }

  };

  $.fn.animateCallback = function(callback){
    var animationEnd = "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd"
      return this.each(function() {
        $(this).bind(animationEnd, function(){
          $(this).unbind(animationEnd);
          return callback.call(this);
        });
    });
  };

  $.fn.animsition = function(method){
    if ( methods[method] ) {
      return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return methods.init.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.'+namespace);
    }
  };

})(jQuery);<|endoftext|>


<commit_before>;(function (factory) {
  'use strict';
  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory(require('jquery'));
  } else {
    factory(jQuery);
  }
}(function ($) {
  'use strict';
  var namespace = 'animsition';
  var __ = {
    init: function(options){
      options = $.extend({
        inClass               :   'fade-in',
        outClass              :   'fade-out',
        inDuration            :    1500,
        outDuration           :    800,
        linkElement           :   '.animsition-link',
        // e.g. linkElement   :   'a:not([target="_blank"]):not([href^=#])'
        loading               :    true,
        loadingParentElement  :   'body', //animsition wrapper element
        loadingClass          :   'animsition-loading',
        loadingInner          :   '', // e.g ''
        timeout               :   false,
        timeoutCountdown      :   5000,
        onLoadEvent           :   true,
        browser               : [ 'animation-duration', '-webkit-animation-duration'],
        // "browser" option allows you to disable the "animsition" in case the css property in the array is not supported by your browser.
        // The default setting is to disable the "animsition" in a browser that does not support "animation-duration".
        overlay               :   false,
        overlayClass          :   'animsition-overlay-slide',
        overlayParentElement  :   'body',
        transition            :   function(url){ window.location.href = url; }
      }, options);

      __.settings = {
        timer: false,
        data: {
          inClass: 'animsition-in-class',
          inDuration: 'animsition-in-duration',
          outClass: 'animsition-out-class',
          outDuration: 'animsition-out-duration',
          overlay: 'animsition-overlay'
        },
        events: {
          inStart: 'animsition.inStart',
          inEnd: 'animsition.inEnd',
          outStart: 'animsition.outStart',
          outEnd: 'animsition.outEnd'
        }
      };

      // Remove the "Animsition" in a browser
      // that does not support the "animaition-duration".
      var support = __.supportCheck.call(this, options);

      if(!support && options.browser.length > 0){
        if(!support || !this.length){
          // If do not have a console object to object window
          if (!('console' in window)) {
            window.console = {};
            window.console.log = function(str){ return str; };
          }
          if(!this.length) console.log('Animsition: Element does not exist on page.');
          if(!support) console.log('Animsition: Does not support this browser.');
          return __.destroy.call(this);
        }
      }

      var overlayMode = __.optionCheck.call(this, options);

      if(overlayMode) __.addOverlay.call(this, options);

      if(options.loading) __.addLoading.call(this, options);

      return this.each(function(){
        var _this = this;
        var $this = $(this);
        var $window = $(window);
        var $document = $(document);
        var data = $this.data(namespace);

        if (!data) {
          options = $.extend({}, options);

          $this.data(namespace, { options: options });

          if(options.timeout) __.addTimer.call(_this);

          if(options.onLoadEvent) {
            $window.on('load.' + namespace, function() {
              if(__.settings.timer) clearTimeout(__.settings.timer);
              __.in.call(_this);
            });
          }

          $window.on('pageshow.' + namespace, function(event) {
            if(event.originalEvent.persisted) __.in.call(_this);
          });

          // Firefox back button issue #4
          $window.on('unload.' + namespace, function() { });

          $document.on('click.' + namespace, options.linkElement, function(event) {
            event.preventDefault();
            var $self = $(this);
            var url = $self.attr('href');

            // middle mouse button issue #24
            // if(middle mouse button || command key || shift key || win control key)
            if (event.which === 2 || event.metaKey || event.shiftKey || navigator.platform.toUpperCase().indexOf('WIN') !== -1 && event.ctrlKey) {
              window.open(url, '_blank');
            } else {
              __.out.call(_this, $self, url);
            }

          });
        }
      }); // end each
    },

    addOverlay: function(options){
      $(options.overlayParentElement)
        .prepend('

');
    },

    addLoading: function(options){
      $(options.loadingParentElement)
        .append('
' + options.loadingInner + '
');
    },

    removeLoading: function(){
      var $this     = $(this);
      var options   = $this.data(namespace).options;
      var $loading  = $(options.loadingParentElement).children('.' + options.loadingClass);

      $loading.fadeOut().remove();
    },

    addTimer: function(){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;

      __.settings.timer = setTimeout(function(){
        __.in.call(_this);
        $(window).off('load.' + namespace);
      }, options.timeoutCountdown);
    },

    supportCheck: function(options){
      var $this = $(this);
      var props = options.browser;
      var propsNum = props.length;
      var support  = false;

      if (propsNum === 0) {
        support = true;
      }
      for (var i = 0; i < propsNum; i++) {
        if (typeof $this.css(props[i]) === 'string') {
          support = true;
          break;
        }
      }
      return support;
    },

    optionCheck: function(options){
      var $this = $(this);
      var overlayMode;
      if(options.overlay || $this.data(__.settings.data.overlay)){
        overlayMode = true;
      } else {
        overlayMode = false;
      }
      return overlayMode;
    },

    animationCheck : function(data, stateClass, stateIn){
      var $this = $(this);
      var options = $this.data(namespace).options;
      var dataType = typeof data;
      var dataDuration = !stateClass && dataType === 'number';
      var dataClass = stateClass && dataType === 'string' && data.length > 0;

      if(dataDuration || dataClass){
        data = data;
      } else if(stateClass && stateIn) {
        data = options.inClass;
      } else if(!stateClass && stateIn) {
        data = options.inDuration;
      } else if(stateClass && !stateIn) {
        data = options.outClass;
      } else if(!stateClass && !stateIn) {
        data = options.outDuration;
      }
      return data;
    },

    in: function(){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var thisInDuration = $this.data(__.settings.data.inDuration);
      var thisInClass = $this.data(__.settings.data.inClass);
      var inDuration = __.animationCheck.call(_this, thisInDuration, false, true);
      var inClass = __.animationCheck.call(_this, thisInClass, true, true);
      var overlayMode = __.optionCheck.call(_this, options);

      if(options.loading) __.removeLoading.call(_this);

      var outClass = $this.data(namespace).outClass;
      if (outClass) {
        $this.removeClass(outClass);
      }

      if(overlayMode) {
        __.inOverlay.call(_this, inClass, inDuration);
      } else {
        __.inDefault.call(_this, inClass, inDuration);
      }
    },

    inDefault: function(inClass, inDuration){
      var $this = $(this);

      $this
        .css({ 'animation-duration' : inDuration + 'ms' })
        .addClass(inClass)
        .trigger(__.settings.events.inStart)
        .animateCallback(function(){
          $this
            .removeClass(inClass)
            .css({ 'opacity' : 1 })
            .trigger(__.settings.events.inEnd);
        });
    },

    inOverlay: function(inClass, inDuration){
      var $this = $(this);
      var options = $this.data(namespace).options;

      $this
        .css({ 'opacity' : 1 })
        .trigger(__.settings.events.inStart);

      $(options.overlayParentElement)
        .children('.' + options.overlayClass)
        .css({ 'animation-duration' : inDuration + 'ms' })
        .addClass(inClass)
        .animateCallback(function(){
          $this
            .trigger(__.settings.events.inEnd);
        });
    },

    out: function($self, url){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var selfOutClass = $self.data(__.settings.data.outClass);
      var thisOutClass = $this.data(__.settings.data.outClass);
      var selfOutDuration = $self.data(__.settings.data.outDuration);
      var thisOutDuration = $this.data(__.settings.data.outDuration);
      var isOutClass = selfOutClass ? selfOutClass : thisOutClass;
      var isOutDuration = selfOutDuration ? selfOutDuration : thisOutDuration;
      var outClass = __.animationCheck.call(_this, isOutClass, true, false);
      var outDuration = __.animationCheck.call(_this, isOutDuration, false, false);
      var overlayMode = __.optionCheck.call(_this, options);

      $this.data(namespace).outClass = outClass;

      if(overlayMode) {
        __.outOverlay.call(_this, outClass, outDuration, url);
      } else {
        __.outDefault.call(_this, outClass, outDuration, url);
      }
    },

    outDefault: function(outClass, outDuration, url){
      var $this = $(this);
      var options = $this.data(namespace).options;

      // (outDuration + 1) | #55 outDuration: 0 crashes on Safari only
      $this
        .css({ 'animation-duration' : (outDuration + 1)  + 'ms' })
        .addClass(outClass)
        .trigger(__.settings.events.outStart)
        .animateCallback(function(){
          $this.trigger(__.settings.events.outEnd);
          options.transition(url);
        });
    },

    outOverlay: function(outClass, outDuration, url){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var thisInClass = $this.data(__.settings.data.inClass);
      var inClass = __.animationCheck.call(_this, thisInClass, true, true);

      // (outDuration + 1) | #55 outDuration: 0 crashes animsition on Safari only
      $(options.overlayParentElement)
        .children('.' + options.overlayClass)
        .css({ 'animation-duration' : (outDuration + 1) + 'ms' })
        .removeClass(inClass)
        .addClass(outClass)
        .trigger(__.settings.events.outStart)
        .animateCallback(function(){
          $this.trigger(__.settings.events.outEnd);
          options.transition(url);
        });
    },

    destroy: function(){
      return this.each(function(){
        var $this = $(this);
        $(window).off('.'+ namespace);
        $this
          .css({'opacity': 1})
          .removeData(namespace);
      });
    }

  };

  $.fn.animateCallback = function(callback){
    var end = 'animationend webkitAnimationEnd';
    return this.each(function() {
      var $this = $(this);
      $this.on(end, function(){
        $this.off(end);
        return callback.call(this);
      });
    });
  };

  $.fn.animsition = function(method){
    if ( __[method] ) {
      return __[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return __.init.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.'+namespace);
    }
  };

}));
<commit_msg>:art:<commit_after>;(function (factory) {
  'use strict';
  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory(require('jquery'));
  } else {
    factory(jQuery);
  }
}(function ($) {
  'use strict';
  var namespace = 'animsition';
  var __ = {
    init: function(options){
      options = $.extend({
        inClass               :   'fade-in',
        outClass              :   'fade-out',
        inDuration            :    1500,
        outDuration           :    800,
        linkElement           :   '.animsition-link',
        // e.g. linkElement   :   'a:not([target="_blank"]):not([href^=#])'
        loading               :    true,
        loadingParentElement  :   'body', //animsition wrapper element
        loadingClass          :   'animsition-loading',
        loadingInner          :   '', // e.g ''
        timeout               :   false,
        timeoutCountdown      :   5000,
        onLoadEvent           :   true,
        browser               : [ 'animation-duration', '-webkit-animation-duration'],
        // "browser" option allows you to disable the "animsition" in case the css property in the array is not supported by your browser.
        // The default setting is to disable the "animsition" in a browser that does not support "animation-duration".
        overlay               :   false,
        overlayClass          :   'animsition-overlay-slide',
        overlayParentElement  :   'body',
        transition            :   function(url){ window.location.href = url; }
      }, options);

      __.settings = {
        timer: false,
        data: {
          inClass: 'animsition-in-class',
          inDuration: 'animsition-in-duration',
          outClass: 'animsition-out-class',
          outDuration: 'animsition-out-duration',
          overlay: 'animsition-overlay'
        },
        events: {
          inStart: 'animsition.inStart',
          inEnd: 'animsition.inEnd',
          outStart: 'animsition.outStart',
          outEnd: 'animsition.outEnd'
        }
      };

      // Remove the "Animsition" in a browser
      // that does not support the "animaition-duration".
      var support = __.supportCheck.call(this, options);

      if(!support && options.browser.length > 0){
        if(!support || !this.length){
          // If do not have a console object to object window
          if (!('console' in window)) {
            window.console = {};
            window.console.log = function(str){ return str; };
          }
          if(!this.length) console.log('Animsition: Element does not exist on page.');
          if(!support) console.log('Animsition: Does not support this browser.');
          return __.destroy.call(this);
        }
      }

      var overlayMode = __.optionCheck.call(this, options);

      if(overlayMode) __.addOverlay.call(this, options);

      if(options.loading) __.addLoading.call(this, options);

      return this.each(function(){
        var _this = this;
        var $this = $(this);
        var $window = $(window);
        var $document = $(document);
        var data = $this.data(namespace);

        if (!data) {
          options = $.extend({}, options);

          $this.data(namespace, { options: options });

          if(options.timeout) __.addTimer.call(_this);

          if(options.onLoadEvent) {
            $window.on('load.' + namespace, function() {
              if(__.settings.timer) clearTimeout(__.settings.timer);
              __.in.call(_this);
            });
          }

          $window.on('pageshow.' + namespace, function(event) {
            if(event.originalEvent.persisted) __.in.call(_this);
          });

          // Firefox back button issue #4
          $window.on('unload.' + namespace, function() { });

          $document.on('click.' + namespace, options.linkElement, function(event) {
            event.preventDefault();
            var $self = $(this);
            var url = $self.attr('href');

            // middle mouse button issue #24
            // if(middle mouse button || command key || shift key || win control key)
            if (event.which === 2 || event.metaKey || event.shiftKey || navigator.platform.toUpperCase().indexOf('WIN') !== -1 && event.ctrlKey) {
              window.open(url, '_blank');
            } else {
              __.out.call(_this, $self, url);
            }

          });
        }
      }); // end each
    },

    addOverlay: function(options){
      $(options.overlayParentElement)
        .prepend('

');
    },

    addLoading: function(options){
      $(options.loadingParentElement)
        .append('
' + options.loadingInner + '
');
    },

    removeLoading: function(){
      var $this     = $(this);
      var options   = $this.data(namespace).options;
      var $loading  = $(options.loadingParentElement).children('.' + options.loadingClass);

      $loading.fadeOut().remove();
    },

    addTimer: function(){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;

      __.settings.timer = setTimeout(function(){
        __.in.call(_this);
        $(window).off('load.' + namespace);
      }, options.timeoutCountdown);
    },

    supportCheck: function(options){
      var $this = $(this);
      var props = options.browser;
      var propsNum = props.length;
      var support  = false;

      if (propsNum === 0) {
        support = true;
      }
      for (var i = 0; i < propsNum; i++) {
        if (typeof $this.css(props[i]) === 'string') {
          support = true;
          break;
        }
      }
      return support;
    },

    optionCheck: function(options){
      var $this = $(this);
      var overlayMode;
      if(options.overlay || $this.data(__.settings.data.overlay)){
        overlayMode = true;
      } else {
        overlayMode = false;
      }
      return overlayMode;
    },

    animationCheck : function(data, stateClass, stateIn){
      var $this = $(this);
      var options = $this.data(namespace).options;
      var dataType = typeof data;
      var dataDuration = !stateClass && dataType === 'number';
      var dataClass = stateClass && dataType === 'string' && data.length > 0;

      if(dataDuration || dataClass){
        data = data;
      } else if(stateClass && stateIn) {
        data = options.inClass;
      } else if(!stateClass && stateIn) {
        data = options.inDuration;
      } else if(stateClass && !stateIn) {
        data = options.outClass;
      } else if(!stateClass && !stateIn) {
        data = options.outDuration;
      }
      return data;
    },

    in: function(){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var thisInDuration = $this.data(__.settings.data.inDuration);
      var thisInClass = $this.data(__.settings.data.inClass);
      var inDuration = __.animationCheck.call(_this, thisInDuration, false, true);
      var inClass = __.animationCheck.call(_this, thisInClass, true, true);
      var overlayMode = __.optionCheck.call(_this, options);
      var outClass = $this.data(namespace).outClass;

      if(options.loading) __.removeLoading.call(_this);

      if(outClass) $this.removeClass(outClass);

      if(overlayMode) {
        __.inOverlay.call(_this, inClass, inDuration);
      } else {
        __.inDefault.call(_this, inClass, inDuration);
      }
    },

    inDefault: function(inClass, inDuration){
      var $this = $(this);

      $this
        .css({ 'animation-duration' : inDuration + 'ms' })
        .addClass(inClass)
        .trigger(__.settings.events.inStart)
        .animateCallback(function(){
          $this
            .removeClass(inClass)
            .css({ 'opacity' : 1 })
            .trigger(__.settings.events.inEnd);
        });
    },

    inOverlay: function(inClass, inDuration){
      var $this = $(this);
      var options = $this.data(namespace).options;

      $this
        .css({ 'opacity' : 1 })
        .trigger(__.settings.events.inStart);

      $(options.overlayParentElement)
        .children('.' + options.overlayClass)
        .css({ 'animation-duration' : inDuration + 'ms' })
        .addClass(inClass)
        .animateCallback(function(){
          $this
            .trigger(__.settings.events.inEnd);
        });
    },

    out: function($self, url){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var selfOutClass = $self.data(__.settings.data.outClass);
      var thisOutClass = $this.data(__.settings.data.outClass);
      var selfOutDuration = $self.data(__.settings.data.outDuration);
      var thisOutDuration = $this.data(__.settings.data.outDuration);
      var isOutClass = selfOutClass ? selfOutClass : thisOutClass;
      var isOutDuration = selfOutDuration ? selfOutDuration : thisOutDuration;
      var outClass = __.animationCheck.call(_this, isOutClass, true, false);
      var outDuration = __.animationCheck.call(_this, isOutDuration, false, false);
      var overlayMode = __.optionCheck.call(_this, options);

      $this.data(namespace).outClass = outClass;

      if(overlayMode) {
        __.outOverlay.call(_this, outClass, outDuration, url);
      } else {
        __.outDefault.call(_this, outClass, outDuration, url);
      }
    },

    outDefault: function(outClass, outDuration, url){
      var $this = $(this);
      var options = $this.data(namespace).options;

      // (outDuration + 1) | #55 outDuration: 0 crashes on Safari only
      $this
        .css({ 'animation-duration' : (outDuration + 1)  + 'ms' })
        .addClass(outClass)
        .trigger(__.settings.events.outStart)
        .animateCallback(function(){
          $this.trigger(__.settings.events.outEnd);
          options.transition(url);
        });
    },

    outOverlay: function(outClass, outDuration, url){
      var _this = this;
      var $this = $(this);
      var options = $this.data(namespace).options;
      var thisInClass = $this.data(__.settings.data.inClass);
      var inClass = __.animationCheck.call(_this, thisInClass, true, true);

      // (outDuration + 1) | #55 outDuration: 0 crashes animsition on Safari only
      $(options.overlayParentElement)
        .children('.' + options.overlayClass)
        .css({ 'animation-duration' : (outDuration + 1) + 'ms' })
        .removeClass(inClass)
        .addClass(outClass)
        .trigger(__.settings.events.outStart)
        .animateCallback(function(){
          $this.trigger(__.settings.events.outEnd);
          options.transition(url);
        });
    },

    destroy: function(){
      return this.each(function(){
        var $this = $(this);
        $(window).off('.'+ namespace);
        $this
          .css({'opacity': 1})
          .removeData(namespace);
      });
    }

  };

  $.fn.animateCallback = function(callback){
    var end = 'animationend webkitAnimationEnd';
    return this.each(function() {
      var $this = $(this);
      $this.on(end, function(){
        $this.off(end);
        return callback.call(this);
      });
    });
  };

  $.fn.animsition = function(method){
    if ( __[method] ) {
      return __[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return __.init.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.'+namespace);
    }
  };

}));
<|endoftext|>


<commit_before>import { hatenaBlue } from './components/colors';

export const designs = [
    'legacy',
    'legacy-no-bold',
    'legacy-num-only',
    'legacy-num-only-no-bold',
    'button',
    'button-num-only'
];

const hatebuSvg = encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="#fff" d="M7.24 6.36a2.37 2.37 0 00-1.66-.75A2.72 2.72 0 007 4.85a2 2 0 00.43-1.35 2.42 2.42 0 00-.33-1.2 2.15 2.15 0 00-.84-.82 3.59 3.59 0 00-1.16-.37A17.41 17.41 0 002.74 1H0v10h2.83a18.33 18.33 0 002.45-.12 4 4 0 001.26-.39 2.31 2.31 0 001-.94 2.86 2.86 0 00.34-1.41 2.57 2.57 0 00-.64-1.78zM2.53 3.22h.59a2.77 2.77 0 011.37.23.88.88 0 01.35.79.82.82 0 01-.38.76 3 3 0 01-1.38.22h-.55zM4.85 9a2.79 2.79 0 01-1.37.24h-1V7h1a2.61 2.61 0 011.36.25 1 1 0 01.37.88.87.87 0 01-.36.87z"/><circle fill="#fff" cx="10.73" cy="9.73" r="1.27"/><path fill="#fff" d="M9.63 1h2.2v6.67h-2.2z"/></svg>');

export const getBadgeStyle = (design, addPrefix = false) => {
    const style = getOriginalStyle(design);
    if (addPrefix) {
        return style.replaceAll(/ (\.fh-badge(:[a-z]+)?) /g, ` .${design} $1 `);
    } else {
        return style;
    }
};

const getOriginalStyle = (design) => {
    const noBold = design.includes('no-bold');
    const numOnly = design.includes('num-only');
    switch (design) {
        case 'legacy':
        case 'legacy-no-bold':
        case 'legacy-num-only':
        case 'legacy-num-only-no-bold':
            return `
            .fh-badge {
                padding: 0 0 1px 0;
                border-bottom-color: #ff0808;
                height: 15px;
                background-color: #ffcbcb;
                font-family: monospace;
                font-size: 12px;
                color: #ff0808;
                ${noBold ? '' : 'text-shadow: 1px 0 #ff0808;'}
                line-height: 15px;
                cursor: pointer;
            }
            .fh-badge.fh-badge-one,
            .fh-badge.fh-badge-lt10 {
                border-bottom-color: #ff6565;
                background-color: #ffeeee;
                color: #ff6565;
                ${noBold ? '' : 'text-shadow: 1px 0 #ff6565;'}
            }
            .fh-badge a {
                border-bottom-style: solid;
                border-bottom-width: 1px;
                border-bottom-color: inherit;
                color: inherit;
            }
            .metadata .fh-badge a:hover,
            .fx .entry .fh-badge a:hover {
                color: inherit;
                text-decoration: none;
            }
            ${numOnly ? '' : `
            .fh-badge a::after {
                content: "users";
                margin-left: 2px;
            }
            .fh-badge.fh-badge-one a::after {
                content: "user";
            }
            `}
            `;
        case 'button':
        case 'button-num-only':
            return `
            .fh-badge {
                display: inline-block;
                padding: 2px;
                border-radius: 3px;
                background-color: ${hatenaBlue};
                line-height: 9px;
                cursor: pointer;
                color: #fff;
            }
            .fh-badge:hover {
                background-color: #0091c5;
                color: #fff;
            }
            .fh-badge a {
                display: inline-block;
                padding: 3px;
                border-radius: 3px;
                font-size: 11px;
                font-family: Helvetica,Arial,"Hiragino Kaku Gothic ProN","ヒラギノ角ゴ ProN W3",Meiryo,"メイリオ",sans-serif;
                font-weight: bold;
                color: inherit;
            }
            .fh-badge a:hover {
                background: #006b92;
                color: #fff;
                text-decoration: none !important;
                color: inherit;
            }
            ${numOnly ? '' : `
            .fh-badge {
                padding-left: 17px;
                background-image: url('data:image/svg+xml,${hatebuSvg}');
                background-repeat: no-repeat;
                background-size: 11px;
                background-position: 5px center;
            }
            `}
           `;
    }
    return '';
}<commit_msg>2.1.0: Add feedly link design<commit_after>import { hatenaBlue } from './components/colors';

export const designs = [
    'legacy',
    'legacy-no-bold',
    'legacy-num-only',
    'legacy-num-only-no-bold',
    'button',
    'button-num-only',
    'feedly',
    'feedly-num-only',
];

const hatebuSvg = encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="#fff" d="M7.24 6.36a2.37 2.37 0 00-1.66-.75A2.72 2.72 0 007 4.85a2 2 0 00.43-1.35 2.42 2.42 0 00-.33-1.2 2.15 2.15 0 00-.84-.82 3.59 3.59 0 00-1.16-.37A17.41 17.41 0 002.74 1H0v10h2.83a18.33 18.33 0 002.45-.12 4 4 0 001.26-.39 2.31 2.31 0 001-.94 2.86 2.86 0 00.34-1.41 2.57 2.57 0 00-.64-1.78zM2.53 3.22h.59a2.77 2.77 0 011.37.23.88.88 0 01.35.79.82.82 0 01-.38.76 3 3 0 01-1.38.22h-.55zM4.85 9a2.79 2.79 0 01-1.37.24h-1V7h1a2.61 2.61 0 011.36.25 1 1 0 01.37.88.87.87 0 01-.36.87z"/><circle fill="#fff" cx="10.73" cy="9.73" r="1.27"/><path fill="#fff" d="M9.63 1h2.2v6.67h-2.2z"/></svg>');

export const getBadgeStyle = (design, addPrefix = false) => {
    const style = getOriginalStyle(design);
    if (addPrefix) {
        return style.replaceAll(/ (\.fh-badge(:{1,2}[a-z]+)?) /g, ` .${design} $1 `);
    } else {
        return style;
    }
};

const getOriginalStyle = (design) => {
    const noBold = design.includes('no-bold');
    const numOnly = design.includes('num-only');
    switch (design) {
        case 'legacy':
        case 'legacy-no-bold':
        case 'legacy-num-only':
        case 'legacy-num-only-no-bold':
            return `
            .fh-badge {
                padding: 0 0 1px 0;
                border-bottom-color: #ff0808;
                height: 15px;
                background-color: #ffcbcb;
                font-family: monospace;
                font-size: 12px;
                color: #ff0808;
                ${noBold ? '' : 'text-shadow: 1px 0 #ff0808;'}
                line-height: 15px;
                cursor: pointer;
            }
            .fh-badge.fh-badge-one,
            .fh-badge.fh-badge-lt10 {
                border-bottom-color: #ff6565;
                background-color: #ffeeee;
                color: #ff6565;
                ${noBold ? '' : 'text-shadow: 1px 0 #ff6565;'}
            }
            .fh-badge a {
                border-bottom-style: solid;
                border-bottom-width: 1px;
                border-bottom-color: inherit;
                color: inherit;
            }
            .metadata .fh-badge a:hover,
            .fx .entry .fh-badge a:hover {
                color: inherit;
                text-decoration: none;
            }
            ${numOnly ? '' : `
            .fh-badge a::after {
                content: "users";
                margin-left: 2px;
            }
            .fh-badge.fh-badge-one a::after {
                content: "user";
            }
            `}
            `;
        case 'button':
        case 'button-num-only':
            return `
            .fh-badge {
                display: inline-block;
                padding: 2px;
                border-radius: 3px;
                background-color: ${hatenaBlue};
                line-height: 9px;
                cursor: pointer;
                color: #fff;
            }
            .fh-badge:hover {
                background-color: #0091c5;
                color: #fff;
            }
            .fh-badge a {
                display: inline-block;
                padding: 3px;
                border-radius: 3px;
                font-size: 11px;
                font-family: Helvetica,Arial,"Hiragino Kaku Gothic ProN","ヒラギノ角ゴ ProN W3",Meiryo,"メイリオ",sans-serif;
                font-weight: bold;
                color: inherit;
            }
            .fh-badge a:hover {
                background: #006b92;
                color: #fff;
                text-decoration: none !important;
                color: inherit;
            }
            ${numOnly ? '' : `
            .fh-badge {
                padding-left: 17px;
                background-image: url('data:image/svg+xml,${hatebuSvg}');
                background-repeat: no-repeat;
                background-size: 11px;
                background-position: 5px center;
            }
            `}
           `;
        case 'feedly':
        case 'feedly-num-only':
            return `
            .fh-badge {
                cursor: pointer;
                color: #9e9e9e;
                font-size: 11px;
                font-family: sans-serif;
            }
            .metadata .fh-badge {
                font-size: 13px;
            }
            .visual .fh-badge {
                border-radius: 1px;
                background-color: #fff;
            }
            .fh-badge a {
                color: inherit;
            }
            .fh-badge a:hover {
                color: #636363;
                text-decoration: underline;
            }
            ${numOnly ? '' : `
            .fh-badge::before {
                content: "B!";
                margin-right: 0.25em;
            }
            `}
            `;
    }
    return '';
}<|endoftext|>


<commit_before>var riot = require('riot');
var DataFilter = require('./dataFilter');

function Controller(){
    /**
     * Communicates between app parts and holds data.
     *
     */

    //make observable
    riot.observable(this);
    this.itemDict = null;
    this.itemList = null;
    this.markers = null;
    this._filterer = new DataFilter();
    this._filter = "";
    var self = this;

    riot.route(this._router.bind(this));

    this.on('ItemsLoaded', this._processItems.bind(this));

    this.one('StartApp', function(){
        riot.route.exec(this._router.bind(this));
    }.bind(this));

    this.on('UpdateFilter', function(filter){
        self._filterItems(filter);
    });
}

Controller.prototype = {
    _filterItems: function(filter){
        this._filter = filter;
        this.itemList = this._filterer.filter(filter);

        var m;
        var markers = new Set(this.itemList.map(function(i){
            return i.marker;
        }));

        this.markers = [];

        for (m of markers){
            this.markers.push(this.itemDict[m])
        }

        this.trigger("ItemsUpdated", this.itemList);
    },
    _processItems: function(items){
        var itemDict = {};
        var markerMap = {};

        this._filterer = new DataFilter(items);
        this.itemList = items;

        var key, associated, i;

        for (i = 0; i<items.length; i++){
            item = items[i];
            key = item.overlay_type + item.id;
            itemDict[item.overlay_type + item.id] = item;

            // map items to containing markers
            if (item.overlay_type === 'marker'){
                associated = [key];
                if (item.photo_array !== null){
                    associated.push.apply(associated, item.photo_array.map(function(i){return "photo" + i}));
                }
                if (item.audio_array !== null){
                    associated.push.apply(associated, item.audio_array.map(function(i){return "audio" + i}));
                }

                markerMap[key]=associated;
            }
        }
        this.itemDict = itemDict;

        // adds marker to each item, should also modify itemList since references are the same
        for (marker in markerMap){
            associated = markerMap[marker];
            for (i=0;i<associated.length;i++){
                itemDict[associated[i]].marker = marker;
            }
        }

        this._filterItems(this._filter);
    },
    _router: function(){
        // routes should be in format view/filter1/filter2/etc ex: map/sites/nature
        // or view/collection/id ex: list/people/12
        //
        // this makes an array with 'ActivateView' as first arg, and arguments as added on
        args = ['ActivateView'].concat(Array.prototype.slice.call(arguments));
        if (args.length === 1){ args.push('Map');}
        this.trigger.apply(this, args);
    }
};

module.exports = Controller;
<commit_msg>less binding<commit_after>var riot = require('riot');
var DataFilter = require('./dataFilter');

function Controller(){
    /**
     * Communicates between app parts and holds data.
     *
     */

    //make observable
    riot.observable(this);
    this.itemDict = null;
    this.itemList = null;
    this.markers = null;
    this._filterer = new DataFilter();
    this._filter = "";
    var self = this;

    riot.route(this._router.bind(this));

    this.on('ItemsLoaded', this._processItems.bind(this));

    this.one('StartApp', function(){
        riot.route.exec(self._router.bind(self));
    });

    this.on('UpdateFilter', function(filter){
        self._filterItems(filter);
    });
}

Controller.prototype = {
    _filterItems: function(filter){
        this._filter = filter;
        this.itemList = this._filterer.filter(filter);

        var m;
        var markers = new Set(this.itemList.map(function(i){
            return i.marker;
        }));

        this.markers = [];

        for (m of markers){
            this.markers.push(this.itemDict[m])
        }

        this.trigger("ItemsUpdated", this.itemList);
    },
    _processItems: function(items){
        var itemDict = {};
        var markerMap = {};

        this._filterer = new DataFilter(items);
        this.itemList = items;

        var key, associated, i;

        for (i = 0; i<items.length; i++){
            item = items[i];
            key = item.overlay_type + item.id;
            itemDict[item.overlay_type + item.id] = item;

            // map items to containing markers
            if (item.overlay_type === 'marker'){
                associated = [key];
                if (item.photo_array !== null){
                    associated.push.apply(associated, item.photo_array.map(function(i){return "photo" + i}));
                }
                if (item.audio_array !== null){
                    associated.push.apply(associated, item.audio_array.map(function(i){return "audio" + i}));
                }

                markerMap[key]=associated;
            }
        }
        this.itemDict = itemDict;

        // adds marker to each item, should also modify itemList since references are the same
        for (marker in markerMap){
            associated = markerMap[marker];
            for (i=0;i<associated.length;i++){
                itemDict[associated[i]].marker = marker;
            }
        }

        this._filterItems(this._filter);
    },
    _router: function(){
        // routes should be in format view/filter1/filter2/etc ex: map/sites/nature
        // or view/collection/id ex: list/people/12
        //
        // this makes an array with 'ActivateView' as first arg, and arguments as added on
        args = ['ActivateView'].concat(Array.prototype.slice.call(arguments));
        if (args.length === 1){ args.push('Map');}
        this.trigger.apply(this, args);
    }
};

module.exports = Controller;
<|endoftext|>