Canvas.setpenopacity(1); const extent = 110; const size = 2; class Cell { constructor(x, y, sideLength) { this.x = x; this.y = y; this.sideLength = sideLength; this.arity = 6; this.nextIndex = Math.floor(Math.random() * this.arity); this.angle = 360/this.arity; this.neighbors = [...Array(this.arity)].map(() => null); this.cellDistance = (sideLength-0.175) * Math.cos(this.angle); } setNeighbor(index, cell) { this.neighbors[(index)%this.arity] = cell; } findNeighborCoords(index) { const turtle = new Turtle(); turtle.penup(); turtle.goto(this.x, this.y); turtle.seth((180 + (this.angle * index + this.angle/2))%360); turtle.forward(2 * this.cellDistance); const tx = turtle.x(); const ty = turtle.y(); return [tx, ty]; } attachNeighbors(cells) { const missing = this.neighbors.map((c, i) => ({ i, c })).filter(({c}) => c===null).map(({i}) => i); missing.forEach(i => { const [tx, ty] = this.findNeighborCoords(i); const newNeighbor = cells.find(c => Math.round(c.x) == Math.round(tx) && Math.round(c.y) == Math.round(ty) ); if (newNeighbor) { if (Math.random() > 0.01) { this.neighbors[i] = true; } else { this.neighbors[i] = newNeighbor; } } }); } findNext(cells) { let ret = false; for (let i = this.nextIndex; i < this.nextIndex+this.arity; i++) { const [tx, ty] = this.findNeighborCoords(i%this.arity); this.nextIndex = i%this.arity; if (!cells.find(c => Math.round(c.x) == Math.round(tx) && Math.round(c.y) == Math.round(ty))) { if (tx >= -extent && tx <= extent && ty >= -extent && ty <= extent) { const newCell = new Cell(tx, ty, this.sideLength); this.setNeighbor(i%this.arity, newCell); newCell.setNeighbor((i%this.arity+(this.arity/2))%this.arity, this); ret = newCell; } else { this.ready = true; break; } } if (this.neighbors.filter(c => !c).length === 0) { this.ready = true; } if (ret) break; } return ret; } render() { const turtle = new Turtle(); turtle.penup(); turtle.goto(this.x - this.sideLength/2, this.y + this.cellDistance); for (let cur, i = 0; i < this.arity; i++, cur = this.neighbors[(i+4)%this.arity]) { if (cur === true) { turtle.pendown(); } turtle.forward(this.sideLength); turtle.right(this.angle); turtle.penup(); } this.rendered = true; } } class Organism { constructor() { this.cells = [new Cell(0, 0, size)]; this.nextRender = []; } grow() { this.cells.filter(c => !c.ready).forEach((c, i) => { const newCell = c.findNext(this.cells); if (newCell) { newCell.attachNeighbors(this.cells); this.cells.splice(0, 0, newCell); } c.attachNeighbors(this.cells); this.nextRender = this.cells.filter(c => c.ready && !c.rendered); }); } render() { this.nextRender.forEach(c => c.render()); return this.nextRender.length; } } const o = new Organism(); let lastProgress = 0; let stalled = 0; function walk(i) { o.grow(); const progress = o.render(); if (progress !== lastProgress) { lastProgress = progress; stalled = 0; } else { stalled++; } return stalled < 10; }