整理桌面

This commit is contained in:
2022-04-10 00:37:53 +08:00
parent 82e3f2623f
commit e25c8bb318
728 changed files with 986384 additions and 16 deletions

View File

@@ -0,0 +1,92 @@
/* Global vars */
var players = [];
var foods = [];
var iteration = 0;
var highestScore = 0;
/** Setup the canvas */
function setup(){
var canvas = createCanvas(WIDTH, HEIGHT);
canvas.parent('field');
initNeat();
// Create some food
for(var i = 0; i < FOOD_AMOUNT; i++){
new Food();
}
startEvaluation();
}
function draw(){
clear();
squareGrid();
// Check if evaluation is done
if(iteration == ITERATIONS){
endEvaluation();
iteration = 0;
}
// Update and visualise players
for(var i = players.length - 1; i >= 0; i--){
var player = players[i];
// Some players are eaten during the iteration
player.update();
player.show();
}
// Update and visualise food
for(var i = foods.length - 1; i >= 0; i--){
foods[i].show();
}
iteration++;
}
/** Draw a square grid with grey lines */
function squareGrid(){
stroke(204, 204, 204, 160);
fill(255);
for(var x = 0; x < WIDTH/40; x++){
line(x * 40, 0, x * 40, HEIGHT);
}
for(var y = 0; y < HEIGHT/40; y++){
line(0, y * 40, WIDTH, y * 40);
}
noStroke();
}
/** Calculate distance between two points */
function distance(x1, y1, x2, y2){
var dx = x1 - x2;
var dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
}
/** Get a relative color between red and green */
var activationColor = function(value, max){
var power = 1 - Math.min(value/max, 1);
var color = [255, 255, 0]
if(power < 0.5){
color[0] = 2 * power * 255;
} else {
color[1] = (1.0 - 2 * (power - 0.5)) * 255;
}
return color;
}
/** Get the angle from one point to another */
function angleToPoint(x1, y1, x2, y2){
d = distance(x1, y1, x2, y2);
dx = (x2-x1) / d;
dy = (y2-y1) / d;
a = Math.acos(dx);
a = dy < 0 ? 2 * Math.PI - a : a;
return a;
}

View File

@@ -0,0 +1,31 @@
function Food(){
this.x = Math.floor(Math.random() * WIDTH);
this.y = Math.floor(Math.random() * HEIGHT);
this.area = FOOD_AREA;
this.color = [
Math.round(Math.random() * 255),
Math.round(Math.random() * 255),
Math.round(Math.random() * 255)
];
foods.push(this);
}
Food.prototype = {
/** Display the player on the field */
show: function(){
var radius = Math.sqrt(this.area / PI);
fill(this.color[0], this.color[1], this.color[2]);
noStroke();
ellipse(this.x, this.y, radius);
},
/** Restart from new position */
restart: function(){
this.x = Math.floor(Math.random() * WIDTH);
this.y = Math.floor(Math.random() * HEIGHT);
},
};

View File

@@ -0,0 +1,37 @@
var scripts = [
{ type: 'script', url: "https://cdn.rawgit.com/wagenaartje/neataptic/a7610e38/dist/neataptic.js"},
{ type: 'script', url: "https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.10/p5.js"},
{ type: 'script', url: "../../js/articles/agar.ioai/main.js"},
{ type: 'script', url: "../../js/articles/agar.ioai/population.js"},
{ type: 'script', url: "../../js/articles/agar.ioai/player.js"},
{ type: 'script', url: "../../js/articles/agar.ioai/food.js"},
{ type: 'script', url: "../../js/articles/agar.ioai/field.js"}
];
/** https://stackoverflow.com/questions/33330636/load-javascript-dynamically-and-sequentially **/
function require(list) {
function loadScript(link) {
return new Promise(function(fulfill, reject) {
if(link.type == 'script'){
var script = document.createElement("script");
script.addEventListener("load", fulfill);
script.src = link.url;
document.head.appendChild(script);
} else if(link.type == 'css'){
var stylesheet = document.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.type = 'text/css';
stylesheet.href = link.url;
stylesheet.media = "screen,print";
document.head.appendChild(stylesheet);
}
});
}
loadScript(list.shift()).then(function() {
if (list.length > 0) {
require(list);
}
})
}
require(scripts);

View File

@@ -0,0 +1,113 @@
/** Rename vars */
var Neat = neataptic.Neat;
var Methods = neataptic.Methods;
var Config = neataptic.Config;
var Architect = neataptic.Architect;
/** Turn off warnings */
Config.warnings = false;
/** Settings */
var WIDTH = $('#field').width();
var HEIGHT = 500;
var MAX_AREA = 10000;
var MIN_AREA = 400;
var RELATIVE_SIZE = 1.1;
var DECREASE_SIZE = 0.998;
var DETECTION_RADIUS = 150;
var FOOD_DETECTION = 3;
var PLAYER_DETECTION = 3;
var MIN_SPEED = 0.6;
var SPEED = 2;
var FOOD_AREA = 80;
var FOOD_AMOUNT = Math.round(WIDTH * HEIGHT * 4e-4);
// GA settings
var PLAYER_AMOUNT = Math.round(WIDTH * HEIGHT * 8e-5);
var ITERATIONS = 10e10;
var START_HIDDEN_SIZE = 0;
// Trained population
var USE_TRAINED_POP = true;
// Global vars
var neat;
/** Construct the genetic algorithm */
function initNeat(){
neat = new Neat(
1 + PLAYER_DETECTION * 3 + FOOD_DETECTION * 2,
2,
null,
{
mutation: [
Methods.Mutation.ADD_NODE,
Methods.Mutation.SUB_NODE,
Methods.Mutation.ADD_CONN,
Methods.Mutation.SUB_CONN,
Methods.Mutation.MOD_WEIGHT,
Methods.Mutation.MOD_BIAS,
Methods.Mutation.MOD_ACTIVATION,
Methods.Mutation.ADD_GATE,
Methods.Mutation.SUB_GATE,
Methods.Mutation.ADD_SELF_CONN,
Methods.Mutation.SUB_SELF_CONN,
Methods.Mutation.ADD_BACK_CONN,
Methods.Mutation.SUB_BACK_CONN
],
popsize: PLAYER_AMOUNT,
mutationRate: 0.3,
elitism: Math.round(0.1 * PLAYER_AMOUNT),
network: new Architect.Random(
1 + PLAYER_DETECTION * 3 + FOOD_DETECTION * 2,
START_HIDDEN_SIZE,
2
)
}
);
if(USE_TRAINED_POP){
neat.population = population;
}
}
/** Start the evaluation of the current generation */
function startEvaluation(){
players = [];
highestScore = 0;
for(var genome in neat.population){
genome = neat.population[genome];
new Player(genome);
}
}
/** End the evaluation of the current generation */
function endEvaluation(){
console.log('Generation:', neat.generation, '- average score:', neat.getAverage());
neat.sort();
var newPopulation = [];
// Elitism
for(var i = 0; i < neat.elitism; i++){
newPopulation.push(neat.population[i]);
}
// Breed the next individuals
for(var i = 0; i < neat.popsize - neat.elitism; i++){
newPopulation.push(neat.getOffspring());
}
// Replace the old population with the new population
neat.population = newPopulation;
neat.mutate();
neat.generation++;
startEvaluation();
}

View File

