class GeneticAlgorithm { constructor(maxUnits, topUnits, game) { this.maxUnits = maxUnits; this.topUnits = topUnits; if(this.maxUnits < this.topUnits) this.topUnits = maxUnits; this.population = []; this.SCALE_FACTOR = 200; this.game = game; } reset() { this.iteration = 1; this.mutateRate = 1; this.bestPopulation = 0; this.bestFitness = 0; this.bestScore = 0; } createPopulation() { this.population.slice(0, this.population.length); for(let i = 0; i < this.maxUnits; i++) { const newUnit = new synaptic.Architect.Perceptron(6 * this.game.fieldSize.x * this.game.fieldSize.y, 35, 5); newUnit.index = i; newUnit.fitness = 0; newUnit.score = 0; newUnit.isWinner = false; this.population.push(newUnit); } } activateBrain(civilization) { // TODO: Inputs const inputs = [civilization.balance, civilization.getBalanceChange()]; for(let x = 0; x < this.game.fieldSize.x; x++) { for(let y = 0; y < this.game.fieldSize.y; y++) { const tile = this.game.field[x][y]; const isFriendly = tile.claimedBy === civilization ? 1 : 0; const hasObject = !!tile.object ? 1 : 0; const canAttack = tile.object instanceof Warrior ? 1 : 0; const rank = !!tile.object ? tile.object.rank / 4 : 0; inputs.push(x, y, isFriendly, hasObject, canAttack, rank); } } console.log(civilization.index); const outputs = this.population[civilization.index].activate(inputs); console.log(outputs); const type = outputs[0]; const x = Math.round(outputs[1] * (this.game.fieldSize.x - 1)); const y = Math.round(outputs[2] * (this.game.fieldSize.y - 1)); const tile = this.game.field[x][y]; if(type < .5) { if(tile.object && tile.claimedBy === civilization && tile.object instanceof Warrior) { const deltaX = Math.round(outputs[3] * 4 - 2) / 4; const deltaY = Math.round(outputs[4] * 4 - 2) / 4; const targetX = x + deltaX; const targetY = y + deltaY; tile.object.move(targetX, targetY); console.log(deltaX, deltaY); } } else { const itemType = outputs[3]; console.log('hey'); if(itemType > .5) { console.log('ho'); if(!tile.getAdjacentTiles().find(tile => tile.claimedBy === civilization)) return; const troopType = Math.round(outputs[4] * 3); const troopTypes = [Farmer, Hoplite, Servant, Knight]; this.game.addWarrior(x, y, civilization, troopTypes[troopType]); } else { if(tile.claimedBy !== civilization) return; const buildingType = Math.round(outputs[4] * 2); const buildingTypes = [Farm, SmallTower, BigTower]; console.log('ho2', buildingType, tile); this.game.addBuilding(x, y, civilization, buildingTypes[buildingType]); } } } evolvePopulation() { const winners = this.selection(); if(this.mutateRate === 1 && winners[0].fitness < 0) { this.createPopulation(); } else { this.mutateRate = .2; } for(let i = this.topUnits; i < this.maxUnits; i++) { let parentA, parentB, offspring; if(i === this.topUnits) { parentA = winners[0].toJSON(); parentB = winners[1].toJSON(); offspring = this.crossOver(parentA, parentB); } else if(i < this.maxUnits - 2) { parentA = this.getRandomUnit(winners).toJSON(); parentB = this.getRandomUnit(winners).toJSON(); offspring = this.crossOver(parentA, parentB); } else { offspring = this.getRandomUnit(winners).toJSON(); } offspring = this.mutation(offspring); const newUnit = synaptic.Network.fromJSON(offspring); newUnit.index = this.population[i].index; newUnit.fitness = 0; newUnit.score = 0; newUnit.isWinner = false; this.population[i] = newUnit; } if(winners[0].fitness > this.bestFitness) { this.bestPopulation = this.iteration; this.bestFitness = winners[0].fitness; this.bestScore = winners[0].score; } this.population.sort((unitA, unitB) => unitA.index - unitB.index); } selection() { const sortedPopulation = this.population.sort((unitA, unitB) => unitB.fitness - unitA.fitness); for(let i = 0; i < this.topUnits; i++) { this.population[i].isWinner = true; } return sortedPopulation.slice(0, this.topUnits); } crossOver(parentA, parentB) { const cutPoint = this.random(0, parentA.neurons.length - 19); for(let i = cutPoint; i < parentA.neurons.length; i++) { const biasFromParentA = parentA.neurons[i]['bias']; parentA.neurons[i]['bias'] = parentB.neurons[i]['bias']; parentB.neurons[i]['bias'] = biasFromParentA; } return this.random(0, 1) === 1 ? parentA : parentB; } mutation(offspring) { for(let i = 0; i < offspring.neurons.length; i++) { offspring.neurons[i]['bias'] = this.mutate(offspring.neurons[i]['bias']); } for(let i = 0; i < offspring.connections.length; i++) { offspring.connections[i]['weight'] = this.mutate(offspring.connections[i]['weight']); } return offspring; } mutate(gene) { if(Math.random() < this.mutateRate) { gene *= 1 + ((Math.random() - .5) * 3 + (Math.random() - .5)); } return gene; } random(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } getRandomUnit(array) { return array[this.random(0, array.length - 1)]; } normalize(value, max) { if(value < -max) value = -max; else if(value > max) value = max; return value / max; } }