|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<style> |
|
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<script> |
|
const WIDTH = 960 |
|
const HEIGHT = 500 |
|
|
|
const PRIMES =[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101, 103, 107,109,113,127,131,137,139,149,151,157] |
|
const OPERATORS = [ |
|
function(a, b) { return a + b }, |
|
function(a, b) { return a - b }, |
|
function(a, b) { return a * b }, |
|
function(a, b) { return a / b }, |
|
function(a, b) { return a % b } |
|
] |
|
|
|
const CRITERION = [ |
|
function(n) { return true }, |
|
function(n) { return n > 110 }, |
|
function(n) { return n % 5 === 0 }, |
|
function(n) { return n % 15 === 0 }, |
|
function(n) { return n % 210 === 0 }, |
|
function(n) { return n % 14 === 0 }, |
|
function(n) { return n % 7 === 0 }, |
|
function(n) { return n > 150 }, |
|
function(n) { return true } |
|
] |
|
|
|
const MUTATION_RATE = 0.0008 |
|
|
|
const clampColor = function(input) { |
|
return Math.round(Math.max(0, Math.min(255, input))) |
|
} |
|
|
|
const wrap = function(n) { |
|
if (n < 0) { return false } // return n + WIDTH * HEIGHT } |
|
if (n > WIDTH * HEIGHT) { return false } // return n - WIDTH * HEIGHT } |
|
return n |
|
} |
|
|
|
const adjacencyTransform = [ |
|
function(n) { return (n % WIDTH !== 0) ? wrap(n - WIDTH - 1) : false }, |
|
function(n) { return wrap(n - WIDTH) }, |
|
function(n) { return ((n + 1) % WIDTH !== 0) ? wrap(n - WIDTH + 1) : false }, |
|
function(n) { return (n % WIDTH !== 0) ? wrap(n - 1) : false }, |
|
function(n) { return ((n + 1) % WIDTH !== 0) ? wrap(n + 1) : false }, |
|
function(n) { return (n % WIDTH !== 0) ? wrap(n + WIDTH - 1) : false }, |
|
function(n) { return wrap(n + WIDTH) }, |
|
function(n) { return ((n + 1) % WIDTH !== 0) ? wrap(n + WIDTH + 1) : false }, |
|
] |
|
|
|
// Set up |
|
var slots, cells, liveCells, pxToPaint, tribes, lineage |
|
|
|
var candidate = function(parent) { |
|
var genes = { |
|
numbers: parent.genes.numbers.map(function(n) { return n }), |
|
operators: parent.genes.operators.map(function(n) { return n }) |
|
} |
|
var colorCache = Object.assign({}, parent.color) |
|
var fitness = parent.fitness |
|
var generation = parent.generation + 1 |
|
var hue = parent.hue |
|
var sat = parent.sat |
|
|
|
// Mutate |
|
if (Math.random() < MUTATION_RATE) { |
|
var numberSlot = Math.floor(Math.random() * genes.numbers.length) |
|
var primeToPick = Math.floor(Math.random() * PRIMES.length) |
|
genes.numbers[numberSlot] = PRIMES[primeToPick] |
|
|
|
fitness = genes.operators.reduce(function(n, currentOp, i) { |
|
return OPERATORS[currentOp](n, genes.numbers[i + 1]) |
|
}, genes.numbers[0]) |
|
|
|
//hue = Math.round(Math.random() * 60 - 30 + 120 + parent.hue) |
|
//hue = (tribes.length % 8) * 45 |
|
hue = parent.hue + 20 + Math.random()*20 |
|
sat = Math.random() * 0.4 + 0.3 |
|
|
|
generation = 0 |
|
} |
|
|
|
var hsl = d3.hsl( |
|
hue, |
|
sat, |
|
0.48 - Math.sin(generation / 15) * 0.03 |
|
) |
|
|
|
var rgb = d3.rgb(hsl) |
|
|
|
color = { |
|
r: clampColor(rgb.r), |
|
g: clampColor(rgb.g), |
|
b: clampColor(rgb.b) |
|
} |
|
|
|
var child = { |
|
generation: generation, |
|
genes: genes, |
|
cooldown: genes.numbers.length + genes.operators.length, |
|
hue: hue, |
|
sat: sat, |
|
color: color, |
|
fitness: fitness, |
|
tribeId: parent.tribeId |
|
} |
|
|
|
return child |
|
} |
|
|
|
var reproduce = function(child, coordinate) { |
|
var adjacentAvalible = adjacencyTransform.map(function(fn) { |
|
return fn(coordinate) |
|
}).filter(function(co) { |
|
return co && slots[co] === null |
|
}) |
|
|
|
if (child.generation === 0) { |
|
if (typeof child.tribeId === 'number') { |
|
// if it isn't one of the originals |
|
var parentTribeId = child.tribeId |
|
|
|
lineage.push({ |
|
parent: parentTribeId, |
|
child: tribes.length |
|
}) |
|
} |
|
|
|
child.tribeId = tribes.length |
|
tribes.push([coordinate]) |
|
} else { |
|
tribes[child.tribeId].push(coordinate) |
|
} |
|
|
|
child.coordinate = coordinate |
|
child.adjacentAvalible = adjacentAvalible |
|
|
|
cells.push(child) |
|
liveCells.push(child) |
|
slots[coordinate] = cells.length - 1 |
|
pxToPaint.push(coordinate) |
|
} |
|
|
|
var reproduceThrow = function(parent, coordinate) { |
|
|
|
if (slots[coordinate] !== null) { |
|
var index = parent.adjacentAvalible.indexOf(coordinate) |
|
parent.adjacentAvalible.splice(index, 1) |
|
|
|
return |
|
} |
|
|
|
var column = Math.floor((coordinate % WIDTH) / WIDTH * 9) |
|
var criteria = CRITERION[column] |
|
|
|
var newChild = candidate(parent) |
|
|
|
if (criteria(newChild.fitness)) { |
|
reproduce(newChild, coordinate) |
|
} |
|
} |
|
|
|
// Feel free to change or delete any of the code you see in this editor! |
|
var canvas = d3.select("body").append("canvas") |
|
.attr("width", WIDTH) |
|
.attr("height", HEIGHT); |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", WIDTH) |
|
.attr("height", HEIGHT) |
|
.style("position", "absolute") |
|
.style("top", 0) |
|
.style("left", 0) |
|
|
|
var ctx = canvas.node().getContext("2d"); |
|
|
|
var imgData = ctx.getImageData(0, 0, WIDTH, HEIGHT) |
|
var data = imgData.data |
|
|
|
var resetSimulation = function() { |
|
slots = d3.range(960 * 500).map(function() { return null }) |
|
cells = [] |
|
liveCells = [] |
|
tribes = [] |
|
lineage = [] |
|
pxToPaint = [] |
|
|
|
overlay() |
|
|
|
var c1 = WIDTH * HEIGHT / 2 + 20 |
|
var c2 = WIDTH * HEIGHT / 2 - 20 |
|
|
|
var firstCell = { |
|
generation: 0, |
|
genes: { |
|
numbers: [13, 0], |
|
operators: [0] |
|
}, |
|
cooldown: 0, |
|
hue: 160, |
|
sat: 0.4, |
|
color: { |
|
r: 155, |
|
g: 155, |
|
b: 155 |
|
}, |
|
fitness: 13 |
|
} |
|
|
|
var secondCell = { |
|
generation: 0, |
|
genes: { |
|
numbers: [13, 0], |
|
operators: [0] |
|
}, |
|
cooldown: 0, |
|
hue: 270, |
|
sat: 0.4, |
|
color: { |
|
r: 155, |
|
g: 155, |
|
b: 155 |
|
}, |
|
fitness: 13 |
|
} |
|
|
|
reproduce(firstCell, c1) |
|
reproduce(secondCell, c2) |
|
|
|
slots.forEach(function(slot, i) { |
|
var b = i * 4 |
|
data[b] = 255 |
|
data[b + 1] = 255 |
|
data[b + 2] = 255 |
|
}) |
|
|
|
ctx.putImageData(imgData, 0, 0) |
|
|
|
window.requestAnimationFrame(tick) |
|
} |
|
|
|
var overlay = function() { |
|
var keys = d3.range(tribes.length) |
|
|
|
var significantTribes = keys.filter(function(key) { |
|
return tribes[key].length > 20 |
|
}) |
|
|
|
var coordinates = significantTribes.map(function(key) { |
|
var tribe = tribes[key] |
|
var originalCoordinate = tribe[0] |
|
|
|
var x = originalCoordinate % WIDTH |
|
var y = Math.floor(originalCoordinate / WIDTH) |
|
|
|
return { x: x, y: y } |
|
}) |
|
|
|
var linkedCoordinates = lineage.filter(function(line) { |
|
return significantTribes.indexOf(line.child) >= 0 |
|
}) |
|
.map(function(pair) { |
|
var parent = tribes[pair.parent][0] |
|
var child = tribes[pair.child][0] |
|
|
|
return { |
|
x1: parent % WIDTH, |
|
y1: Math.floor(parent / WIDTH), |
|
x2: child % WIDTH, |
|
y2: Math.floor(child / WIDTH) |
|
} |
|
}) |
|
|
|
var circles = svg.selectAll('circle') |
|
.data(coordinates) |
|
|
|
circles.enter().append("circle") |
|
.attr('r', 5) |
|
.attr('fill', 'none') |
|
.attr('stroke-width', 2.5) |
|
.attr('stroke', '#ffffff') |
|
.merge(circles) |
|
.attr('cx', function(d) { return d.x }) |
|
.attr('cy', function(d) { return d.y }) |
|
|
|
circles.exit().remove() |
|
|
|
var lines = svg.selectAll('line') |
|
.data(linkedCoordinates) |
|
|
|
lines.enter().append('line') |
|
.attr('stroke', '#ffffff') |
|
.attr('stroke-width', 1.5) |
|
.merge(lines) |
|
.attr('stroke-dasharray', function(d) { |
|
var dx = d.x1 - d.x2 |
|
var dy = d.y1 - d.y2 |
|
var length = Math.sqrt(dx * dx + dy * dy) |
|
|
|
return '0, 4, ' + (length - 12) |
|
}) |
|
.attr('x1', function(d) { return d.x1 }) |
|
.attr('y1', function(d) { return d.y1 }) |
|
.attr('x2', function(d) { return d.x2 }) |
|
.attr('y2', function(d) { return d.y2 }) |
|
|
|
lines.exit().remove() |
|
} |
|
|
|
var tick = function() { |
|
// Should Continue |
|
pxToPaint = [] |
|
|
|
// Simulate |
|
var cullCells = [] |
|
liveCells.forEach(function(cell, i) { |
|
if (cell.adjacentAvalible.length <= 0) { |
|
cullCells.push(i) |
|
return |
|
} |
|
|
|
var target = cell.adjacentAvalible[Math.floor(cell.adjacentAvalible.length * Math.random())] |
|
|
|
reproduceThrow(cell, target) |
|
}) |
|
|
|
cullCells.reverse() |
|
cullCells.forEach(function(id) { |
|
liveCells.splice(id, 1) |
|
}) |
|
|
|
// Paint |
|
pxToPaint.forEach(function(coordinate) { |
|
var r = coordinate * 4 |
|
var g = r + 1 |
|
var b = g + 1 |
|
var a = b + 1 |
|
|
|
var slot = slots[coordinate] |
|
|
|
// if slot has a cell |
|
var cell = cells[slot] |
|
|
|
data[r] = cell.color.r |
|
data[g] = cell.color.g |
|
data[b] = cell.color.b |
|
data[a] = 255 |
|
}) |
|
|
|
ctx.putImageData(imgData, 0, 0) |
|
|
|
if (Math.random() > 0.9) { |
|
overlay() |
|
} |
|
|
|
if (liveCells.length > 0) { |
|
window.requestAnimationFrame(tick) |
|
} else { |
|
console.log('done') |
|
setTimeout(resetSimulation, 5000) |
|
} |
|
|
|
} |
|
|
|
resetSimulation() |
|
|
|
|
|
</script> |
|
</body> |