@@ -0,0 +1,180 @@
function Player(genome){
this.x = Math.floor(Math.random() * WIDTH);
this.y = Math.floor(Math.random() * HEIGHT);
this.vx = 0;
this.vy = 0;
this.brain = genome;
this.brain.score = 0;
this.area = MIN_AREA;
this.visualarea = this.area;
players.push(this);
}
Player.prototype = {
/** Update the stats */
update: function(){
if(this.area > MAX_AREA) this.area = MAX_AREA;
if(this.area < MIN_AREA) this.area = MIN_AREA;
var input = this.detect();
var output = this.brain.activate(input);
var moveangle = output[0] * 2 * PI;
var movespeed = output[1] > 1 ? 1 : output[1] < 0 ? 0 : output[1];
this.vx = movespeed * Math.cos(moveangle) * SPEED;
this.vy = movespeed * Math.sin(moveangle) * SPEED;
// Large blobs move slower
this.vx *= Math.max(1 - (this.area / MAX_AREA), MIN_SPEED / SPEED);
this.vy *= Math.max(1 - (this.area / MAX_AREA), MIN_SPEED / SPEED);
this.x += this.vx;
this.y += this.vy;
// Limit position to width and height
this.x = this.x >= WIDTH ? this.x % WIDTH : this.x <= 0 ? this.x + WIDTH : this.x;
this.y = this.y >= HEIGHT ? this.y % HEIGHT : this.y <= 0 ? this.y + HEIGHT : this.y;
this.area *= DECREASE_SIZE;
// Replace highest score to visualise
this.brain.score = this.area;
highestScore = this.brain.score > highestScore ? this.brain.score : highestScore;
},
/** Restart from new position */
restart: function(){
this.x = Math.floor(Math.random() * WIDTH);
this.y = Math.floor(Math.random() * HEIGHT);
this.vx = 0;
this.vy = 0;
this.area = MIN_AREA;
this.visualarea = this.area;
},
/** Display the player on the field */
show: function(){
this.visualarea = lerp(this.visualarea, this.area, 0.2);
var radius = Math.sqrt(this.visualarea / PI);
var color = activationColor(this.brain.score, highestScore);
fill(color);
ellipse(this.x, this.y, radius);
},
/** Visualies the detection of the brain */
showDetection: function(detected){
noFill();
for(var object in detected){
object = detected[object];
if(object != undefined){
stroke(object instanceof Player ? 'red' : 'lightgreen');
line(this.x, this.y, object.x, object.y);
}
}
var color = activationColor(this.brain.score, highestScore);
stroke(color);
ellipse(this.x, this.y, DETECTION_RADIUS*2);
noStroke();
},
/* Checks if object can be eaten */
eat: function(object){
var dist = distance(this.x, this.y, object.x, object.y);
var radius1 = Math.sqrt(this.area / PI);
var radius2 = Math.sqrt(object.area / PI);
if(dist < (radius1 + radius2) / 2 && this.area > object.area * RELATIVE_SIZE){
this.area += object.area;
object.restart();
return true;
}
return false;
},
/** Detect other genomes around */
detect: function(){
// Detect nearest objects
var nearestPlayers = [];
var playerDistances = Array.apply(null, Array(PLAYER_DETECTION)).map(Number.prototype.valueOf, Infinity);
for(var player in players){
player = players[player];
if(player == this || this.eat(player)) continue;
var dist = distance(this.x, this.y, player.x, player.y);
if(dist < DETECTION_RADIUS){
// Check if closer than any other object
var maxNearestDistance = Math.max.apply(null, playerDistances);
var index = playerDistances.indexOf(maxNearestDistance);
if(dist < maxNearestDistance){
playerDistances[index] = dist;
nearestPlayers[index] = player;
}
}
}
// Detect nearest foods
var nearestFoods = [];
var foodDistances = Array.apply(null, Array(FOOD_DETECTION)).map(Number.prototype.valueOf, Infinity);
for(var food in foods){
food = foods[food];
if(this.eat(food)) continue;
var dist = distance(this.x, this.y, food.x, food.y);
if(dist < DETECTION_RADIUS){
// Check if closer than any other object
var maxNearestDistance = Math.max.apply(null, foodDistances);
var index = foodDistances.indexOf(maxNearestDistance);
if(dist < maxNearestDistance){
foodDistances[index] = dist;
nearestFoods[index] = food;
}
}
}
// Create and normalize input
var output = [this.area / MAX_AREA];
for(var i = 0; i < PLAYER_DETECTION; i++){
var player = nearestPlayers[i];
var dist = playerDistances[i];
if(player == undefined){
output = output.concat([0, 0, 0]);
} else {
output.push(angleToPoint(this.x, this.y, player.x, player.y) / (2 * PI));
output.push(dist / DETECTION_RADIUS);
output.push(player.area / MAX_AREA);
}
}
for(var i = 0; i < FOOD_DETECTION; i++){
var food = nearestFoods[i];
var dist = foodDistances[i];
if(food == undefined){
output = output.concat([0, 0]);
} else {
output.push(angleToPoint(this.x, this.y, food.x, food.y) / (2 * PI));
output.push(dist / DETECTION_RADIUS);
}
}
if(distance(mouseX, mouseY, this.x, this.y) < Math.sqrt(this.visualarea / PI)){
var detected = nearestPlayers.concat(nearestFoods);
this.showDetection(detected);
}
return output;
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
#circle {
width: 20px;
height: 20px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
display: inline-block;
}

View File

@@ -0,0 +1,24 @@
$('.colors').change(function(){
var color = $(this).attr('value');
if($(this).is(":checked")) {
COLORS.push(color);
} else {
COLORS.splice(COLORS.indexOf(color), 1);
}
set = createSet();
visualiseSet();
});
$('.start').click(function(e){
e.preventDefault();
if(running == false){
running = true;
iteration = 0;
createNeat();
$(this).html('<span class="glyphicon glyphicon-pause"></span> Stop evolution');
setTimeout(loop, 1);
} else {
running = false;
$(this).html('<span class="glyphicon glyphicon-play"></span> Start evolution');
}
});

View File

@@ -0,0 +1,35 @@
var scripts = [
{ type: 'script', url: "https://wagenaartje.github.io/neataptic/cdn/1.2.22/neataptic.js" },
{ type: 'script', url: "../../js/articles/classifycolors/events.js" },
{ type: 'script', url: "../../js/articles/classifycolors/randomColor.js" },
{ type: 'script', url: "../../js/articles/classifycolors/neural.js" },
{ type: 'css', url: "../../js/articles/classifycolors/custom.css"}
];
/** https://stackoverflow.com/questions/33330636/load-javascript-dynamically-and-sequentially **/
function require(list) {
function loadScript(link) {
return new Promise(function(fulfill, reject) {
if(link.type == 'script'){
var script = document.createElement("script");
script.addEventListener("load", fulfill);
script.src = link.url;
document.head.appendChild(script);
} else if(link.type == 'css'){
var stylesheet = document.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.type = 'text/css';
stylesheet.href = link.url;
stylesheet.media = "screen,print";
document.head.appendChild(stylesheet);
}
});
}
loadScript(list.shift()).then(function() {
if (list.length > 0) {
require(list);
}
})
}
require(scripts);

View File

@@ -0,0 +1,106 @@
var Neat = neataptic.Neat;
var Methods = neataptic.Methods;
var Network = neataptic.Network;
var set;
var neat;
var running = false;
var iteration = 0;
var PER_COLOR = 50;
// Possible colors: red, orange, yellow, green, blue, purple, pink and monochrome
var COLORS = ['red', 'green', 'blue'];
var network;
$( document ).ready(function(){
set = createSet();
visualiseSet();
});
function createNeat(){
network = new Network(3, COLORS.length);
/*neat = new Neat(3, COLORS.length, fitness, {
mutation: [
Methods.Mutation.ADD_NODE,
Methods.Mutation.ADD_CONN,
Methods.Mutation.MOD_WEIGHT,
Methods.Mutation.MOD_BIAS,
Methods.Mutation.SUB_NODE,
Methods.Mutation.MOD_ACTIVATION
],
mutationRate: 0.6,
elitism: 5,
popsize: 100,
});*/
}
function visualiseSet(){
$('.set').empty();
for(color in COLORS){
$('.set').append('<div class="row ' + COLORS[color] + '"><h3>' + COLORS[color] + '</h3></div>');
}
for(var item in set){
item = set[item];
$('.'+ item.color).append('<div id="circle" style="background-color:' + item.rgb + '"></div>');
}
}
function visualiseGenomeSet(genome){
$('.fittestset').empty();
for(color in COLORS){
$('.fittestset').append('<div class="row fittest' + COLORS[color] + '"><h3>' + COLORS[color] + '</h3></div>');
}
for(var item in set){
item = set[item];
var output = genome.activate(item.input);
var max = Math.max.apply(null, output);
var color = COLORS[output.indexOf(max)];
$('.fittest'+ color).append('<div id="circle" style="background-color:' + item.rgb + '"></div>');
}
}
function loop(){
network.evolve(set, {
iterations: 1,
mutationRate: 0.6,
elisitm: 5,
popSize: 100,
mutation: Methods.Mutation.FFW,
cost: Methods.Cost.MSE
});
visualiseGenomeSet(network);
$('.iteration').text(iteration);
$('.bestfitness').text(network.test(set).error);
iteration++;
if(running) setTimeout(loop, 1);
}
// Thanks to https://github.com/davidmerfield/randomColor !!
function createSet(){
var set = [];
for(index in COLORS){
var color = COLORS[index];
var randomColors = randomColor({ hue : color, count: PER_COLOR, format: 'rgb'});
for(var random in randomColors){
var rgb = randomColors[random];
random = rgb.substring(4, rgb.length-1).replace(/ /g, '').split(',');
for(var y in random) random[y] = random[y]/255;
var output = Array.apply(null, Array(COLORS.length)).map(Number.prototype.valueOf, 0);
output[index] = 1;
set.push({ input: random, output: output, color: color, rgb: rgb});
}
}
return set;
}

View File

@@ -0,0 +1,431 @@
// randomColor by David Merfield under the CC0 license
// https://github.com/davidmerfield/randomColor/
;(function(root, factory) {
// Support AMD
if (typeof define === 'function' && define.amd) {
define([], factory);
// Support CommonJS
} else if (typeof exports === 'object') {
var randomColor = factory();
// Support NodeJS & Component, which allow module.exports to be a function
if (typeof module === 'object' && module && module.exports) {
exports = module.exports = randomColor;
}
// Support CommonJS 1.1.1 spec
exports.randomColor = randomColor;
// Support vanilla script loading
} else {
root.randomColor = factory();
}
}(this, function() {
// Seed to get repeatable colors
var seed = null;
// Shared color dictionary
var colorDictionary = {};
// Populate the color dictionary
loadColorBounds();
var randomColor = function (options) {
options = options || {};
// Check if there is a seed and ensure it's an
// integer. Otherwise, reset the seed value.
if (options.seed !== undefined && options.seed !== null && options.seed === parseInt(options.seed, 10)) {
seed = options.seed;
// A string was passed as a seed
} else if (typeof options.seed === 'string') {
seed = stringToInteger(options.seed);
// Something was passed as a seed but it wasn't an integer or string
} else if (options.seed !== undefined && options.seed !== null) {
throw new TypeError('The seed value must be an integer or string');
// No seed, reset the value outside.
} else {
seed = null;
}
var H,S,B;
// Check if we need to generate multiple colors
if (options.count !== null && options.count !== undefined) {
var totalColors = options.count,
colors = [];
options.count = null;
while (totalColors > colors.length) {
// Since we're generating multiple colors,
// incremement the seed. Otherwise we'd just
// generate the same color each time...
if (seed && options.seed) options.seed += 1;
colors.push(randomColor(options));
}
options.count = totalColors;
return colors;
}
// First we pick a hue (H)
H = pickHue(options);
// Then use H to determine saturation (S)
S = pickSaturation(H, options);
// Then use S and H to determine brightness (B).
B = pickBrightness(H, S, options);
// Then we return the HSB color in the desired format
return setFormat([H,S,B], options);
};
function pickHue (options) {
var hueRange = getHueRange(options.hue),
hue = randomWithin(hueRange);
// Instead of storing red as two seperate ranges,
// we group them, using negative numbers
if (hue < 0) {hue = 360 + hue;}
return hue;
}
function pickSaturation (hue, options) {
if (options.luminosity === 'random') {
return randomWithin([0,100]);
}
if (options.hue === 'monochrome') {
return 0;
}
var saturationRange = getSaturationRange(hue);
var sMin = saturationRange[0],
sMax = saturationRange[1];
switch (options.luminosity) {
case 'bright':
sMin = 55;
break;
case 'dark':
sMin = sMax - 10;
break;
case 'light':
sMax = 55;
break;
}
return randomWithin([sMin, sMax]);
}
function pickBrightness (H, S, options) {
var bMin = getMinimumBrightness(H, S),
bMax = 100;
switch (options.luminosity) {
case 'dark':
bMax = bMin + 20;
break;
case 'light':
bMin = (bMax + bMin)/2;
break;
case 'random':
bMin = 0;
bMax = 100;
break;
}
return randomWithin([bMin, bMax]);
}
function setFormat (hsv, options) {
switch (options.format) {
case 'hsvArray':
return hsv;
case 'hslArray':
return HSVtoHSL(hsv);
case 'hsl':
var hsl = HSVtoHSL(hsv);
return 'hsl('+hsl[0]+', '+hsl[1]+'%, '+hsl[2]+'%)';
case 'hsla':
var hslColor = HSVtoHSL(hsv);
var alpha = options.alpha || Math.random();
return 'hsla('+hslColor[0]+', '+hslColor[1]+'%, '+hslColor[2]+'%, ' + alpha + ')';
case 'rgbArray':
return HSVtoRGB(hsv);
case 'rgb':
var rgb = HSVtoRGB(hsv);
return 'rgb(' + rgb.join(', ') + ')';
case 'rgba':
var rgbColor = HSVtoRGB(hsv);
var alpha = options.alpha || Math.random();
return 'rgba(' + rgbColor.join(', ') + ', ' + alpha + ')';
default:
return HSVtoHex(hsv);
}
}
function getMinimumBrightness(H, S) {
var lowerBounds = getColorInfo(H).lowerBounds;
for (var i = 0; i < lowerBounds.length - 1; i++) {
var s1 = lowerBounds[i][0],
v1 = lowerBounds[i][1];
var s2 = lowerBounds[i+1][0],
v2 = lowerBounds[i+1][1];
if (S >= s1 && S <= s2) {
var m = (v2 - v1)/(s2 - s1),
b = v1 - m*s1;
return m*S + b;
}
}
return 0;
}
function getHueRange (colorInput) {
if (typeof parseInt(colorInput) === 'number') {
var number = parseInt(colorInput);
if (number < 360 && number > 0) {
return [number, number];
}
}
if (typeof colorInput === 'string') {
if (colorDictionary[colorInput]) {
var color = colorDictionary[colorInput];
if (color.hueRange) {return color.hueRange;}
}
}
return [0,360];
}
function getSaturationRange (hue) {
return getColorInfo(hue).saturationRange;
}
function getColorInfo (hue) {
// Maps red colors to make picking hue easier
if (hue >= 334 && hue <= 360) {
hue-= 360;
}
for (var colorName in colorDictionary) {
var color = colorDictionary[colorName];
if (color.hueRange &&
hue >= color.hueRange[0] &&
hue <= color.hueRange[1]) {
return colorDictionary[colorName];
}
} return 'Color not found';
}
function randomWithin (range) {
if (seed === null) {
return Math.floor(range[0] + Math.random()*(range[1] + 1 - range[0]));
} else {
//Seeded random algorithm from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
var max = range[1] || 1;
var min = range[0] || 0;
seed = (seed * 9301 + 49297) % 233280;
var rnd = seed / 233280.0;
return Math.floor(min + rnd * (max - min));
}
}
function HSVtoHex (hsv){
var rgb = HSVtoRGB(hsv);
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? '0' + hex : hex;
}
var hex = '#' + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
return hex;
}
function defineColor (name, hueRange, lowerBounds) {
var sMin = lowerBounds[0][0],
sMax = lowerBounds[lowerBounds.length - 1][0],
bMin = lowerBounds[lowerBounds.length - 1][1],
bMax = lowerBounds[0][1];
colorDictionary[name] = {
hueRange: hueRange,
lowerBounds: lowerBounds,
saturationRange: [sMin, sMax],
brightnessRange: [bMin, bMax]
};
}
function loadColorBounds () {
defineColor(
'monochrome',
null,
[[0,0],[100,0]]
);
defineColor(
'red',
[-26,18],
[[20,100],[30,92],[40,89],[50,85],[60,78],[70,70],[80,60],[90,55],[100,50]]
);
defineColor(
'orange',
[19,46],
[[20,100],[30,93],[40,88],[50,86],[60,85],[70,70],[100,70]]
);
defineColor(
'yellow',
[47,62],
[[25,100],[40,94],[50,89],[60,86],[70,84],[80,82],[90,80],[100,75]]
);
defineColor(
'green',
[63,178],
[[30,100],[40,90],[50,85],[60,81],[70,74],[80,64],[90,50],[100,40]]
);
defineColor(
'blue',
[179, 257],
[[20,100],[30,86],[40,80],[50,74],[60,60],[70,52],[80,44],[90,39],[100,35]]
);
defineColor(
'purple',
[258, 282],
[[20,100],[30,87],[40,79],[50,70],[60,65],[70,59],[80,52],[90,45],[100,42]]
);
defineColor(
'pink',
[283, 334],
[[20,100],[30,90],[40,86],[60,84],[80,80],[90,75],[100,73]]
);
}
function HSVtoRGB (hsv) {
// this doesn't work for the values of 0 and 360
// here's the hacky fix
var h = hsv[0];
if (h === 0) {h = 1;}
if (h === 360) {h = 359;}
// Rebase the h,s,v values
h = h/360;
var s = hsv[1]/100,
v = hsv[2]/100;
var h_i = Math.floor(h*6),
f = h * 6 - h_i,
p = v * (1 - s),
q = v * (1 - f*s),
t = v * (1 - (1 - f)*s),
r = 256,
g = 256,
b = 256;
switch(h_i) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
var result = [Math.floor(r*255), Math.floor(g*255), Math.floor(b*255)];
return result;
}
function HSVtoHSL (hsv) {
var h = hsv[0],
s = hsv[1]/100,
v = hsv[2]/100,
k = (2-s)*v;
return [
h,
Math.round(s*v / (k<1 ? k : 2-k) * 10000) / 100,
k/2 * 100
];
}
function stringToInteger (string) {
var total = 0
for (var i = 0; i !== string.length; i++) {
if (total >= Number.MAX_SAFE_INTEGER) break;
total += string.charCodeAt(i)
}
return total
}
return randomColor;
}));

