Created
December 7, 2016 21:04
-
-
Save anonymous/f51587ffc33cb49edd4737324bf2516d to your computer and use it in GitHub Desktop.
JS Bin // source http://jsbin.com/hipadem
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width"> | |
<title>JS Bin</title> | |
<style id="jsbin-css"> | |
body { | |
margin: 50px 25px 0 25px; | |
background-color: #ccc; | |
} | |
canvas { | |
float: left; | |
} | |
#stats { | |
float: left; | |
} | |
#tools { | |
clear: left; | |
padding: 0.1rem; | |
} | |
#tools a { | |
margin: 0 0.3rem 0 0; | |
font-size: 0.9rem; | |
font-family: sans-serif; | |
} | |
#tools a#selected { | |
background-color: red; | |
color: white; | |
} | |
#log { | |
background-color: white; | |
margin-top: 0.5rem; | |
} | |
#log span { | |
display: block; | |
font-family: monospace; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id="c" width="256" height="256"></canvas> | |
<div id="stats"></div> | |
<div id="tools"></div> | |
<div id="log"></div> | |
<script id="jsbin-javascript"> | |
var cfg = { | |
cellSize: 9, | |
tileColors: [ | |
function(v) { return 'rgb(255,255,255)'; }, | |
function(v) { | |
var fv = Math.floor(map(v, 0, 1, 0, 225)); | |
return 'rgb('+fv+','+fv+','+fv+')'; }, | |
function(v) { return 'rgb(0,255,0)'; }, | |
function(v) { return 'rgb(0,0,0)'; }, | |
function(v) { return 'rgb(127,0,127)'; }, | |
], | |
jobSpeed: 250, | |
gridWidth: 32, | |
gridHeight: 32, | |
gridAreal: 32 * 32 | |
}; | |
var V_TYPE = { | |
VACUUM: 0, | |
ROOM: 1, | |
PRODUCER: 2, | |
WALL: 3, | |
REPAIR: 4, | |
}; | |
var logs = []; | |
var logContainer; | |
var statsContainer; | |
var jobsGrow = []; | |
var jobsShrink = []; | |
var jobsRepair = []; | |
var tiles = []; | |
var curTool; | |
var canvas = document.getElementById('c'); | |
var ctx = canvas.getContext('2d'); | |
var offsetsDiag = [ | |
{x:-1, y:1}, {x:-1, y:-1}, {x:1, y:-1}, {x:1, y:1} | |
]; | |
var offsetsVonN = [ | |
{x:0, y:1}, {x:-1, y:0}, {x:0, y:-1}, {x:1, y:0} | |
]; | |
var offsetsMoore = [ | |
{x:0, y:1}, {x:-1, y:1}, {x:-1, y:0}, | |
{x:-1, y:-1}, {x:0, y:-1}, {x:1, y:-1}, | |
{x:1, y:0}, {x:1, y:1} | |
]; | |
var tools = { | |
vacuum: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER || | |
tile.t === V_TYPE.ROOM || | |
tile.t === V_TYPE.VACUUM) { | |
return; | |
} | |
addVacuum(tile); | |
} | |
}, | |
room: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER) { | |
return; | |
} | |
var neighbours = getOrderedNeighboursTo(tile.x, tile.y); | |
for (var j=0; j<neighbours.length; j++) { | |
var ni = neighbours[j]; | |
if (ni === -1) { | |
continue; | |
} | |
if (tiles[ni].t !== V_TYPE.VACUUM) { | |
continue; | |
} | |
tiles[ni].t = V_TYPE.WALL; | |
} | |
// If any of the neighbours are vacuum, consume | |
// the tile by vacuum. | |
/* | |
var vacuum = false; | |
eachNeighbour(tile, function(ntile, n, i) { | |
if (ntile.t === V_TYPE.VACUUM) { | |
vacuum = true; | |
} | |
}); | |
if (vacuum) { | |
tile.t = V_TYPE.ROOM; | |
fillVacuum(tile); | |
return; | |
} | |
*/ | |
addRoom(tile); | |
// Otherwise, add room and continue the | |
// grow algorithm if any | |
var ns = getNeighboursTo(tile.x, tile.y); | |
var max = 0; | |
var m = -1; | |
for (var i=0; i<ns.length; i++) { | |
var n = ns[i]; | |
if (tiles[n].v > max) { | |
max = tiles[n].v; | |
m = i; | |
} | |
} | |
if (max > 0) { | |
growFrom(tiles[ns[m]]); | |
} | |
} | |
}, | |
producer: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER) { | |
tile.t = V_TYPE.ROOM; | |
tile.v = 7; | |
shrinkFrom(tile); | |
return | |
} | |
if (tile.t === V_TYPE.ROOM || | |
tile.t === V_TYPE.VACUUM) { | |
tile.t = V_TYPE.PRODUCER; | |
tile.v = 8; | |
growFrom(tile); | |
return; | |
} | |
} | |
}, | |
wall: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER) { | |
return; | |
} | |
// When repairing a wall | |
if (tile.t === V_TYPE.VACUUM) { | |
// If tile has two adjacent neighbours, either NS or EW, | |
// consider it a repair | |
var sig = getSignature(tile); | |
if (sig === 5 || sig === 10) { | |
console.log('repair'); | |
var neighs = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighs.length; i++) { | |
var n = neighs[i]; | |
var nt = tiles[n]; | |
var v = net.findVertex(nt.x, nt.y); | |
console.log(nt.x, nt.y, 'has vertex', v); | |
if (v < 0) { | |
continue; | |
} | |
console.log('fill from', nt.x, nt.y, '(',tile.x, tile.y,')'); | |
floodFill(nt.x, nt.y, V_TYPE.ROOM); | |
net.addEdgesToTile(nt); | |
net.addFacesToTile(nt); | |
updateStats(); | |
// look for neighbours that are of type ROOM | |
// if (tiles[n].t !== V_TYPE.ROOM) { | |
// continue; | |
// } | |
// var ok = repairFill(nt.x, nt.y); | |
// log("ok "+ok); | |
// if (ok) { | |
tile.t = V_TYPE.WALL; | |
tile.v = 0; | |
// } | |
} | |
return; | |
} | |
} | |
tile.t = V_TYPE.WALL; | |
tile.v = 0; | |
var min = 8; | |
var ni = -1; | |
eachNeighbour(tile, function(n, i) { | |
if (n.t !== V_TYPE.ROOM && | |
n.t !== V_TYPE.PRODUCER) { | |
return; | |
} | |
if (n.t === V_TYPE.ROOM) { | |
net.addEdgesToTile(n); | |
net.addFacesToTile(n); | |
updateStats(); | |
} | |
if (n.v < min) { | |
min = n.v; | |
ni = flattenIndex(n.x, n.y); | |
} | |
}); | |
if (min === 0 || min === 8) { | |
return; | |
} | |
var top = crawlUp(tiles[ni]); | |
if (top.v < 8) { | |
shrinkFrom(top); | |
} | |
} | |
}, | |
}; | |
var net = { | |
verts: [], | |
edges: [], | |
faces: [], | |
// bitmasks for illegal combos. There are only three (or two | |
// in this case): | |
// - when there are exactly three neighbours, and they are all | |
// on line, either along the x or y axis. So something like: | |
// 1010 and 0101 | |
illegals: [], | |
getNeighbours: function(x, y) { | |
var neighbours = []; | |
neighbours.push(this.findVertex(x, y+1)); // S | |
neighbours.push(this.findVertex(x-1, y )); // W | |
neighbours.push(this.findVertex(x , y-1)); // N | |
neighbours.push(this.findVertex(x+1, y )); // E | |
return neighbours; | |
}, | |
addMooreNeighbours: function(list, tile, offsets) { | |
for (var n=0, o=1; n<offsets.length; n++, o+=2) { | |
var v = this.findVertex(tile.x + offsets[n].x, | |
tile.y + offsets[n].y); | |
list.splice(o, 0, v); | |
} | |
}, | |
addVertex: function(tile) { | |
this.verts.push({x: tile.x, y: tile.y}); | |
var v = this.verts.length - 1; | |
var neighbours = this.getNeighbours(tile.x, tile.y); | |
this.addEdges(v, neighbours); | |
this.addMooreNeighbours(neighbours, tile, offsetsDiag); | |
this.addFaces(v, neighbours); | |
}, | |
findVertex: function(x, y) { | |
for (var i=0; i<this.verts.length; i++) { | |
if (this.verts[i].x === x && | |
this.verts[i].y === y) { | |
return i; | |
} | |
} | |
return -1; | |
}, | |
addEdgesToTile: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var neighbours = this.getNeighbours(tile.x, tile.y); | |
this.addEdges(v, neighbours); | |
}, | |
addEdges: function(fromVert, neighbours) { | |
function valid(n) { | |
return n > -1; | |
} | |
var nlist = neighbours.filter(valid); | |
for (var i=0; i<nlist.length; i++) { | |
this.edges.push(fromVert); | |
this.edges.push(nlist[i]); | |
} | |
}, | |
findEdges: function(v) { | |
var edges = []; | |
for (var i=0; i<this.edges.length; i+=2) { | |
if (this.edges[i] === v || | |
this.edges[i+1] === v) { | |
edges.push(i); | |
} | |
} | |
return edges; | |
}, | |
removeEdges: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var edges = this.findEdges(v); | |
for (var i=edges.length-1; i>=0; i--) { | |
this.edges.splice(edges[i], 2); | |
} | |
}, | |
addFacesToTile: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var neighbours = this.getNeighbours(tile.x, tile.y); | |
this.addMooreNeighbours(neighbours, tile, offsetsDiag); | |
this.addFaces(v, neighbours); | |
}, | |
addFaces: function(fromVert, neighbours) { | |
var exists = function(a, b, c) { | |
return this[a] > -1 && | |
this[b] > -1 && | |
this[c] > -1; | |
}.bind(neighbours); | |
var create = function(a, b, c) { | |
if (exists(a, b, c)) { | |
this.faces.push(fromVert); | |
this.faces.push(neighbours[a]); | |
this.faces.push(neighbours[b]); | |
this.faces.push(neighbours[c]); | |
} | |
}.bind(this); | |
create(0,1,2); | |
create(2,3,4); | |
create(4,5,6); | |
create(6,7,0); | |
}, | |
findFaces: function(v) { | |
var faces = []; | |
for (var i=0; i<this.faces.length; i+=4) { | |
if (this.faces[i] === v || | |
this.faces[i+1] === v || | |
this.faces[i+2] === v || | |
this.faces[i+3] === v) { | |
faces.push(i); | |
} | |
} | |
return faces; | |
}, | |
removeFaces: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var faces = this.findFaces(v); | |
for (var i=faces.length-1; i>=0; i--) { | |
this.faces.splice(faces[i], 4); | |
} | |
}, | |
}; | |
function addVacuum(tile) { | |
if (tile.t === V_TYPE.WALL) { | |
eachNeighbour(tile, function(n, i) { | |
if (n.t === V_TYPE.ROOM) { | |
net.removeEdges(n); | |
net.removeFaces(n); | |
updateStats(); | |
} | |
}); | |
} | |
eachNeighbour(tile, function(n, i) { | |
if (n.t === V_TYPE.ROOM) { | |
fillVacuum(n); | |
} | |
}); | |
tile.t = V_TYPE.VACUUM; | |
} | |
function addRoom(tile) { | |
tile.t = V_TYPE.ROOM; | |
net.addVertex(tile); | |
updateStats(); | |
// network.addVertex(tile.x, tile.y) | |
// - check neighbours (look in vertices list) | |
// - add edges and faces | |
// num neighbours | |
// 0: add its own vertex to network mesh | |
// 1: add vertex, 1 edge | |
// 2: add vertex, 2 edges, 1 face | |
// 3: add vertex, 3 edges, 2 faces | |
// 4: add vertex, 4 edges, 3 faces | |
// edges = neighbours | |
// faces = edges - 1 | |
} | |
function updateStats() { | |
var v = net.verts.length; | |
var e = net.edges.length / 2; | |
var f = net.faces.length / 4; | |
var state = v - e + f; | |
stat((state === 1 ? 'closed' : 'open') + ' ('+v+', '+e+', '+f+') '+state); | |
} | |
function drawRect(x, y, w, h, t) { | |
drawLine([x, y], w, 0, t); | |
drawLine([x, y+1], h-2, 1, t); | |
drawLine([x+w-1, y+1], h-2, 1, t); | |
drawLine([x, y+h-1], w, 0, t); | |
} | |
// dir is either 0 or 1, depending on the direction | |
// you want to draw. 0 = x and 1 = y | |
function drawLine(coord, len, dir, t) { | |
for (var i=0; i<len; i++) { | |
var n = flattenIndex(coord[0], coord[1]); | |
tiles[n].t = t; | |
coord[dir]++; | |
} | |
} | |
function drawDot(x, y, t) { | |
var n = flattenIndex(x, y); | |
tiles[n].t = t; | |
} | |
function floodFill(x, y, t, cb) { | |
var fi = flattenIndex(x, y); | |
var fill_type = tiles[fi].t; | |
var fill = []; | |
fill.push(tiles[fi]); | |
while (fill.length > 0) { | |
var tile = fill.shift(); | |
if (tile.t === t) { | |
continue; | |
} | |
tile.t = t; | |
if (typeof cb !== 'undefined') { | |
cb(tile); | |
} | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (tiles[n].t === fill_type) { | |
fill.push(tiles[n]); | |
} | |
} | |
} | |
} | |
function repairFill(x, y) { | |
var fi = flattenIndex(x, y); | |
var fill = []; | |
fill.push(tiles[fi]); | |
var success = true; | |
while (fill.length > 0) { | |
var tile = fill.shift(); | |
if (tile.t === V_TYPE.VACUUM) { | |
success = false; | |
break; | |
} | |
tile.t = V_TYPE.REPAIR; | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (tiles[n].t === V_TYPE.ROOM) { | |
fill.push(tiles[n]); | |
} | |
} | |
} | |
return success; | |
} | |
function crawlUp(tile) { | |
var ti = flattenIndex(tile.x, tile.y); | |
var list = []; | |
list.push(tiles[ti]); | |
var top = 0; | |
while (list.length > 0) { | |
var t = list.shift(); | |
if (t.v === 0 || t.v === 8) { | |
break; | |
} | |
var max = 0; | |
var ni = -1; | |
var neighbours = getNeighboursTo(t.x, t.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (tiles[n].v > max) { | |
max = tiles[n].v; | |
ni = n; | |
} | |
} | |
if (max > 0) { | |
top = max; | |
ti = ni; | |
list.push(tiles[ni]); | |
} | |
} | |
return tiles[ti]; | |
} | |
function fillVacuum(tile) { | |
floodFill(tile.x, | |
tile.y, | |
V_TYPE.VACUUM, | |
function(t) { t.a = -1; }); | |
} | |
function flattenIndex(x, y) { | |
return x + cfg.gridWidth * y; | |
} | |
function setTiles(callback) { | |
var x=0; | |
var y=0; | |
for (var i=0; i<cfg.gridAreal; i++) { | |
x = (x + 1) % cfg.gridWidth; | |
y = Math.floor(i / cfg.gridHeight); | |
var n = flattenIndex(x,y); | |
tiles[n] = callback(tiles[n], x, y, i); | |
} | |
} | |
function fillTiles() { | |
setTiles(function(tile, x, y, i) { | |
return {x:x, y:y, t:V_TYPE.VACUUM, v:0, a:0}; | |
}); | |
} | |
function clear() { | |
ctx.fillStyle = 'rgb(255,255,255)'; | |
ctx.fillRect(0, 0, | |
cfg.cellSize * cfg.gridWidth, | |
cfg.cellSize * cfg.gridHeight); | |
} | |
function getTile(event, cellSize) { | |
return { | |
x: Math.floor(event.offsetX / cellSize), | |
y: Math.floor(event.offsetY / cellSize) | |
}; | |
} | |
function render() { | |
clear(); | |
var scale = function(val) { | |
return { | |
x: val.x * cfg.cellSize + cfg.cellSize * 0.5, | |
y: val.y * cfg.cellSize + cfg.cellSize * 0.5, | |
} | |
} | |
var i; | |
for (i=0; i<cfg.gridAreal; i++) { | |
var tile = tiles[i]; | |
var color = cfg.tileColors[tile.t]; | |
ctx.fillStyle = color(1 - (tile.v / 8)); | |
ctx.fillRect(tile.x * cfg.cellSize, | |
tile.y * cfg.cellSize, | |
cfg.cellSize, | |
cfg.cellSize); | |
} | |
for (i=0; i<net.verts.length; i++) { | |
var vert = net.verts[i]; | |
ctx.fillStyle = 'rgb(0, 0, 0)'; | |
ctx.fillRect(scale(vert.x), scale(vert.y), 1, 1); | |
} | |
ctx.beginPath(); | |
for (i=0; i<net.edges.length; i+=2) { | |
var fv = net.verts[net.edges[i]]; | |
var tv = net.verts[net.edges[i+1]]; | |
fv = scale(fv); | |
tv = scale(tv); | |
ctx.moveTo(fv.x, fv.y); | |
ctx.lineTo(tv.x, tv.y); | |
} | |
ctx.stroke(); | |
} | |
// A tile's signature is a bitmask telling | |
// which sides of the tile are walls | |
function getSignature(tile) { | |
var neighbours = getOrderedNeighboursTo(tile.x, tile.y); | |
var sig = 0; | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (typeof n === 'undefined') { | |
continue; | |
} | |
if (tiles[n].t === V_TYPE.WALL) { | |
sig += 1 << i; | |
} | |
} | |
return sig; | |
} | |
function countNeighbours(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
var count = 0; | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
count += clamp01(tiles[n].t); | |
} | |
return count; | |
} | |
function getNeighboursBySig(tile, sig) { | |
var neighbours = []; | |
var dirs = [{x:0,y:1},{x:-1,y:0},{x:0,y:-1},{x:1,y:0}]; | |
for (var i=1, n=0; i<16; i*=2, n++) { | |
if (sig & i === 0) { | |
continue; | |
} | |
var x = tile.x + dirs[n].x; | |
var y = tile.y + dirs[n].y; | |
var j = flattenIndex(x, y); | |
if (j < 0 || j >= tiles.length) { | |
continue; | |
} | |
neighbours[n] = tiles[j]; | |
} | |
return neighbours; | |
} | |
// Returns a list of neighbours within the | |
// Von Neumann neighbourhood. The list is | |
// not ordered on neighbourhood | |
function getNeighboursTo(x, y) { | |
var addNeighbour = function(x, y, list) { | |
var i = flattenIndex(x, y); | |
if (i >= 0 && i < tiles.length) { | |
list.push(i); | |
} | |
} | |
var neighbours = []; | |
addNeighbour(x, y+1, neighbours); // S | |
addNeighbour(x-1, y , neighbours); // W | |
addNeighbour(x, y-1, neighbours); // N | |
addNeighbour(x+1, y , neighbours); // E | |
return neighbours; | |
} | |
function getOrderedNeighboursTo(x, y) { | |
var getNeighbour = function(x, y) { | |
var i = flattenIndex(x, y); | |
if (i >= 0 && i < tiles.length) { | |
return i; | |
} | |
return -1; | |
} | |
var neighbours = []; | |
neighbours.push(getNeighbour(x, y+1)); // S | |
neighbours.push(getNeighbour(x-1, y )); // W | |
neighbours.push(getNeighbour(x, y-1)); // N | |
neighbours.push(getNeighbour(x+1, y )); // E | |
return neighbours; | |
} | |
function getOrderedMooreNeighboursTo(x, y) { | |
var getNeighbour = function(x, y) { | |
var i = flattenIndex(x, y); | |
if (i >= 0 && i < tiles.length) { | |
return i; | |
} | |
return -1; | |
} | |
var neighbours = []; | |
neighbours.push(getNeighbour(x, y+1)); // S | |
neighbours.push(getNeighbour(x-1, y+1)); // SW | |
neighbours.push(getNeighbour(x-1, y )); // W | |
neighbours.push(getNeighbour(x-1, y-1)); // NW | |
neighbours.push(getNeighbour(x, y-1)); // N | |
neighbours.push(getNeighbour(x+1, y-1)); // NE | |
neighbours.push(getNeighbour(x+1, y )); // E | |
neighbours.push(getNeighbour(x+1, y+1)); // SE | |
return neighbours; | |
} | |
function eachNeighbour(tile, cb) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
cb(tiles[n], i, n); | |
} | |
} | |
function processJobs(jobs, cb) { | |
var numJobs = jobs.length; | |
var counter = 0; | |
while (counter < numJobs) { | |
cb(jobs.shift()); | |
counter++; | |
} | |
} | |
function growFrom(tile) { | |
jobsGrow.push(tile); | |
window.setTimeout(grow, cfg.jobSpeed); | |
} | |
function grow() { | |
processJobs(jobsGrow, function(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
// We don't care about neighbours that are greater | |
// or equal, or exactly one value lower than the | |
// current tile. | |
var diff = tiles[n].v - tile.v; | |
if (diff >= 0 || diff === -1) { | |
continue; | |
} | |
// Ignore walls | |
if (tiles[n].t === V_TYPE.WALL) { | |
continue; | |
} | |
tiles[n].v = tile.v - 1; | |
if (tiles[n].v > 1) { | |
jobsGrow.push(tiles[n]); | |
} | |
} | |
}); | |
render(); | |
if (jobsGrow.length > 0) { | |
window.setTimeout(grow, cfg.jobSpeed); | |
} | |
} | |
function shrinkFrom(tile) { | |
var addJob = function(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
jobsShrink.push(tiles[neighbours[i]]); | |
} | |
jobsShrink.push(tile); | |
} | |
if (tile instanceof Array) { | |
for (var t=0; t<tile.length; t++) { | |
addJob(tile[t]); | |
} | |
} else { | |
addJob(tile); | |
} | |
window.setTimeout(shrink, cfg.jobSpeed); | |
} | |
function shrink() { | |
var moreJobs = []; | |
processJobs(jobsShrink, function(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
var v = {v:0}; | |
for (var i=0; i<neighbours.length; i++) { | |
var ni = neighbours[i]; | |
if (tiles[ni].v > v.v) { | |
v = tiles[ni]; | |
} | |
} | |
if (v.v <= tile.v) { | |
var newVal = v.v - 1; | |
if (newVal >= 0) { | |
tile.v = newVal; | |
moreJobs.push(tile); | |
} else { | |
tile.v = 0; | |
} | |
} | |
}); | |
render(); | |
shrinkFrom(moreJobs); | |
} | |
canvas.addEventListener('click', function(event) { | |
var click = getTile(event, cfg.cellSize); | |
var i = flattenIndex(click.x, click.y); | |
var tile = tiles[i]; | |
tools[curTool].callback(tile); | |
render(); | |
}); | |
function registerTools(tools, defaultTool) { | |
var container = document.getElementById('tools'); | |
Object.keys(tools).forEach(function(tool) { | |
var a = document.createElement('a'); | |
a.innerHTML= tool; | |
a.dataset.tool = tool; | |
container.appendChild(a); | |
tools[tool].element = a; | |
}); | |
container.addEventListener('click', function(event) { | |
setTool(event.target.dataset.tool); | |
}); | |
setTool(defaultTool); | |
} | |
function setTool(tool) { | |
curTool = tool; | |
Object.keys(tools).forEach(function(tool) { | |
var elm = tools[tool].element; | |
elm.id = elm.dataset.tool == curTool ? 'selected' : ''; | |
}); | |
} | |
function log(str) { | |
logs.push(str); | |
var diff = Math.max(logs.length - 20, 0); | |
logs.splice(0, diff); | |
while (logContainer.firstChild) { | |
logContainer.removeChild(logContainer.firstChild); | |
} | |
for (var i=0; i<logs.length; i++) { | |
var l = document.createElement('span'); | |
l.innerHTML = logs[i]; | |
logContainer.appendChild(l); | |
} | |
} | |
function stat(val) { | |
statsContainer.innerHTML = val; | |
} | |
function clamp01(val) { | |
return clamp(val, 0, 1); | |
} | |
function clamp(val, min, max) { | |
return Math.min(Math.max(val, min), max); | |
} | |
function map(val, mina, maxa, minb, maxb) { | |
return minb + (maxb - minb) * ((val - mina) / (maxa - mina)); | |
} | |
canvas.width = cfg.cellSize * cfg.gridWidth; | |
canvas.height = cfg.cellSize * cfg.gridHeight; | |
fillTiles(); | |
/* | |
drawRect(4, 4, 8, 8, 3); | |
drawRect(11, 4, 5, 5, 3); | |
drawRect(4, 11, 8, 8, 3); | |
drawRect(11, 11, 12, 12, 3); | |
drawDot(6, 11, 0); | |
//drawDot(11, 14, 0); | |
floodFill(5, 5, V_TYPE.ROOM); | |
*/ | |
render(); | |
registerTools(tools, 'room'); | |
logContainer = document.getElementById('log'); | |
statsContainer = document.getElementById('stats'); | |
</script> | |
<script id="jsbin-source-css" type="text/css">body { | |
margin: 50px 25px 0 25px; | |
background-color: #ccc; | |
} | |
canvas { | |
float: left; | |
} | |
#stats { | |
float: left; | |
} | |
#tools { | |
clear: left; | |
padding: 0.1rem; | |
} | |
#tools a { | |
margin: 0 0.3rem 0 0; | |
font-size: 0.9rem; | |
font-family: sans-serif; | |
} | |
#tools a#selected { | |
background-color: red; | |
color: white; | |
} | |
#log { | |
background-color: white; | |
margin-top: 0.5rem; | |
} | |
#log span { | |
display: block; | |
font-family: monospace; | |
}</script> | |
<script id="jsbin-source-javascript" type="text/javascript">var cfg = { | |
cellSize: 9, | |
tileColors: [ | |
function(v) { return 'rgb(255,255,255)'; }, | |
function(v) { | |
var fv = Math.floor(map(v, 0, 1, 0, 225)); | |
return 'rgb('+fv+','+fv+','+fv+')'; }, | |
function(v) { return 'rgb(0,255,0)'; }, | |
function(v) { return 'rgb(0,0,0)'; }, | |
function(v) { return 'rgb(127,0,127)'; }, | |
], | |
jobSpeed: 250, | |
gridWidth: 32, | |
gridHeight: 32, | |
gridAreal: 32 * 32 | |
}; | |
var V_TYPE = { | |
VACUUM: 0, | |
ROOM: 1, | |
PRODUCER: 2, | |
WALL: 3, | |
REPAIR: 4, | |
}; | |
var logs = []; | |
var logContainer; | |
var statsContainer; | |
var jobsGrow = []; | |
var jobsShrink = []; | |
var jobsRepair = []; | |
var tiles = []; | |
var curTool; | |
var canvas = document.getElementById('c'); | |
var ctx = canvas.getContext('2d'); | |
var offsetsDiag = [ | |
{x:-1, y:1}, {x:-1, y:-1}, {x:1, y:-1}, {x:1, y:1} | |
]; | |
var offsetsVonN = [ | |
{x:0, y:1}, {x:-1, y:0}, {x:0, y:-1}, {x:1, y:0} | |
]; | |
var offsetsMoore = [ | |
{x:0, y:1}, {x:-1, y:1}, {x:-1, y:0}, | |
{x:-1, y:-1}, {x:0, y:-1}, {x:1, y:-1}, | |
{x:1, y:0}, {x:1, y:1} | |
]; | |
var tools = { | |
vacuum: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER || | |
tile.t === V_TYPE.ROOM || | |
tile.t === V_TYPE.VACUUM) { | |
return; | |
} | |
addVacuum(tile); | |
} | |
}, | |
room: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER) { | |
return; | |
} | |
var neighbours = getOrderedNeighboursTo(tile.x, tile.y); | |
for (var j=0; j<neighbours.length; j++) { | |
var ni = neighbours[j]; | |
if (ni === -1) { | |
continue; | |
} | |
if (tiles[ni].t !== V_TYPE.VACUUM) { | |
continue; | |
} | |
tiles[ni].t = V_TYPE.WALL; | |
} | |
// If any of the neighbours are vacuum, consume | |
// the tile by vacuum. | |
/* | |
var vacuum = false; | |
eachNeighbour(tile, function(ntile, n, i) { | |
if (ntile.t === V_TYPE.VACUUM) { | |
vacuum = true; | |
} | |
}); | |
if (vacuum) { | |
tile.t = V_TYPE.ROOM; | |
fillVacuum(tile); | |
return; | |
} | |
*/ | |
addRoom(tile); | |
// Otherwise, add room and continue the | |
// grow algorithm if any | |
var ns = getNeighboursTo(tile.x, tile.y); | |
var max = 0; | |
var m = -1; | |
for (var i=0; i<ns.length; i++) { | |
var n = ns[i]; | |
if (tiles[n].v > max) { | |
max = tiles[n].v; | |
m = i; | |
} | |
} | |
if (max > 0) { | |
growFrom(tiles[ns[m]]); | |
} | |
} | |
}, | |
producer: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER) { | |
tile.t = V_TYPE.ROOM; | |
tile.v = 7; | |
shrinkFrom(tile); | |
return | |
} | |
if (tile.t === V_TYPE.ROOM || | |
tile.t === V_TYPE.VACUUM) { | |
tile.t = V_TYPE.PRODUCER; | |
tile.v = 8; | |
growFrom(tile); | |
return; | |
} | |
} | |
}, | |
wall: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER) { | |
return; | |
} | |
// When repairing a wall | |
if (tile.t === V_TYPE.VACUUM) { | |
// If tile has two adjacent neighbours, either NS or EW, | |
// consider it a repair | |
var sig = getSignature(tile); | |
if (sig === 5 || sig === 10) { | |
console.log('repair'); | |
var neighs = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighs.length; i++) { | |
var n = neighs[i]; | |
var nt = tiles[n]; | |
var v = net.findVertex(nt.x, nt.y); | |
console.log(nt.x, nt.y, 'has vertex', v); | |
if (v < 0) { | |
continue; | |
} | |
console.log('fill from', nt.x, nt.y, '(',tile.x, tile.y,')'); | |
floodFill(nt.x, nt.y, V_TYPE.ROOM); | |
net.addEdgesToTile(nt); | |
net.addFacesToTile(nt); | |
updateStats(); | |
// look for neighbours that are of type ROOM | |
// if (tiles[n].t !== V_TYPE.ROOM) { | |
// continue; | |
// } | |
// var ok = repairFill(nt.x, nt.y); | |
// log("ok "+ok); | |
// if (ok) { | |
tile.t = V_TYPE.WALL; | |
tile.v = 0; | |
// } | |
} | |
return; | |
} | |
} | |
tile.t = V_TYPE.WALL; | |
tile.v = 0; | |
var min = 8; | |
var ni = -1; | |
eachNeighbour(tile, function(n, i) { | |
if (n.t !== V_TYPE.ROOM && | |
n.t !== V_TYPE.PRODUCER) { | |
return; | |
} | |
if (n.t === V_TYPE.ROOM) { | |
net.addEdgesToTile(n); | |
net.addFacesToTile(n); | |
updateStats(); | |
} | |
if (n.v < min) { | |
min = n.v; | |
ni = flattenIndex(n.x, n.y); | |
} | |
}); | |
if (min === 0 || min === 8) { | |
return; | |
} | |
var top = crawlUp(tiles[ni]); | |
if (top.v < 8) { | |
shrinkFrom(top); | |
} | |
} | |
}, | |
}; | |
var net = { | |
verts: [], | |
edges: [], | |
faces: [], | |
// bitmasks for illegal combos. There are only three (or two | |
// in this case): | |
// - when there are exactly three neighbours, and they are all | |
// on line, either along the x or y axis. So something like: | |
// 1010 and 0101 | |
illegals: [], | |
getNeighbours: function(x, y) { | |
var neighbours = []; | |
neighbours.push(this.findVertex(x, y+1)); // S | |
neighbours.push(this.findVertex(x-1, y )); // W | |
neighbours.push(this.findVertex(x , y-1)); // N | |
neighbours.push(this.findVertex(x+1, y )); // E | |
return neighbours; | |
}, | |
addMooreNeighbours: function(list, tile, offsets) { | |
for (var n=0, o=1; n<offsets.length; n++, o+=2) { | |
var v = this.findVertex(tile.x + offsets[n].x, | |
tile.y + offsets[n].y); | |
list.splice(o, 0, v); | |
} | |
}, | |
addVertex: function(tile) { | |
this.verts.push({x: tile.x, y: tile.y}); | |
var v = this.verts.length - 1; | |
var neighbours = this.getNeighbours(tile.x, tile.y); | |
this.addEdges(v, neighbours); | |
this.addMooreNeighbours(neighbours, tile, offsetsDiag); | |
this.addFaces(v, neighbours); | |
}, | |
findVertex: function(x, y) { | |
for (var i=0; i<this.verts.length; i++) { | |
if (this.verts[i].x === x && | |
this.verts[i].y === y) { | |
return i; | |
} | |
} | |
return -1; | |
}, | |
addEdgesToTile: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var neighbours = this.getNeighbours(tile.x, tile.y); | |
this.addEdges(v, neighbours); | |
}, | |
addEdges: function(fromVert, neighbours) { | |
function valid(n) { | |
return n > -1; | |
} | |
var nlist = neighbours.filter(valid); | |
for (var i=0; i<nlist.length; i++) { | |
this.edges.push(fromVert); | |
this.edges.push(nlist[i]); | |
} | |
}, | |
findEdges: function(v) { | |
var edges = []; | |
for (var i=0; i<this.edges.length; i+=2) { | |
if (this.edges[i] === v || | |
this.edges[i+1] === v) { | |
edges.push(i); | |
} | |
} | |
return edges; | |
}, | |
removeEdges: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var edges = this.findEdges(v); | |
for (var i=edges.length-1; i>=0; i--) { | |
this.edges.splice(edges[i], 2); | |
} | |
}, | |
addFacesToTile: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var neighbours = this.getNeighbours(tile.x, tile.y); | |
this.addMooreNeighbours(neighbours, tile, offsetsDiag); | |
this.addFaces(v, neighbours); | |
}, | |
addFaces: function(fromVert, neighbours) { | |
var exists = function(a, b, c) { | |
return this[a] > -1 && | |
this[b] > -1 && | |
this[c] > -1; | |
}.bind(neighbours); | |
var create = function(a, b, c) { | |
if (exists(a, b, c)) { | |
this.faces.push(fromVert); | |
this.faces.push(neighbours[a]); | |
this.faces.push(neighbours[b]); | |
this.faces.push(neighbours[c]); | |
} | |
}.bind(this); | |
create(0,1,2); | |
create(2,3,4); | |
create(4,5,6); | |
create(6,7,0); | |
}, | |
findFaces: function(v) { | |
var faces = []; | |
for (var i=0; i<this.faces.length; i+=4) { | |
if (this.faces[i] === v || | |
this.faces[i+1] === v || | |
this.faces[i+2] === v || | |
this.faces[i+3] === v) { | |
faces.push(i); | |
} | |
} | |
return faces; | |
}, | |
removeFaces: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var faces = this.findFaces(v); | |
for (var i=faces.length-1; i>=0; i--) { | |
this.faces.splice(faces[i], 4); | |
} | |
}, | |
}; | |
function addVacuum(tile) { | |
if (tile.t === V_TYPE.WALL) { | |
eachNeighbour(tile, function(n, i) { | |
if (n.t === V_TYPE.ROOM) { | |
net.removeEdges(n); | |
net.removeFaces(n); | |
updateStats(); | |
} | |
}); | |
} | |
eachNeighbour(tile, function(n, i) { | |
if (n.t === V_TYPE.ROOM) { | |
fillVacuum(n); | |
} | |
}); | |
tile.t = V_TYPE.VACUUM; | |
} | |
function addRoom(tile) { | |
tile.t = V_TYPE.ROOM; | |
net.addVertex(tile); | |
updateStats(); | |
// network.addVertex(tile.x, tile.y) | |
// - check neighbours (look in vertices list) | |
// - add edges and faces | |
// num neighbours | |
// 0: add its own vertex to network mesh | |
// 1: add vertex, 1 edge | |
// 2: add vertex, 2 edges, 1 face | |
// 3: add vertex, 3 edges, 2 faces | |
// 4: add vertex, 4 edges, 3 faces | |
// edges = neighbours | |
// faces = edges - 1 | |
} | |
function updateStats() { | |
var v = net.verts.length; | |
var e = net.edges.length / 2; | |
var f = net.faces.length / 4; | |
var state = v - e + f; | |
stat((state === 1 ? 'closed' : 'open') + ' ('+v+', '+e+', '+f+') '+state); | |
} | |
function drawRect(x, y, w, h, t) { | |
drawLine([x, y], w, 0, t); | |
drawLine([x, y+1], h-2, 1, t); | |
drawLine([x+w-1, y+1], h-2, 1, t); | |
drawLine([x, y+h-1], w, 0, t); | |
} | |
// dir is either 0 or 1, depending on the direction | |
// you want to draw. 0 = x and 1 = y | |
function drawLine(coord, len, dir, t) { | |
for (var i=0; i<len; i++) { | |
var n = flattenIndex(coord[0], coord[1]); | |
tiles[n].t = t; | |
coord[dir]++; | |
} | |
} | |
function drawDot(x, y, t) { | |
var n = flattenIndex(x, y); | |
tiles[n].t = t; | |
} | |
function floodFill(x, y, t, cb) { | |
var fi = flattenIndex(x, y); | |
var fill_type = tiles[fi].t; | |
var fill = []; | |
fill.push(tiles[fi]); | |
while (fill.length > 0) { | |
var tile = fill.shift(); | |
if (tile.t === t) { | |
continue; | |
} | |
tile.t = t; | |
if (typeof cb !== 'undefined') { | |
cb(tile); | |
} | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (tiles[n].t === fill_type) { | |
fill.push(tiles[n]); | |
} | |
} | |
} | |
} | |
function repairFill(x, y) { | |
var fi = flattenIndex(x, y); | |
var fill = []; | |
fill.push(tiles[fi]); | |
var success = true; | |
while (fill.length > 0) { | |
var tile = fill.shift(); | |
if (tile.t === V_TYPE.VACUUM) { | |
success = false; | |
break; | |
} | |
tile.t = V_TYPE.REPAIR; | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (tiles[n].t === V_TYPE.ROOM) { | |
fill.push(tiles[n]); | |
} | |
} | |
} | |
return success; | |
} | |
function crawlUp(tile) { | |
var ti = flattenIndex(tile.x, tile.y); | |
var list = []; | |
list.push(tiles[ti]); | |
var top = 0; | |
while (list.length > 0) { | |
var t = list.shift(); | |
if (t.v === 0 || t.v === 8) { | |
break; | |
} | |
var max = 0; | |
var ni = -1; | |
var neighbours = getNeighboursTo(t.x, t.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (tiles[n].v > max) { | |
max = tiles[n].v; | |
ni = n; | |
} | |
} | |
if (max > 0) { | |
top = max; | |
ti = ni; | |
list.push(tiles[ni]); | |
} | |
} | |
return tiles[ti]; | |
} | |
function fillVacuum(tile) { | |
floodFill(tile.x, | |
tile.y, | |
V_TYPE.VACUUM, | |
function(t) { t.a = -1; }); | |
} | |
function flattenIndex(x, y) { | |
return x + cfg.gridWidth * y; | |
} | |
function setTiles(callback) { | |
var x=0; | |
var y=0; | |
for (var i=0; i<cfg.gridAreal; i++) { | |
x = (x + 1) % cfg.gridWidth; | |
y = Math.floor(i / cfg.gridHeight); | |
var n = flattenIndex(x,y); | |
tiles[n] = callback(tiles[n], x, y, i); | |
} | |
} | |
function fillTiles() { | |
setTiles(function(tile, x, y, i) { | |
return {x:x, y:y, t:V_TYPE.VACUUM, v:0, a:0}; | |
}); | |
} | |
function clear() { | |
ctx.fillStyle = 'rgb(255,255,255)'; | |
ctx.fillRect(0, 0, | |
cfg.cellSize * cfg.gridWidth, | |
cfg.cellSize * cfg.gridHeight); | |
} | |
function getTile(event, cellSize) { | |
return { | |
x: Math.floor(event.offsetX / cellSize), | |
y: Math.floor(event.offsetY / cellSize) | |
}; | |
} | |
function render() { | |
clear(); | |
var scale = function(val) { | |
return { | |
x: val.x * cfg.cellSize + cfg.cellSize * 0.5, | |
y: val.y * cfg.cellSize + cfg.cellSize * 0.5, | |
} | |
} | |
var i; | |
for (i=0; i<cfg.gridAreal; i++) { | |
var tile = tiles[i]; | |
var color = cfg.tileColors[tile.t]; | |
ctx.fillStyle = color(1 - (tile.v / 8)); | |
ctx.fillRect(tile.x * cfg.cellSize, | |
tile.y * cfg.cellSize, | |
cfg.cellSize, | |
cfg.cellSize); | |
} | |
for (i=0; i<net.verts.length; i++) { | |
var vert = net.verts[i]; | |
ctx.fillStyle = 'rgb(0, 0, 0)'; | |
ctx.fillRect(scale(vert.x), scale(vert.y), 1, 1); | |
} | |
ctx.beginPath(); | |
for (i=0; i<net.edges.length; i+=2) { | |
var fv = net.verts[net.edges[i]]; | |
var tv = net.verts[net.edges[i+1]]; | |
fv = scale(fv); | |
tv = scale(tv); | |
ctx.moveTo(fv.x, fv.y); | |
ctx.lineTo(tv.x, tv.y); | |
} | |
ctx.stroke(); | |
} | |
// A tile's signature is a bitmask telling | |
// which sides of the tile are walls | |
function getSignature(tile) { | |
var neighbours = getOrderedNeighboursTo(tile.x, tile.y); | |
var sig = 0; | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (typeof n === 'undefined') { | |
continue; | |
} | |
if (tiles[n].t === V_TYPE.WALL) { | |
sig += 1 << i; | |
} | |
} | |
return sig; | |
} | |
function countNeighbours(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
var count = 0; | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
count += clamp01(tiles[n].t); | |
} | |
return count; | |
} | |
function getNeighboursBySig(tile, sig) { | |
var neighbours = []; | |
var dirs = [{x:0,y:1},{x:-1,y:0},{x:0,y:-1},{x:1,y:0}]; | |
for (var i=1, n=0; i<16; i*=2, n++) { | |
if (sig & i === 0) { | |
continue; | |
} | |
var x = tile.x + dirs[n].x; | |
var y = tile.y + dirs[n].y; | |
var j = flattenIndex(x, y); | |
if (j < 0 || j >= tiles.length) { | |
continue; | |
} | |
neighbours[n] = tiles[j]; | |
} | |
return neighbours; | |
} | |
// Returns a list of neighbours within the | |
// Von Neumann neighbourhood. The list is | |
// not ordered on neighbourhood | |
function getNeighboursTo(x, y) { | |
var addNeighbour = function(x, y, list) { | |
var i = flattenIndex(x, y); | |
if (i >= 0 && i < tiles.length) { | |
list.push(i); | |
} | |
} | |
var neighbours = []; | |
addNeighbour(x, y+1, neighbours); // S | |
addNeighbour(x-1, y , neighbours); // W | |
addNeighbour(x, y-1, neighbours); // N | |
addNeighbour(x+1, y , neighbours); // E | |
return neighbours; | |
} | |
function getOrderedNeighboursTo(x, y) { | |
var getNeighbour = function(x, y) { | |
var i = flattenIndex(x, y); | |
if (i >= 0 && i < tiles.length) { | |
return i; | |
} | |
return -1; | |
} | |
var neighbours = []; | |
neighbours.push(getNeighbour(x, y+1)); // S | |
neighbours.push(getNeighbour(x-1, y )); // W | |
neighbours.push(getNeighbour(x, y-1)); // N | |
neighbours.push(getNeighbour(x+1, y )); // E | |
return neighbours; | |
} | |
function getOrderedMooreNeighboursTo(x, y) { | |
var getNeighbour = function(x, y) { | |
var i = flattenIndex(x, y); | |
if (i >= 0 && i < tiles.length) { | |
return i; | |
} | |
return -1; | |
} | |
var neighbours = []; | |
neighbours.push(getNeighbour(x, y+1)); // S | |
neighbours.push(getNeighbour(x-1, y+1)); // SW | |
neighbours.push(getNeighbour(x-1, y )); // W | |
neighbours.push(getNeighbour(x-1, y-1)); // NW | |
neighbours.push(getNeighbour(x, y-1)); // N | |
neighbours.push(getNeighbour(x+1, y-1)); // NE | |
neighbours.push(getNeighbour(x+1, y )); // E | |
neighbours.push(getNeighbour(x+1, y+1)); // SE | |
return neighbours; | |
} | |
function eachNeighbour(tile, cb) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
cb(tiles[n], i, n); | |
} | |
} | |
function processJobs(jobs, cb) { | |
var numJobs = jobs.length; | |
var counter = 0; | |
while (counter < numJobs) { | |
cb(jobs.shift()); | |
counter++; | |
} | |
} | |
function growFrom(tile) { | |
jobsGrow.push(tile); | |
window.setTimeout(grow, cfg.jobSpeed); | |
} | |
function grow() { | |
processJobs(jobsGrow, function(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
// We don't care about neighbours that are greater | |
// or equal, or exactly one value lower than the | |
// current tile. | |
var diff = tiles[n].v - tile.v; | |
if (diff >= 0 || diff === -1) { | |
continue; | |
} | |
// Ignore walls | |
if (tiles[n].t === V_TYPE.WALL) { | |
continue; | |
} | |
tiles[n].v = tile.v - 1; | |
if (tiles[n].v > 1) { | |
jobsGrow.push(tiles[n]); | |
} | |
} | |
}); | |
render(); | |
if (jobsGrow.length > 0) { | |
window.setTimeout(grow, cfg.jobSpeed); | |
} | |
} | |
function shrinkFrom(tile) { | |
var addJob = function(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
jobsShrink.push(tiles[neighbours[i]]); | |
} | |
jobsShrink.push(tile); | |
} | |
if (tile instanceof Array) { | |
for (var t=0; t<tile.length; t++) { | |
addJob(tile[t]); | |
} | |
} else { | |
addJob(tile); | |
} | |
window.setTimeout(shrink, cfg.jobSpeed); | |
} | |
function shrink() { | |
var moreJobs = []; | |
processJobs(jobsShrink, function(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
var v = {v:0}; | |
for (var i=0; i<neighbours.length; i++) { | |
var ni = neighbours[i]; | |
if (tiles[ni].v > v.v) { | |
v = tiles[ni]; | |
} | |
} | |
if (v.v <= tile.v) { | |
var newVal = v.v - 1; | |
if (newVal >= 0) { | |
tile.v = newVal; | |
moreJobs.push(tile); | |
} else { | |
tile.v = 0; | |
} | |
} | |
}); | |
render(); | |
shrinkFrom(moreJobs); | |
} | |
canvas.addEventListener('click', function(event) { | |
var click = getTile(event, cfg.cellSize); | |
var i = flattenIndex(click.x, click.y); | |
var tile = tiles[i]; | |
tools[curTool].callback(tile); | |
render(); | |
}); | |
function registerTools(tools, defaultTool) { | |
var container = document.getElementById('tools'); | |
Object.keys(tools).forEach(function(tool) { | |
var a = document.createElement('a'); | |
a.innerHTML= tool; | |
a.dataset.tool = tool; | |
container.appendChild(a); | |
tools[tool].element = a; | |
}); | |
container.addEventListener('click', function(event) { | |
setTool(event.target.dataset.tool); | |
}); | |
setTool(defaultTool); | |
} | |
function setTool(tool) { | |
curTool = tool; | |
Object.keys(tools).forEach(function(tool) { | |
var elm = tools[tool].element; | |
elm.id = elm.dataset.tool == curTool ? 'selected' : ''; | |
}); | |
} | |
function log(str) { | |
logs.push(str); | |
var diff = Math.max(logs.length - 20, 0); | |
logs.splice(0, diff); | |
while (logContainer.firstChild) { | |
logContainer.removeChild(logContainer.firstChild); | |
} | |
for (var i=0; i<logs.length; i++) { | |
var l = document.createElement('span'); | |
l.innerHTML = logs[i]; | |
logContainer.appendChild(l); | |
} | |
} | |
function stat(val) { | |
statsContainer.innerHTML = val; | |
} | |
function clamp01(val) { | |
return clamp(val, 0, 1); | |
} | |
function clamp(val, min, max) { | |
return Math.min(Math.max(val, min), max); | |
} | |
function map(val, mina, maxa, minb, maxb) { | |
return minb + (maxb - minb) * ((val - mina) / (maxa - mina)); | |
} | |
canvas.width = cfg.cellSize * cfg.gridWidth; | |
canvas.height = cfg.cellSize * cfg.gridHeight; | |
fillTiles(); | |
/* | |
drawRect(4, 4, 8, 8, 3); | |
drawRect(11, 4, 5, 5, 3); | |
drawRect(4, 11, 8, 8, 3); | |
drawRect(11, 11, 12, 12, 3); | |
drawDot(6, 11, 0); | |
//drawDot(11, 14, 0); | |
floodFill(5, 5, V_TYPE.ROOM); | |
*/ | |
render(); | |
registerTools(tools, 'room'); | |
logContainer = document.getElementById('log'); | |
statsContainer = document.getElementById('stats'); | |
</script></body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
margin: 50px 25px 0 25px; | |
background-color: #ccc; | |
} | |
canvas { | |
float: left; | |
} | |
#stats { | |
float: left; | |
} | |
#tools { | |
clear: left; | |
padding: 0.1rem; | |
} | |
#tools a { | |
margin: 0 0.3rem 0 0; | |
font-size: 0.9rem; | |
font-family: sans-serif; | |
} | |
#tools a#selected { | |
background-color: red; | |
color: white; | |
} | |
#log { | |
background-color: white; | |
margin-top: 0.5rem; | |
} | |
#log span { | |
display: block; | |
font-family: monospace; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var cfg = { | |
cellSize: 9, | |
tileColors: [ | |
function(v) { return 'rgb(255,255,255)'; }, | |
function(v) { | |
var fv = Math.floor(map(v, 0, 1, 0, 225)); | |
return 'rgb('+fv+','+fv+','+fv+')'; }, | |
function(v) { return 'rgb(0,255,0)'; }, | |
function(v) { return 'rgb(0,0,0)'; }, | |
function(v) { return 'rgb(127,0,127)'; }, | |
], | |
jobSpeed: 250, | |
gridWidth: 32, | |
gridHeight: 32, | |
gridAreal: 32 * 32 | |
}; | |
var V_TYPE = { | |
VACUUM: 0, | |
ROOM: 1, | |
PRODUCER: 2, | |
WALL: 3, | |
REPAIR: 4, | |
}; | |
var logs = []; | |
var logContainer; | |
var statsContainer; | |
var jobsGrow = []; | |
var jobsShrink = []; | |
var jobsRepair = []; | |
var tiles = []; | |
var curTool; | |
var canvas = document.getElementById('c'); | |
var ctx = canvas.getContext('2d'); | |
var offsetsDiag = [ | |
{x:-1, y:1}, {x:-1, y:-1}, {x:1, y:-1}, {x:1, y:1} | |
]; | |
var offsetsVonN = [ | |
{x:0, y:1}, {x:-1, y:0}, {x:0, y:-1}, {x:1, y:0} | |
]; | |
var offsetsMoore = [ | |
{x:0, y:1}, {x:-1, y:1}, {x:-1, y:0}, | |
{x:-1, y:-1}, {x:0, y:-1}, {x:1, y:-1}, | |
{x:1, y:0}, {x:1, y:1} | |
]; | |
var tools = { | |
vacuum: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER || | |
tile.t === V_TYPE.ROOM || | |
tile.t === V_TYPE.VACUUM) { | |
return; | |
} | |
addVacuum(tile); | |
} | |
}, | |
room: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER) { | |
return; | |
} | |
var neighbours = getOrderedNeighboursTo(tile.x, tile.y); | |
for (var j=0; j<neighbours.length; j++) { | |
var ni = neighbours[j]; | |
if (ni === -1) { | |
continue; | |
} | |
if (tiles[ni].t !== V_TYPE.VACUUM) { | |
continue; | |
} | |
tiles[ni].t = V_TYPE.WALL; | |
} | |
// If any of the neighbours are vacuum, consume | |
// the tile by vacuum. | |
/* | |
var vacuum = false; | |
eachNeighbour(tile, function(ntile, n, i) { | |
if (ntile.t === V_TYPE.VACUUM) { | |
vacuum = true; | |
} | |
}); | |
if (vacuum) { | |
tile.t = V_TYPE.ROOM; | |
fillVacuum(tile); | |
return; | |
} | |
*/ | |
addRoom(tile); | |
// Otherwise, add room and continue the | |
// grow algorithm if any | |
var ns = getNeighboursTo(tile.x, tile.y); | |
var max = 0; | |
var m = -1; | |
for (var i=0; i<ns.length; i++) { | |
var n = ns[i]; | |
if (tiles[n].v > max) { | |
max = tiles[n].v; | |
m = i; | |
} | |
} | |
if (max > 0) { | |
growFrom(tiles[ns[m]]); | |
} | |
} | |
}, | |
producer: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER) { | |
tile.t = V_TYPE.ROOM; | |
tile.v = 7; | |
shrinkFrom(tile); | |
return | |
} | |
if (tile.t === V_TYPE.ROOM || | |
tile.t === V_TYPE.VACUUM) { | |
tile.t = V_TYPE.PRODUCER; | |
tile.v = 8; | |
growFrom(tile); | |
return; | |
} | |
} | |
}, | |
wall: { | |
callback: function(tile) { | |
if (tile.t === V_TYPE.PRODUCER) { | |
return; | |
} | |
// When repairing a wall | |
if (tile.t === V_TYPE.VACUUM) { | |
// If tile has two adjacent neighbours, either NS or EW, | |
// consider it a repair | |
var sig = getSignature(tile); | |
if (sig === 5 || sig === 10) { | |
console.log('repair'); | |
var neighs = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighs.length; i++) { | |
var n = neighs[i]; | |
var nt = tiles[n]; | |
var v = net.findVertex(nt.x, nt.y); | |
console.log(nt.x, nt.y, 'has vertex', v); | |
if (v < 0) { | |
continue; | |
} | |
console.log('fill from', nt.x, nt.y, '(',tile.x, tile.y,')'); | |
floodFill(nt.x, nt.y, V_TYPE.ROOM); | |
net.addEdgesToTile(nt); | |
net.addFacesToTile(nt); | |
updateStats(); | |
// look for neighbours that are of type ROOM | |
// if (tiles[n].t !== V_TYPE.ROOM) { | |
// continue; | |
// } | |
// var ok = repairFill(nt.x, nt.y); | |
// log("ok "+ok); | |
// if (ok) { | |
tile.t = V_TYPE.WALL; | |
tile.v = 0; | |
// } | |
} | |
return; | |
} | |
} | |
tile.t = V_TYPE.WALL; | |
tile.v = 0; | |
var min = 8; | |
var ni = -1; | |
eachNeighbour(tile, function(n, i) { | |
if (n.t !== V_TYPE.ROOM && | |
n.t !== V_TYPE.PRODUCER) { | |
return; | |
} | |
if (n.t === V_TYPE.ROOM) { | |
net.addEdgesToTile(n); | |
net.addFacesToTile(n); | |
updateStats(); | |
} | |
if (n.v < min) { | |
min = n.v; | |
ni = flattenIndex(n.x, n.y); | |
} | |
}); | |
if (min === 0 || min === 8) { | |
return; | |
} | |
var top = crawlUp(tiles[ni]); | |
if (top.v < 8) { | |
shrinkFrom(top); | |
} | |
} | |
}, | |
}; | |
var net = { | |
verts: [], | |
edges: [], | |
faces: [], | |
// bitmasks for illegal combos. There are only three (or two | |
// in this case): | |
// - when there are exactly three neighbours, and they are all | |
// on line, either along the x or y axis. So something like: | |
// 1010 and 0101 | |
illegals: [], | |
getNeighbours: function(x, y) { | |
var neighbours = []; | |
neighbours.push(this.findVertex(x, y+1)); // S | |
neighbours.push(this.findVertex(x-1, y )); // W | |
neighbours.push(this.findVertex(x , y-1)); // N | |
neighbours.push(this.findVertex(x+1, y )); // E | |
return neighbours; | |
}, | |
addMooreNeighbours: function(list, tile, offsets) { | |
for (var n=0, o=1; n<offsets.length; n++, o+=2) { | |
var v = this.findVertex(tile.x + offsets[n].x, | |
tile.y + offsets[n].y); | |
list.splice(o, 0, v); | |
} | |
}, | |
addVertex: function(tile) { | |
this.verts.push({x: tile.x, y: tile.y}); | |
var v = this.verts.length - 1; | |
var neighbours = this.getNeighbours(tile.x, tile.y); | |
this.addEdges(v, neighbours); | |
this.addMooreNeighbours(neighbours, tile, offsetsDiag); | |
this.addFaces(v, neighbours); | |
}, | |
findVertex: function(x, y) { | |
for (var i=0; i<this.verts.length; i++) { | |
if (this.verts[i].x === x && | |
this.verts[i].y === y) { | |
return i; | |
} | |
} | |
return -1; | |
}, | |
addEdgesToTile: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var neighbours = this.getNeighbours(tile.x, tile.y); | |
this.addEdges(v, neighbours); | |
}, | |
addEdges: function(fromVert, neighbours) { | |
function valid(n) { | |
return n > -1; | |
} | |
var nlist = neighbours.filter(valid); | |
for (var i=0; i<nlist.length; i++) { | |
this.edges.push(fromVert); | |
this.edges.push(nlist[i]); | |
} | |
}, | |
findEdges: function(v) { | |
var edges = []; | |
for (var i=0; i<this.edges.length; i+=2) { | |
if (this.edges[i] === v || | |
this.edges[i+1] === v) { | |
edges.push(i); | |
} | |
} | |
return edges; | |
}, | |
removeEdges: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var edges = this.findEdges(v); | |
for (var i=edges.length-1; i>=0; i--) { | |
this.edges.splice(edges[i], 2); | |
} | |
}, | |
addFacesToTile: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var neighbours = this.getNeighbours(tile.x, tile.y); | |
this.addMooreNeighbours(neighbours, tile, offsetsDiag); | |
this.addFaces(v, neighbours); | |
}, | |
addFaces: function(fromVert, neighbours) { | |
var exists = function(a, b, c) { | |
return this[a] > -1 && | |
this[b] > -1 && | |
this[c] > -1; | |
}.bind(neighbours); | |
var create = function(a, b, c) { | |
if (exists(a, b, c)) { | |
this.faces.push(fromVert); | |
this.faces.push(neighbours[a]); | |
this.faces.push(neighbours[b]); | |
this.faces.push(neighbours[c]); | |
} | |
}.bind(this); | |
create(0,1,2); | |
create(2,3,4); | |
create(4,5,6); | |
create(6,7,0); | |
}, | |
findFaces: function(v) { | |
var faces = []; | |
for (var i=0; i<this.faces.length; i+=4) { | |
if (this.faces[i] === v || | |
this.faces[i+1] === v || | |
this.faces[i+2] === v || | |
this.faces[i+3] === v) { | |
faces.push(i); | |
} | |
} | |
return faces; | |
}, | |
removeFaces: function(tile) { | |
var v = this.findVertex(tile.x, tile.y); | |
var faces = this.findFaces(v); | |
for (var i=faces.length-1; i>=0; i--) { | |
this.faces.splice(faces[i], 4); | |
} | |
}, | |
}; | |
function addVacuum(tile) { | |
if (tile.t === V_TYPE.WALL) { | |
eachNeighbour(tile, function(n, i) { | |
if (n.t === V_TYPE.ROOM) { | |
net.removeEdges(n); | |
net.removeFaces(n); | |
updateStats(); | |
} | |
}); | |
} | |
eachNeighbour(tile, function(n, i) { | |
if (n.t === V_TYPE.ROOM) { | |
fillVacuum(n); | |
} | |
}); | |
tile.t = V_TYPE.VACUUM; | |
} | |
function addRoom(tile) { | |
tile.t = V_TYPE.ROOM; | |
net.addVertex(tile); | |
updateStats(); | |
// network.addVertex(tile.x, tile.y) | |
// - check neighbours (look in vertices list) | |
// - add edges and faces | |
// num neighbours | |
// 0: add its own vertex to network mesh | |
// 1: add vertex, 1 edge | |
// 2: add vertex, 2 edges, 1 face | |
// 3: add vertex, 3 edges, 2 faces | |
// 4: add vertex, 4 edges, 3 faces | |
// edges = neighbours | |
// faces = edges - 1 | |
} | |
function updateStats() { | |
var v = net.verts.length; | |
var e = net.edges.length / 2; | |
var f = net.faces.length / 4; | |
var state = v - e + f; | |
stat((state === 1 ? 'closed' : 'open') + ' ('+v+', '+e+', '+f+') '+state); | |
} | |
function drawRect(x, y, w, h, t) { | |
drawLine([x, y], w, 0, t); | |
drawLine([x, y+1], h-2, 1, t); | |
drawLine([x+w-1, y+1], h-2, 1, t); | |
drawLine([x, y+h-1], w, 0, t); | |
} | |
// dir is either 0 or 1, depending on the direction | |
// you want to draw. 0 = x and 1 = y | |
function drawLine(coord, len, dir, t) { | |
for (var i=0; i<len; i++) { | |
var n = flattenIndex(coord[0], coord[1]); | |
tiles[n].t = t; | |
coord[dir]++; | |
} | |
} | |
function drawDot(x, y, t) { | |
var n = flattenIndex(x, y); | |
tiles[n].t = t; | |
} | |
function floodFill(x, y, t, cb) { | |
var fi = flattenIndex(x, y); | |
var fill_type = tiles[fi].t; | |
var fill = []; | |
fill.push(tiles[fi]); | |
while (fill.length > 0) { | |
var tile = fill.shift(); | |
if (tile.t === t) { | |
continue; | |
} | |
tile.t = t; | |
if (typeof cb !== 'undefined') { | |
cb(tile); | |
} | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (tiles[n].t === fill_type) { | |
fill.push(tiles[n]); | |
} | |
} | |
} | |
} | |
function repairFill(x, y) { | |
var fi = flattenIndex(x, y); | |
var fill = []; | |
fill.push(tiles[fi]); | |
var success = true; | |
while (fill.length > 0) { | |
var tile = fill.shift(); | |
if (tile.t === V_TYPE.VACUUM) { | |
success = false; | |
break; | |
} | |
tile.t = V_TYPE.REPAIR; | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (tiles[n].t === V_TYPE.ROOM) { | |
fill.push(tiles[n]); | |
} | |
} | |
} | |
return success; | |
} | |
function crawlUp(tile) { | |
var ti = flattenIndex(tile.x, tile.y); | |
var list = []; | |
list.push(tiles[ti]); | |
var top = 0; | |
while (list.length > 0) { | |
var t = list.shift(); | |
if (t.v === 0 || t.v === 8) { | |
break; | |
} | |
var max = 0; | |
var ni = -1; | |
var neighbours = getNeighboursTo(t.x, t.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (tiles[n].v > max) { | |
max = tiles[n].v; | |
ni = n; | |
} | |
} | |
if (max > 0) { | |
top = max; | |
ti = ni; | |
list.push(tiles[ni]); | |
} | |
} | |
return tiles[ti]; | |
} | |
function fillVacuum(tile) { | |
floodFill(tile.x, | |
tile.y, | |
V_TYPE.VACUUM, | |
function(t) { t.a = -1; }); | |
} | |
function flattenIndex(x, y) { | |
return x + cfg.gridWidth * y; | |
} | |
function setTiles(callback) { | |
var x=0; | |
var y=0; | |
for (var i=0; i<cfg.gridAreal; i++) { | |
x = (x + 1) % cfg.gridWidth; | |
y = Math.floor(i / cfg.gridHeight); | |
var n = flattenIndex(x,y); | |
tiles[n] = callback(tiles[n], x, y, i); | |
} | |
} | |
function fillTiles() { | |
setTiles(function(tile, x, y, i) { | |
return {x:x, y:y, t:V_TYPE.VACUUM, v:0, a:0}; | |
}); | |
} | |
function clear() { | |
ctx.fillStyle = 'rgb(255,255,255)'; | |
ctx.fillRect(0, 0, | |
cfg.cellSize * cfg.gridWidth, | |
cfg.cellSize * cfg.gridHeight); | |
} | |
function getTile(event, cellSize) { | |
return { | |
x: Math.floor(event.offsetX / cellSize), | |
y: Math.floor(event.offsetY / cellSize) | |
}; | |
} | |
function render() { | |
clear(); | |
var scale = function(val) { | |
return { | |
x: val.x * cfg.cellSize + cfg.cellSize * 0.5, | |
y: val.y * cfg.cellSize + cfg.cellSize * 0.5, | |
} | |
} | |
var i; | |
for (i=0; i<cfg.gridAreal; i++) { | |
var tile = tiles[i]; | |
var color = cfg.tileColors[tile.t]; | |
ctx.fillStyle = color(1 - (tile.v / 8)); | |
ctx.fillRect(tile.x * cfg.cellSize, | |
tile.y * cfg.cellSize, | |
cfg.cellSize, | |
cfg.cellSize); | |
} | |
for (i=0; i<net.verts.length; i++) { | |
var vert = net.verts[i]; | |
ctx.fillStyle = 'rgb(0, 0, 0)'; | |
ctx.fillRect(scale(vert.x), scale(vert.y), 1, 1); | |
} | |
ctx.beginPath(); | |
for (i=0; i<net.edges.length; i+=2) { | |
var fv = net.verts[net.edges[i]]; | |
var tv = net.verts[net.edges[i+1]]; | |
fv = scale(fv); | |
tv = scale(tv); | |
ctx.moveTo(fv.x, fv.y); | |
ctx.lineTo(tv.x, tv.y); | |
} | |
ctx.stroke(); | |
} | |
// A tile's signature is a bitmask telling | |
// which sides of the tile are walls | |
function getSignature(tile) { | |
var neighbours = getOrderedNeighboursTo(tile.x, tile.y); | |
var sig = 0; | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
if (typeof n === 'undefined') { | |
continue; | |
} | |
if (tiles[n].t === V_TYPE.WALL) { | |
sig += 1 << i; | |
} | |
} | |
return sig; | |
} | |
function countNeighbours(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
var count = 0; | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
count += clamp01(tiles[n].t); | |
} | |
return count; | |
} | |
function getNeighboursBySig(tile, sig) { | |
var neighbours = []; | |
var dirs = [{x:0,y:1},{x:-1,y:0},{x:0,y:-1},{x:1,y:0}]; | |
for (var i=1, n=0; i<16; i*=2, n++) { | |
if (sig & i === 0) { | |
continue; | |
} | |
var x = tile.x + dirs[n].x; | |
var y = tile.y + dirs[n].y; | |
var j = flattenIndex(x, y); | |
if (j < 0 || j >= tiles.length) { | |
continue; | |
} | |
neighbours[n] = tiles[j]; | |
} | |
return neighbours; | |
} | |
// Returns a list of neighbours within the | |
// Von Neumann neighbourhood. The list is | |
// not ordered on neighbourhood | |
function getNeighboursTo(x, y) { | |
var addNeighbour = function(x, y, list) { | |
var i = flattenIndex(x, y); | |
if (i >= 0 && i < tiles.length) { | |
list.push(i); | |
} | |
} | |
var neighbours = []; | |
addNeighbour(x, y+1, neighbours); // S | |
addNeighbour(x-1, y , neighbours); // W | |
addNeighbour(x, y-1, neighbours); // N | |
addNeighbour(x+1, y , neighbours); // E | |
return neighbours; | |
} | |
function getOrderedNeighboursTo(x, y) { | |
var getNeighbour = function(x, y) { | |
var i = flattenIndex(x, y); | |
if (i >= 0 && i < tiles.length) { | |
return i; | |
} | |
return -1; | |
} | |
var neighbours = []; | |
neighbours.push(getNeighbour(x, y+1)); // S | |
neighbours.push(getNeighbour(x-1, y )); // W | |
neighbours.push(getNeighbour(x, y-1)); // N | |
neighbours.push(getNeighbour(x+1, y )); // E | |
return neighbours; | |
} | |
function getOrderedMooreNeighboursTo(x, y) { | |
var getNeighbour = function(x, y) { | |
var i = flattenIndex(x, y); | |
if (i >= 0 && i < tiles.length) { | |
return i; | |
} | |
return -1; | |
} | |
var neighbours = []; | |
neighbours.push(getNeighbour(x, y+1)); // S | |
neighbours.push(getNeighbour(x-1, y+1)); // SW | |
neighbours.push(getNeighbour(x-1, y )); // W | |
neighbours.push(getNeighbour(x-1, y-1)); // NW | |
neighbours.push(getNeighbour(x, y-1)); // N | |
neighbours.push(getNeighbour(x+1, y-1)); // NE | |
neighbours.push(getNeighbour(x+1, y )); // E | |
neighbours.push(getNeighbour(x+1, y+1)); // SE | |
return neighbours; | |
} | |
function eachNeighbour(tile, cb) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
cb(tiles[n], i, n); | |
} | |
} | |
function processJobs(jobs, cb) { | |
var numJobs = jobs.length; | |
var counter = 0; | |
while (counter < numJobs) { | |
cb(jobs.shift()); | |
counter++; | |
} | |
} | |
function growFrom(tile) { | |
jobsGrow.push(tile); | |
window.setTimeout(grow, cfg.jobSpeed); | |
} | |
function grow() { | |
processJobs(jobsGrow, function(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
var n = neighbours[i]; | |
// We don't care about neighbours that are greater | |
// or equal, or exactly one value lower than the | |
// current tile. | |
var diff = tiles[n].v - tile.v; | |
if (diff >= 0 || diff === -1) { | |
continue; | |
} | |
// Ignore walls | |
if (tiles[n].t === V_TYPE.WALL) { | |
continue; | |
} | |
tiles[n].v = tile.v - 1; | |
if (tiles[n].v > 1) { | |
jobsGrow.push(tiles[n]); | |
} | |
} | |
}); | |
render(); | |
if (jobsGrow.length > 0) { | |
window.setTimeout(grow, cfg.jobSpeed); | |
} | |
} | |
function shrinkFrom(tile) { | |
var addJob = function(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
for (var i=0; i<neighbours.length; i++) { | |
jobsShrink.push(tiles[neighbours[i]]); | |
} | |
jobsShrink.push(tile); | |
} | |
if (tile instanceof Array) { | |
for (var t=0; t<tile.length; t++) { | |
addJob(tile[t]); | |
} | |
} else { | |
addJob(tile); | |
} | |
window.setTimeout(shrink, cfg.jobSpeed); | |
} | |
function shrink() { | |
var moreJobs = []; | |
processJobs(jobsShrink, function(tile) { | |
var neighbours = getNeighboursTo(tile.x, tile.y); | |
var v = {v:0}; | |
for (var i=0; i<neighbours.length; i++) { | |
var ni = neighbours[i]; | |
if (tiles[ni].v > v.v) { | |
v = tiles[ni]; | |
} | |
} | |
if (v.v <= tile.v) { | |
var newVal = v.v - 1; | |
if (newVal >= 0) { | |
tile.v = newVal; | |
moreJobs.push(tile); | |
} else { | |
tile.v = 0; | |
} | |
} | |
}); | |
render(); | |
shrinkFrom(moreJobs); | |
} | |
canvas.addEventListener('click', function(event) { | |
var click = getTile(event, cfg.cellSize); | |
var i = flattenIndex(click.x, click.y); | |
var tile = tiles[i]; | |
tools[curTool].callback(tile); | |
render(); | |
}); | |
function registerTools(tools, defaultTool) { | |
var container = document.getElementById('tools'); | |
Object.keys(tools).forEach(function(tool) { | |
var a = document.createElement('a'); | |
a.innerHTML= tool; | |
a.dataset.tool = tool; | |
container.appendChild(a); | |
tools[tool].element = a; | |
}); | |
container.addEventListener('click', function(event) { | |
setTool(event.target.dataset.tool); | |
}); | |
setTool(defaultTool); | |
} | |
function setTool(tool) { | |
curTool = tool; | |
Object.keys(tools).forEach(function(tool) { | |
var elm = tools[tool].element; | |
elm.id = elm.dataset.tool == curTool ? 'selected' : ''; | |
}); | |
} | |
function log(str) { | |
logs.push(str); | |
var diff = Math.max(logs.length - 20, 0); | |
logs.splice(0, diff); | |
while (logContainer.firstChild) { | |
logContainer.removeChild(logContainer.firstChild); | |
} | |
for (var i=0; i<logs.length; i++) { | |
var l = document.createElement('span'); | |
l.innerHTML = logs[i]; | |
logContainer.appendChild(l); | |
} | |
} | |
function stat(val) { | |
statsContainer.innerHTML = val; | |
} | |
function clamp01(val) { | |
return clamp(val, 0, 1); | |
} | |
function clamp(val, min, max) { | |
return Math.min(Math.max(val, min), max); | |
} | |
function map(val, mina, maxa, minb, maxb) { | |
return minb + (maxb - minb) * ((val - mina) / (maxa - mina)); | |
} | |
canvas.width = cfg.cellSize * cfg.gridWidth; | |
canvas.height = cfg.cellSize * cfg.gridHeight; | |
fillTiles(); | |
/* | |
drawRect(4, 4, 8, 8, 3); | |
drawRect(11, 4, 5, 5, 3); | |
drawRect(4, 11, 8, 8, 3); | |
drawRect(11, 11, 12, 12, 3); | |
drawDot(6, 11, 0); | |
//drawDot(11, 14, 0); | |
floodFill(5, 5, V_TYPE.ROOM); | |
*/ | |
render(); | |
registerTools(tools, 'room'); | |
logContainer = document.getElementById('log'); | |
statsContainer = document.getElementById('stats'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment