整理桌面

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,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();
},
};