View File

@@ -0,0 +1,20 @@
$(document).on('click', '.panel div.clickable', function (e) {
var $this = $(this); //Heading
var $panel = $this.parent('.panel');
var $panel_body = $panel.children('.panel-body');
var $display = $panel_body.css('display');
if ($display == 'block') {
$panel_body.slideUp();
} else if($display == 'none') {
$panel_body.slideDown();
}
});
$(document).ready(function(e){
var $classy = '.panel.autocollapse';
var $found = $($classy);
$found.find('.panel-body').hide();
$found.removeClass($classy);
});

View File

@@ -0,0 +1,89 @@
.node {
cursor: move;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #BDBDBD;
stroke-width: 1.5px;
opacity: 0.4;
marker-end: url(#end-arrow);
}
.label {
fill: #CCCCCC;
font-size: 9px;
text-anchor: middle;
cursor: move;
font-family: Arial;
}
#end-arrow{
opacity: 0.4;
}
.INPUT{
fill: #ff6666;
stroke: #ff4d4d;
}
.OUTPUT{
fill : #ff8c66;
stroke: #ff794d;
}
.LOGISTIC{
fill: #ffb366;
stroke: #ffa64d;
}
.TANH{
fill: #ffd966;
stroke: #ffd24d;
}
.IDENTITY{
fill: #ffff66;
stroke: #ffff4d;
}
.STEP{
fill: #d9ff66;
stroke: #d2ff4d;
}
.RELU{
fill: #b3ff66;
stroke: #a6ff4d;
}
.SOFTSIGN{
fill: #8cff66;
stroke: #79ff4d;
}
.SINUSOID{
fill: #66ff66;
stroke: #4dff4d;
}
.GAUSSIAN{
fill: #66ff8c;
stroke: #4dff79;
}
.BENT_IDENTITY{
fill: #66ffd9;
stroke: #4dffd2;
}
.BIPOLAR{
fill: #66d9ff;
stroke: #4dd2ff;
}
.BIPOLAR_SIGMOID{
fill: #66b3ff;
stroke: #4da6ff;
}
.HARD_TANH{
fill: #668cff;
stroke: #4d79ff;
}
.ABSOLUTE{
fill: #6666ff;
stroke: #4d4dff;
}
.GATE{
fill: #003300;
stroke: #001a00;
}
.CONSTANT{
fill: #ff00ff;
stroke: #e600e6;
}

View File

@@ -0,0 +1,122 @@
var NODE_RADIUS = 7;
var REPEL_FORCE = 0;
var LINK_DISTANCE = 100;
var drawGraph = function(graph, panel, width, height) {
var d3cola = cola.d3adaptor()
.avoidOverlaps(true)
.size([width, height]);
var svg = d3.select(panel);
d3.selectAll(panel + "> *").remove();
// define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 6)
.attr('markerWidth', 4)
.attr('markerHeight', 4)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
graph.nodes.forEach(function (v) { v.height = v.width = 2 * NODE_RADIUS; });
d3cola
.nodes(graph.nodes)
.links(graph.links)
//.constraints(graph.constraints)
.symmetricDiffLinkLengths(REPEL_FORCE)
.linkDistance(LINK_DISTANCE)
.start(10, 15, 20);
var path = svg.selectAll(".link")
.data(graph.links)
.enter().append('svg:path')
.attr('class', 'link')
path.append("title")
.text(function (d) {
var text = "";
text += "Weight: " + Math.round(d.weight*1000)/1000 + "\n";;
text += "Source: " + d.source.id + "\n";;
text += "Target: " + d.target.id;
return text;
});
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", function(d){
return "node " + d.name;
})
.attr("r", function(d) { return NODE_RADIUS; })
.call(d3cola.drag);
node.append("title")
.text(function (d){
var text = "";
text += "Activation: " + Math.round(d.activation*1000)/1000 + "\n";
text += "Bias: " + Math.round(d.bias*1000)/1000 + "\n";
text += "Position: " + d.id;
return text;
});
var label = svg.selectAll(".label")
.data(graph.nodes)
.enter().append("text")
.attr("class", "label")
.text(function (d){return '(' + d.index + ') ' + d.name; })
.call(d3cola.drag)
d3cola.on("tick", function () {
// draw directed edges with proper padding from node centers
path.attr('d', function (d) {
var deltaX = d.target.x - d.source.x,
deltaY = d.target.y - d.source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist;
if(isNaN(normX)) normX = 0;
if(isNaN(normY)) normY = 0;
sourcePadding = NODE_RADIUS,
targetPadding = NODE_RADIUS + 2,
sourceX = d.source.x + (sourcePadding * normX),
sourceY = d.source.y + (sourcePadding * normY),
targetX = d.target.x - (targetPadding * normX),
targetY = d.target.y - (targetPadding * normY);
// Defaults for normal edge.
drx = 0,
dry = 0,
xRotation = 0, // degrees
largeArc = 0, // 1 or 0
sweep = 1; // 1 or 0
// Self edge.
if (d.source.x === d.target.x && d.source.y === d.target.y) {
drx = dist;
dry = dist;
xRotation = -45;
largeArc = 1;
drx = 20;
dry = 20;
targetX = targetX + 1;
targetY = targetY + 1;
}
return 'M' + sourceX + ',' + sourceY + "A" + drx + "," + dry + " " + xRotation + "," + largeArc + "," + sweep + " " + targetX + ',' + targetY;
});
node.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
label
.attr("x", function (d) { return d.x + 10; })
.attr("y", function (d) { return d.y - 10; });
});
};

View File

@@ -0,0 +1,38 @@
var scripts = [
{ type: 'script', url: "https://wagenaartje.github.io/neataptic/cdn/1.2.22/neataptic.js"},
{ type: 'script', url: "https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.js"},
{ type: 'script', url: "../../js/articles/neuroevolution/webcola.js"},
{ type: 'script', url: "../../js/articles/neuroevolution/events.js"},
{ type: 'script', url: "../../js/articles/neuroevolution/graph.js"},
{ type: 'script', url: "../../js/articles/neuroevolution/neural.js"},
{ type: 'css', url: "../../js/articles/neuroevolution/graph.css"}
];
/** https://stackoverflow.com/questions/33330636/load-javascript-dynamically-and-sequentially **/
function require(list) {
function loadScript(link) {
return new Promise(function(fulfill, reject) {
if(link.type == 'script'){
var script = document.createElement("script");
script.addEventListener("load", fulfill);
script.src = link.url;
document.head.appendChild(script);
} else if(link.type == 'css'){
var stylesheet = document.createElement('link');
stylesheet.addEventListener("load", fulfill);
stylesheet.rel = 'stylesheet';
stylesheet.type = 'text/css';
stylesheet.href = link.url;
stylesheet.media = "screen,print";
document.head.appendChild(stylesheet);
}
});
}
loadScript(list.shift()).then(function() {
if (list.length > 0) {
require(list);
}
})
}
require(scripts);

View File

@@ -0,0 +1,165 @@
var Network = neataptic.Network;
var Methods = neataptic.Methods;
var Neat = neataptic.Neat;
var Config = neataptic.Config;
Config.warnings = false;
var examples = [
{
set: [
{ input: [0.0], output: [0.2] },
{ input: [0.2], output: [0.4] },
{ input: [0.4], output: [0.6] },
{ input: [0.6], output: [0.8] },
{ input: [0.8], output: [1.0] },
{ input: [1.0], output: [0.8] },
{ input: [0.8], output: [0.6] },
{ input: [0.6], output: [0.4] },
{ input: [0.4], output: [0.2] },
{ input: [0.2], output: [0.0] }
],
options: {
mutation: Methods.Mutation.ALL,
equal: true,
elitism: 5,
iterations: 1500,
clear: true,
error: 0.003
}
},
{
set: [
{ input: [0], output: [0] },
{ input: [0], output: [0] },
{ input: [0], output: [0] },
{ input: [0], output: [0] },
{ input: [0], output: [0] },
{ input: [0], output: [0] },
{ input: [0], output: [0] },
{ input: [0], output: [0] },
{ input: [0], output: [0] },
{ input: [0], output: [1] }
],
options: {
mutation: Methods.Mutation.ALL,
equal: true,
elitism: 5,
iterations: 1500,
clear: true,
error: 0.003
}
},
{
set: [
{ input: [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [1] }, // A
{ input: [0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // B
{ input: [0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // C
{ input: [0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // D
{ input: [0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [1] }, // E
{ input: [0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // F
{ input: [0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // G
{ input: [0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // H
{ input: [0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [1] }, // I
{ input: [0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // J
{ input: [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // K
{ input: [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // L
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // M
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0], output: [0] }, // N
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0], output: [1] }, // O
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0], output: [0] }, // P
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0], output: [0] }, // Q
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0], output: [0] }, // R
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0], output: [0] }, // S
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0], output: [0] }, // T
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0], output: [1] }, // U
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0], output: [0] }, // V
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0], output: [0] }, // W
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0], output: [0] }, // X
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0], output: [0] }, // Y
{ input: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], output: [0] }, // Z
],
options: {
mutation: Methods.Mutation.FFW,
equal: true,
elitism: 5,
iterations: 1500,
clear: true,
error: 0.001
}
},
]
function showModal(id, type){
if(type == 0){
var set = examples[id-1].set;
var s = '';
for(var i = 0; i < set.length; i++){
var input = JSON.stringify(set[i].input);
var output = JSON.stringify(set[i].output);
s += (`Input: ${input}, output: ${output}\n`);
}
$('.modalcontent').html(s);
$('.modal-title').text('Training set');
} else if(type == 1){
var options = examples[id-1].options;
var keys = Object.keys(options);
var s = '';
for(var i = 0; i < keys.length; i++){
if(keys[i] == 'mutation'){
var value = '';
for(var j = 0; j < options[keys[i]].length; j++){
value += options[keys[i]][j].name + ', ';
}
} else {
var value = options[keys[i]];
}
s += (`${keys[i]}: ${value}\n`);
}
$('.modalcontent').html(s);
$('.modal-title').text('Evolve settings');
} else if(type == 2){
$('.modalcontent').html(examples[id-1].output);
$('.modal-title').text('Output');
}
$('#modal').modal();
}
function run(id){
var set = examples[id-1].set;
var options = examples[id-1].options;
$('.status' + id).show();
$('.status' + id).text('Running...');
setTimeout(freeze, 10, id, set, options)
}
function freeze(id, set, options){
var network = new Network(set[0].input.length, set[0].output.length);
var results = network.evolve(set, options);
$('.example' + id).width('100%');
$('.example' + id).height(400);
$('.example' + id).show();
var width = $('.example' + id).width();
drawGraph(network.graph(width, 400), '.example' + id, width, 400);
var s = '';
for(var i = 0; i < set.length; i++){
var input = JSON.stringify(set[i].input);
var targetoutput = JSON.stringify(set[i].output);
var output = network.activate(set[i].input);
for(var j = 0; j < output.length; j++){
output[j] = Math.round(output[j] * 1000) / 1000;
}
output = JSON.stringify(output);
s += (`Input: ${input}, wanted output: ${targetoutput}, actual: ${output}\n`);
}
examples[id-1].output = s;
$('.status' + id).text('Show outputs');
$('.error' + id).text('Error ' + Math.round(-results.error * 1000) / 1000);
$('.error' + id).show();
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
$( document ).ready(function() {
$( ".start" ).click(function() {
$( ".start" ).html('<span class="glyphicon glyphicon-dashboard" aria-hidden="true"></span> Running...');
newNeat();
loop();
});
});

View File

@@ -0,0 +1,92 @@
.btn-group, .input-group, pre{
margin-top: 10px;
}
.node {
cursor: move;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #BDBDBD;
stroke-width: 1.5px;
opacity: 0.4;
marker-end: url(#end-arrow);
}
.label {
fill: #CCCCCC;
font-size: 9px;
text-anchor: middle;
cursor: move;
font-family: Arial;
}
#end-arrow{
opacity: 0.4;
}
.INPUT{
fill: #ff6666;
stroke: #ff4d4d;
}
.OUTPUT{
fill : #ff8c66;
stroke: #ff794d;
}
.LOGISTIC{
fill: #ffb366;
stroke: #ffa64d;
}
.TANH{
fill: #ffd966;
stroke: #ffd24d;
}
.IDENTITY{
fill: #ffff66;
stroke: #ffff4d;
}
.STEP{
fill: #d9ff66;
stroke: #d2ff4d;
}
.RELU{
fill: #b3ff66;
stroke: #a6ff4d;
}
.SOFTSIGN{
fill: #8cff66;
stroke: #79ff4d;
}
.SINUSOID{
fill: #66ff66;
stroke: #4dff4d;
}
.GAUSSIAN{
fill: #66ff8c;
stroke: #4dff79;
}
.BENT_IDENTITY{
fill: #66ffd9;
stroke: #4dffd2;
}
.BIPOLAR{
fill: #66d9ff;
stroke: #4dd2ff;
}
.BIPOLAR_SIGMOID{
fill: #66b3ff;
stroke: #4da6ff;
}
.HARD_TANH{
fill: #668cff;
stroke: #4d79ff;
}
.ABSOLUTE{
fill: #6666ff;
stroke: #4d4dff;
}
.GATE{
fill: #003300;
stroke: #001a00;
}
.CONSTANT{
fill: #ff00ff;
stroke: #e600e6;
}

View File

@@ -0,0 +1,128 @@
var NODE_RADIUS = 7;
var GATE_RADIUS = 2;
var REPEL_FORCE = 0;
var LINK_DISTANCE = 100;
var WIDTH = 500;
var HEIGHT = 600;
var d3cola = cola.d3adaptor()
.avoidOverlaps(true)
.size([WIDTH, HEIGHT]);
var drawGraph = function(graph, panel) {
var svg = d3.select(panel);
d3.selectAll(panel + "> *").remove();
// define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 6)
.attr('markerWidth', 4)
.attr('markerHeight', 4)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
graph.nodes.forEach(function(v){
v.height = v.width = 2 * (v.name == 'GATE' ? GATE_RADIUS : NODE_RADIUS); }
);
d3cola
.nodes(graph.nodes)
.links(graph.links)
.constraints(graph.constraints)
.symmetricDiffLinkLengths(REPEL_FORCE)
.linkDistance(LINK_DISTANCE)
.start(10, 15, 20);
var path = svg.selectAll(".link")
.data(graph.links)
.enter().append('svg:path')
.attr('class', 'link')
path.append("title")
.text(function (d) {
var text = "";
text += "Weight: " + Math.round(d.weight*1000)/1000 + "\n";;
text += "Source: " + d.source.id + "\n";;
text += "Target: " + d.target.id;
return text;
});
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", function(d){
return "node " + d.name;
})
.attr("r", function(d) { return d.name == 'GATE' ? GATE_RADIUS : NODE_RADIUS; })
.call(d3cola.drag);
node.append("title")
.text(function (d){
var text = "";
text += "Activation: " + Math.round(d.activation*1000)/1000 + "\n";
text += "Bias: " + Math.round(d.bias*1000)/1000 + "\n";
text += "Position: " + d.id;
return text;
});
var label = svg.selectAll(".label")
.data(graph.nodes)
.enter().append("text")
.attr("class", "label")
.text(function (d){return '(' + d.index + ') ' + d.name; })
.call(d3cola.drag)
d3cola.on("tick", function () {
// draw directed edges with proper padding from node centers
path.attr('d', function (d) {
var deltaX = d.target.x - d.source.x,
deltaY = d.target.y - d.source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist;
if(isNaN(normX)) normX = 0;
if(isNaN(normY)) normY = 0;
sourcePadding = d.source.width / 2,
targetPadding = d.target.width / 2 + 2,
sourceX = d.source.x + (sourcePadding * normX),
sourceY = d.source.y + (sourcePadding * normY),
targetX = d.target.x - (targetPadding * normX),
targetY = d.target.y - (targetPadding * normY);
// Defaults for normal edge.
drx = 0,
dry = 0,
xRotation = 0, // degrees
largeArc = 0, // 1 or 0
sweep = 1; // 1 or 0
// Self edge.
if (d.source.x === d.target.x && d.source.y === d.target.y) {
drx = dist;
dry = dist;
xRotation = -45;
largeArc = 1;
drx = 20;
dry = 20;
targetX = targetX + 1;
targetY = targetY + 1;
}
return 'M' + sourceX + ',' + sourceY + "A" + drx + "," + dry + " " + xRotation + "," + largeArc + "," + sweep + " " + targetX + ',' + targetY;
});
node.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
label
.attr("x", function (d) { return d.x + 10; })
.attr("y", function (d) { return d.y - 10; });
});
};

View File

@@ -0,0 +1,38 @@
var scripts = [
{ type: 'script', url: "https://wagenaartje.github.io/neataptic/cdn/1.3.4/neataptic.js"},
{ type: 'script', url: "https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.js"},
{ type: 'script', url: "../../js/articles/neuroevolution/webcola.js"},
{ type: 'script', url: "../../js/articles/playground/events.js"},
{ type: 'script', url: "../../js/articles/playground/graph.js"},
{ type: 'script', url: "../../js/articles/playground/neural.js"},
{ type: 'css', url: "../../js/articles/playground/extra.css"}
];
/** https://stackoverflow.com/questions/33330636/load-javascript-dynamically-and-sequentially **/
function require(list) {
function loadScript(link) {
return new Promise(function(fulfill, reject) {
if(link.type == 'script'){
var script = document.createElement("script");
script.addEventListener("load", fulfill);
script.src = link.url;
document.head.appendChild(script);
} else if(link.type == 'css'){
var stylesheet = document.createElement('link');
stylesheet.addEventListener("load", fulfill);
stylesheet.rel = 'stylesheet';
stylesheet.type = 'text/css';
stylesheet.href = link.url;
stylesheet.media = "screen,print";
document.head.appendChild(stylesheet);
}
});
}
loadScript(list.shift()).then(function() {
if (list.length > 0) {
require(list);
}
})
}
require(scripts);

View File

@@ -0,0 +1,28 @@
var { Network, methods, architect } = neataptic;
var network = new Network(2, 1);
$(document).ready(function () {
refresh();
activate();
});
function mutate (method) {
network.mutate(method);
refresh();
activate();
}
function activate () {
var in1 = $('.input1').val();
var in2 = $('.input2').val();
if(in1 > 1 || in1 < 0 || in2 > 1 || in2 < 0){
alert('Inputs must be between 0 and 1!');
}
var output = network.activate([in1, in2]);
$('.output').text(output);
}
function refresh(){
drawGraph(network.graph($('.draw').width() / 1.4, $('.draw').height() / 1.4), '.draw');
}

View File

@@ -0,0 +1,99 @@
/* Global vars */
var players = [];
var walker = new Walker();
var iteration = 0;
var highestScore = 0;
/** Setup the canvas */
function setup(){
var canvas = createCanvas(WIDTH, HEIGHT);
canvas.parent('field');
initNeat();
// Do some initial mutation
if(!USE_TRAINED_POP){
for(var i = 0; i < 1; i++) neat.mutate();
}
startEvaluation();
}
function draw(){
clear();
squareGrid();
// Check if evaluation is done
if(iteration == ITERATIONS){
endEvaluation();
iteration = 0;
}
// Update and visualise players
for(var i = players.length - 1; i >= 0; i--){
var player = players[i];
// Some players are eaten during the iteration
player.update();
player.show();
}
walker.update();
walker.show();
iteration++;
}
/** Draw a square grid with grey lines */
function squareGrid(){
stroke(204, 204, 204, 160);
strokeWeight(1);
fill(255);
for(var x = 0; x < WIDTH/40; x++){
line(x * 40, 0, x * 40, HEIGHT);
}
for(var y = 0; y < HEIGHT/40; y++){
line(0, y * 40, WIDTH, y * 40);
}
noStroke();
}
/** Calculate distance between two points */
function distance(x1, y1, x2, y2){
var dx = x1 - x2;
var dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
}
/** Get a relative color between red and green */
var activationColor = function(value, max){
var power = 1 - Math.min(value/max, 1);
var color = [255, 255, 0]
if(power < 0.5){
color[0] = 2 * power * 255;
} else {
color[1] = (1.0 - 2 * (power - 0.5)) * 255;
}
return color;
}
/** Get the angle from one point to another */
function angleToPoint(x1, y1, x2, y2){
d = distance(x1, y1, x2, y2);
dx = (x2-x1) / d;
dy = (y2-y1) / d;
a = Math.acos(dx);
a = dy < 0 ? 2 * Math.PI - a : a;
return a;
}
/** Set the walker to a new location */
function mouseClicked(){
if(mouseX >= 0 && mouseX <= WIDTH && mouseY >= 0 && mouseY <= HEIGHT){
walker.x = mouseX;
walker.y = mouseY;
}
}

View File

@@ -0,0 +1,158 @@
var colorTable = [
'#2124FF', // input
'#FF2718', // output
'#1F22C1', // logistic sigmoid
'#EE8A2A', // tanh
'#B17516', // identity
'#B1B0AA', // hlim
'#2CB11F', // relu
'#C5B12C', // softsign
'#E685E7', // sinusoid
'#257580', // gaussian
'#B0484B', // softplus
'#4CB148', // bent_identity
'#000000' // GATE
];
var NODE_RADIUS = 7;
var REPEL_FORCE = 10;
var LINK_DISTANCE = 100;
var drawGraph = function(graph, panel, activation) {
var d3cola = cola.d3adaptor()
.avoidOverlaps(true)
.size([$('.best').width(), $('.best').height()]);
var svg = d3.select(panel);
d3.selectAll(panel + "> *").remove();
// define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 6)
.attr('markerWidth', 5)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#000');
graph.nodes.forEach(function (v) { v.height = v.width = 2 * NODE_RADIUS; });
d3cola
.nodes(graph.nodes)
.links(graph.links)
.symmetricDiffLinkLengths(REPEL_FORCE)
.linkDistance(LINK_DISTANCE)
.start(10, 15, 20);
var path = svg.selectAll(".link")
.data(graph.links)
.enter().append('svg:path')
.attr('class', 'link')
.style("stroke-width", function (d) {
if(activation){
return 1.5;
} else {
return 1.5 + Math.sqrt(d.weight * 5);
}
})
.style("stroke", function (d) {
if(activation){
return activationColor(d.source.activation * d.weight, graph.main.maxActivation * graph.main.maxWeight);
} else if(d.gate){
if(d.source.activation){
return activationColor(d.source.activation, graph.main.maxActivation);
} else{
return 'rgb(255,0,0)';
}
}
});
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) { return NODE_RADIUS; })
.style("fill", function (d) {
if(activation){
return activationColor(d.activation, graph.main.maxActivation);
} else {
return colorTable[d.type];
}
})
.call(d3cola.drag);
node.append("title")
.text(function (d) { return d.id; });
var label = svg.selectAll(".label")
.data(graph.nodes)
.enter().append("text")
.attr("class", "label")
.text(function (d) { return '(' + d.index + ') ' + d.name; })
.call(d3cola.drag)
d3cola.on("tick", function () {
// draw directed edges with proper padding from node centers
path.attr('d', function (d) {
var deltaX = d.target.x - d.source.x,
deltaY = d.target.y - d.source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist;
if(isNaN(normX)) normX = 0;
if(isNaN(normY)) normY = 0;
sourcePadding = NODE_RADIUS,
targetPadding = NODE_RADIUS + 2,
sourceX = d.source.x + (sourcePadding * normX),
sourceY = d.source.y + (sourcePadding * normY),
targetX = d.target.x - (targetPadding * normX),
targetY = d.target.y - (targetPadding * normY);
// Defaults for normal edge.
drx = 0,
dry = 0,
xRotation = 0, // degrees
largeArc = 0, // 1 or 0
sweep = 1; // 1 or 0
// Self edge.
if (d.source.x === d.target.x && d.source.y === d.target.y) {
drx = dist;
dry = dist;
// Fiddle with this angle to get loop oriented.
xRotation = -45;
// Needs to be 1.
largeArc = 1;
// Change sweep to change orientation of loop.
//sweep = 0;
// Make drx and dry different to get an ellipse
// instead of a circle.
drx = 20;
dry = 20;
// For whatever reason the arc collapses to a point if the beginning
// and ending points of the arc are the same, so kludge it.
targetX = targetX + 1;
targetY = targetY + 1;
}
return 'M' + sourceX + ',' + sourceY + "A" + drx + "," + dry + " " + xRotation + "," + largeArc + "," + sweep + " " + targetX + ',' + targetY;
});
node.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
label
.attr("x", function (d) { return d.x + 10; })
.attr("y", function (d) { return d.y - 10; });
});
};

View File

@@ -0,0 +1,37 @@
var scripts = [
{ type: 'script', url: "https://cdn.rawgit.com/wagenaartje/neataptic/a7610e38/dist/neataptic.js"},
{ type: 'script', url: "https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.10/p5.js"},
{ type: 'script', url: "../../js/articles/target-seekingai/main.js"},
{ type: 'script', url: "../../js/articles/target-seekingai/population.js"},
{ type: 'script', url: "../../js/articles/target-seekingai/player.js"},
{ type: 'script', url: "../../js/articles/target-seekingai/walker.js"},
{ type: 'script', url: "../../js/articles/target-seekingai/field.js"}
];
/** https://stackoverflow.com/questions/33330636/load-javascript-dynamically-and-sequentially **/
function require(list) {
function loadScript(link) {
return new Promise(function(fulfill, reject) {
if(link.type == 'script'){
var script = document.createElement("script");
script.addEventListener("load", fulfill);
script.src = link.url;
document.head.appendChild(script);
} else if(link.type == 'css'){
var stylesheet = document.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.type = 'text/css';
stylesheet.href = link.url;
stylesheet.media = "screen,print";
document.head.appendChild(stylesheet);
}
});
}
loadScript(list.shift()).then(function() {
if (list.length > 0) {
require(list);
}
})
}
require(scripts);

View File

@@ -0,0 +1,108 @@
/** Rename vars */
var Neat = neataptic.Neat;
var Methods = neataptic.Methods;
var Config = neataptic.Config;
var Architect = neataptic.Architect;
/** Turn off warnings */
Config.warnings = false;
/** Settings */
var WIDTH = $('#field').width();
var HEIGHT = 500;
var MAX_SPEED = 5;
var START_X = WIDTH/2;
var START_Y = HEIGHT/2;
var SCORE_RADIUS = 100;
// GA settings
var PLAYER_AMOUNT = Math.round(2.3e-4 * WIDTH * HEIGHT);
var ITERATIONS = 10e6; // should be ~250 for real use
var MUTATION_RATE = 0.3;
var ELITISM = Math.round(0.1 * PLAYER_AMOUNT);
// Trained population
var USE_TRAINED_POP = true;
/** Global vars */
var neat;
/** Construct the genetic algorithm */
function initNeat(){
neat = new Neat(
6, 1,
null,
{
mutation: [
Methods.Mutation.ADD_NODE,
Methods.Mutation.SUB_NODE,
Methods.Mutation.ADD_CONN,
Methods.Mutation.SUB_CONN,
Methods.Mutation.MOD_WEIGHT,
Methods.Mutation.MOD_BIAS,
Methods.Mutation.MOD_ACTIVATION,
Methods.Mutation.ADD_GATE,
Methods.Mutation.SUB_GATE,
Methods.Mutation.ADD_SELF_CONN,
Methods.Mutation.SUB_SELF_CONN,
Methods.Mutation.ADD_BACK_CONN,
Methods.Mutation.SUB_BACK_CONN
],
popsize: PLAYER_AMOUNT,
mutationRate: MUTATION_RATE,
elitism: ELITISM
}
);
if(USE_TRAINED_POP){
neat.population = population;
}
}
/** Start the evaluation of the current generation */
function startEvaluation(){
players = [];
highestScore = 0;
for(var genome in neat.population){
genome = neat.population[genome];
new Player(genome);
}
walker.reset();
}
/** End the evaluation of the current generation */
function endEvaluation(){
console.log('Generation:', neat.generation, '- average score:', Math.round(neat.getAverage()));
console.log('Fittest score:', Math.round(neat.getFittest().score));
// Networks shouldn't get too big
for(var genome in neat.population){
genome = neat.population[genome];
genome.score -= genome.nodes.length * SCORE_RADIUS / 10;
}
// Sort the population by score
neat.sort();
// Init new pop
var newPopulation = [];
// Elitism
for(var i = 0; i < neat.elitism; i++){
newPopulation.push(neat.population[i]);
}
// Breed the next individuals
for(var i = 0; i < neat.popsize - neat.elitism; i++){
newPopulation.push(neat.getOffspring());
}
// Replace the old population with the new population
neat.population = newPopulation;
neat.mutate();
neat.generation++;
startEvaluation();
}

View File

@@ -0,0 +1,91 @@
function Player(genome){
this.x = START_X;
this.y = START_Y;
this.vx = 0;
this.vy = 0;
this.r = 6;
this.brain = genome;
this.brain.score = 0;
players.push(this);
}
Player.prototype = {
/** Update the stats */
update: function(){
var input = this.detect();
var output = this.brain.activate(input);
var moveangle = output[0] * 2 * PI;
// Calculate next position
this.ax = Math.cos(moveangle);
this.ay = Math.sin(moveangle);
this.vx += this.ax;
this.vy += this.ay;
// Limit speeds to maximum speed
this.vx = this.vx > MAX_SPEED ? MAX_SPEED : this.vx < -MAX_SPEED ? -MAX_SPEED : this.vx;
this.vy = this.vy > MAX_SPEED ? MAX_SPEED : this.vy < -MAX_SPEED ? -MAX_SPEED : this.vy;
this.x += this.vx;
this.y += this.vy;
// Limit position to width and height
this.x = this.x >= WIDTH ? WIDTH : this.x <= 0 ? 0 : this.x;
this.y = this.y >= HEIGHT ? HEIGHT : this.y <= 0 ? 0 : this.y;
if(this.x == 0 || this.x == WIDTH) this.vx = -this.vx;
if(this.y == 0 || this.y == HEIGHT) this.vy = -this.vy;
this.score();
},
/** Calculate fitness of this players genome **/
score: function(){
var dist = distance(this.x, this.y, walker.x, walker.y);
if(!isNaN(dist) && dist < SCORE_RADIUS){
this.brain.score += SCORE_RADIUS - dist;
}
// Replace highest score to visualise
highestScore = this.brain.score > highestScore ? this.brain.score : highestScore;
},
/** Display the player on the field, parts borrowed from the CodingTrain */
show: function(){
// Draw a triangle rotated in the direction of velocity
var angle = angleToPoint(this.x, this.y, this.x + this.vx, this.y + this.vy) + HALF_PI;
var color = activationColor(this.brain.score, highestScore);
push();
translate(this.x, this.y);
rotate(angle);
fill(color);
beginShape();
vertex(0, -this.r * 2);
vertex(-this.r, this.r * 2);
vertex(this.r, this.r * 2);
endShape(CLOSE);
pop();
},
/** Detect and normalize inputs */
detect: function(){
var dist = Math.sqrt(this.x, this.y, walker.x, walker.y) / Math.sqrt(WIDTH**2 + HEIGHT**2);
var targetAngle = angleToPoint(this.x, this.y, walker.x, walker.y) / TWO_PI;
var vx = (this.vx + MAX_SPEED) / MAX_SPEED;
var vy = (this.vy + MAX_SPEED) / MAX_SPEED;
var tvx = (walker.vx + MAX_SPEED) / MAX_SPEED;
var tvy = (walker.vy + MAX_SPEED) / MAX_SPEED;
// NaN checking
targetAngle = isNaN(targetAngle) ? 0 : targetAngle;
dist = isNaN(dist) ? 0 : dist;
return [vx, vy, tvx, tvy, targetAngle, dist];
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
function Walker(){
this.x = START_X;
this.y = START_Y;
this.vx = 0;
this.vy = 0;
this.r = 10;
this.angle = Math.random() * Math.PI * 2;
}
Walker.prototype = {
/** Update the stats */
update: function(){
if(Math.random() > 0.5){
this.angle += Math.random()* 2 -1;
}
// Calculate next position
this.ax = Math.cos(this.angle);
this.ay = Math.sin(this.angle);
this.vx += this.ax;
this.vy += this.ay;
// Limit speeds to maximum speed
this.vx = this.vx > MAX_SPEED/2 ? MAX_SPEED/2 : this.vx < -MAX_SPEED/2 ? -MAX_SPEED/2 : this.vx;
this.vy = this.vy > MAX_SPEED/2 ? MAX_SPEED/2 : this.vy < -MAX_SPEED/2 ? -MAX_SPEED/2 : this.vy;
this.x += this.vx;
this.y += this.vy;
// Limit position to width and height
this.x = this.x >= WIDTH ? WIDTH : this.x <= 0 ? 0 : this.x;
this.y = this.y >= HEIGHT ? HEIGHT : this.y <= 0 ? 0 : this.y;
if(this.x == 0 || this.x == WIDTH){
this.vx = -this.vx;
this.angle += PI;
}
if(this.y == 0 || this.y == HEIGHT){
this.vy = -this.vy;
this.angle += PI;
}
},
reset: function(){
this.x = START_X;
this.y = START_Y;
this.vx = 0;
this.vy = 0;
this.angle = Math.random() * Math.PI * 2;
},
/** Display the walker on the field */
show: function(){
fill(0);
ellipse(this.x, this.y, this.r*2);
// Score radius
noFill();
stroke('lightgreen');
strokeWeight(2);
ellipse(this.x, this.y, SCORE_RADIUS*2);
noStroke();
},
};