-
-
Save ArneBab/b955df115ab04079019e to your computer and use it in GitHub Desktop.
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
8fb2e1d19eba11e40ffaa70425f1585aeef48fe5 0 iJwEAAEIAAYFAlUPNnMACgkQ3M8NswvBBUixLAP/dujl7lJDpdoR97e52NCmANPaNb3cXCVwoZ96RXyFKXMj4sFp/cN321AoVBcThfHTco2NV02rTRQD9ZzrWIlTC412qezijC8hqBle9w9G1w5JWCIaLUX18vz/MECLj5Y/UQU7mvh8Dd7okfJxvDIYKzw1A6IQxd9hB31SS1xgbbA= |
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 canvas = document.getElementById('hexmap'); | |
var hexHeight, | |
hexRadius, | |
hexRectangleHeight, | |
hexRectangleWidth, | |
hexagonAngle = 0.523598776, // 30 degrees in radians | |
sideLength = 3, | |
boardWidth = 200, | |
boardHeight = 200, | |
fillColorActive = "#000000", | |
fillColorAlive = "#aaaaff", | |
fillColorInactive = "#ffffff", | |
strokeColor = "#cccccc", | |
clickCount = 0, | |
clickCountToTriggerConway = 5, | |
neededToLive = {3: true, 5: true}, | |
neededToRise = {2: true}, | |
// Alternate version (random does not stabilize). Shown by http://www.cse.sc.edu/~bays/h6h6h6/ | |
// neededToLive = {3: true}, | |
// neededToRise = {2: true, 4: true, 5: true}, | |
// Alternate: 34/2 (as by http://www.cs.ou.edu/~rlpage/SEcollab/20projects/ConwayPlusTopology.htm ) | |
// neededToLive = {3: true, 4: true}, | |
// neededToRise = {2: true}, | |
livingHexes = {5: {5: true, 7: true}, 7: {3: true, 5: true, 7: true, 9: true}, 9: {4: true, 8: true}, 10: {5: true, 7: true}, | |
32: {14: true, 16: true, 18: true}, 33: {15: true, 17: true}, 34: {13: true, 19: true}, 35: {15: true, 17: true}, | |
50: {30: true}, 51: {30: true}, 52: {30: true}, 53: {30: true}, 54: {30: true}, | |
60: {40: true}, 61: {40: true}, 62: {40: true}, 63: {40: true}, | |
80: {40: true, 41: true}, 81: {41: true, 39: true, 38: true, 37: true}, 82: {40: true, 37: true}, 83: {38: true}, | |
91: {40: true}, 92: {39: true, 41: true}, 93: {39: true, 41: true}, 94: {41: true}, | |
112: {51: true}, 113: {49: true, 51: true}, 114: {49: true, 51: true}, 116: {50: true}, | |
122: {50: true, 51: true}, 123: {50: true}, | |
100: {39: true, 40: true, 41: true, 43: true}, 101: {42: true}, | |
70: {39: true, 41: true}, 71: {40: true, 43: true}, 73: {41: true, 43: true}, 74: {42: true}}; | |
// add random hexes | |
// for (i=0; i < 1000; ++i) { | |
// var hx = 1 + Math.floor(Math.random()*boardWidth), | |
// hy = 1 + Math.floor(Math.random()*boardHeight); | |
// if (!(hx in livingHexes)) { | |
// livingHexes[hx] = {}; | |
// } | |
// livingHexes[hx][hy] = true; | |
// } | |
// Add a areas with a higher concentration | |
for (j=1; j < 12; ++j) { | |
for (k=1; k < (10-j) || k == 1; ++k) { | |
for (i=0; i < j*j*4+5; ++i) { | |
var hx = Math.floor(boardWidth/16*(j*1.5) + Math.random()*boardWidth/16), | |
hy = Math.floor(boardHeight/16*(j*1.5+(k*3-2)-1) + j + Math.random()*boardHeight/16); | |
if (!(hx in livingHexes)) { | |
livingHexes[hx] = {}; | |
} | |
livingHexes[hx][hy] = true; | |
} | |
} | |
} | |
hexHeight = Math.sin(hexagonAngle) * sideLength; | |
hexRadius = Math.cos(hexagonAngle) * sideLength; | |
hexRectangleHeight = sideLength + 2 * hexHeight; | |
hexRectangleWidth = 2 * hexRadius; | |
if (canvas.getContext){ | |
var ctx = canvas.getContext('2d'); | |
ctx.fillStyle = fillColorActive; | |
ctx.strokeStyle = strokeColor; | |
ctx.lineWidth = 1; | |
ctx.lasthexX = 0; | |
ctx.lasthexY = 0; | |
drawBoard(ctx, boardWidth, boardHeight); | |
drawAlive(ctx, livingHexes); | |
canvas.addEventListener("mousedown", clickHex, false); | |
canvas.addEventListener("mousemove", handleMouseMove); | |
} | |
function isInt(n) | |
{ | |
return n == parseInt(n); | |
} | |
function isOdd(n) | |
{ | |
return isInt(n) && (n % 2 == 0); | |
} | |
// FIXME: Conway uses the Moore neighborhood: 8 cels around, not 4. See http://en.wikipedia.org/wiki/Moore_neighborhood | |
// FIXME: Implement Chebyshev distance: max steps per index (x,y) = 1. A hexgrid has 3 indizes (u,v,w), only two of whom are independent. | |
// To be similar to Moore neighborhood (two complementary axes with fields), use sum(abs(i) for i in [u,v,w]) <= 2. | |
function hexesAround(hx, hy) { | |
// returns 6 arrays with 2 numbers each: hx and hy. | |
var arr = []; | |
// first the easy part: x + 1 and x - 1 | |
arr[arr.length] = [parseInt(hx) + 1, parseInt(hy)] | |
arr[arr.length] = [parseInt(hx) - 1, parseInt(hy)] | |
// next is simple, too: y + 1 and y - 1 | |
arr[arr.length] = [parseInt(hx), parseInt(hy) + 1] | |
arr[arr.length] = [parseInt(hx), parseInt(hy) - 1] | |
// now it gets nasty: Whether to use x+1 or x-1 depends on | |
// whether y is odd or even | |
if (isOdd(hy)) { | |
arr[arr.length] = [parseInt(hx) - 1, parseInt(hy) + 1] | |
arr[arr.length] = [parseInt(hx) - 1, parseInt(hy) - 1] | |
} else { | |
arr[arr.length] = [parseInt(hx) + 1, parseInt(hy) + 1] | |
arr[arr.length] = [parseInt(hx) + 1, parseInt(hy) - 1] | |
} | |
// TODO: now for the second level fields | |
// alert("around: " + JSON.stringify(arr) + "hx, hy: " + JSON.stringify(hx) + " " + JSON.stringify(hy)); | |
return arr; | |
} | |
function toggleLivingHex(livingHexes, hexX, hexY) { | |
if (!(hexX in livingHexes)) { | |
livingHexes[hexX] = {}; | |
} | |
if (hexY in livingHexes[hexX]){ | |
if (livingHexes[hexX][hexY] == true) { | |
livingHexes[hexX][hexY] = false; | |
} | |
else { | |
livingHexes[hexX][hexY] = true; | |
} | |
} else { | |
livingHexes[hexX][hexY] = true; | |
} | |
} | |
function clickHex(event) { | |
var x = event.x, | |
y = event.y, | |
rect = canvas.getBoundingClientRect(); | |
var canvasX = event.clientX - rect.left, | |
canvasY = event.clientY - rect.top; | |
hexY = screenXtohexX(canvasY, hexHeight, sideLength); | |
hexX = screenYtohexY(canvasX, hexY, hexRectangleWidth, hexRadius); | |
// toggleLivingHex(livingHexes, hexX, hexY); | |
var around = hexesAround(hexX, hexY), | |
ax, | |
ay; | |
for (var aroundidx in around) { | |
var ax = around[aroundidx][0], | |
ay = around[aroundidx][1]; | |
toggleLivingHex(livingHexes, ax, ay); | |
// alert("livingHexes: " + JSON.stringify(livingHexes) + "hx, hy, ax, ay: " + JSON.stringify(hx) + " " + JSON.stringify(hy) + " " + JSON.stringify(ax) + " " + JSON.stringify(ay) + " aroundMe: " + aroundMe) | |
} | |
// alert("clicked hex X: " + hexX + ", hex Y: " + hexY) | |
if (clickCount >= clickCountToTriggerConway - 1) { | |
lifeOfConway(livingHexes); | |
clickCount = 0; | |
} else { | |
clickCount += 1; | |
} | |
drawAlive(ctx, livingHexes); | |
} | |
function handleMouseMove(eventInfo) { | |
var x, | |
y, | |
hexX, | |
hexY, | |
screenX, | |
screenY, | |
rect; | |
rect = canvas.getBoundingClientRect(); | |
x = eventInfo.clientX - rect.left; | |
y = eventInfo.clientY - rect.top; | |
hexY = screenXtohexX(y, hexHeight, sideLength); | |
hexX = screenYtohexY(x, hexY, hexRectangleWidth, hexRadius); | |
screenX = hexXtoscreenX(hexX, hexY, hexRectangleWidth, hexRadius); | |
screenY = hexYtoscreenY(hexY, hexHeight, sideLength); | |
// Check if the mouse's coords are on the board | |
if(hexX >= 0 && hexX < boardWidth) { | |
if (hexY != ctx.lasthexY || hexX != ctx.lasthexX) { | |
if(hexY >= 0 && hexY < boardHeight) { | |
lastscreenX = hexXtoscreenX(ctx.lasthexX, ctx.lasthexY, hexRectangleWidth, hexRadius); | |
lastscreenY = hexYtoscreenY(ctx.lasthexY, hexHeight, sideLength); | |
ctx.fillStyle = fillColorInactive; | |
drawHexagon(ctx, lastscreenX, lastscreenY, true); | |
drawAlive(ctx, livingHexes); | |
ctx.fillStyle = fillColorActive; | |
drawHexagon(ctx, screenX, screenY, true); | |
ctx.lasthexX = hexX; | |
ctx.lasthexY = hexY; | |
} | |
} | |
} | |
} | |
function clearBoard(canvasContext, width, height) { | |
canvasContext.clearRect(0, 0, width, height); | |
} | |
function drawBoard(canvasContext, width, height) { | |
var i, | |
j; | |
for(i = 0; i < width; ++i) { | |
for(j = 0; j < height; ++j) { | |
drawHexagon( | |
canvasContext, | |
hexXtoscreenX(i, j, hexRectangleWidth,hexRadius), | |
hexYtoscreenY(j, hexHeight, sideLength), | |
false | |
); | |
} | |
} | |
} | |
function drawAlive(canvasContext, livingHexes) { | |
var hx, hy; | |
for(var i in livingHexes) { | |
for(var j in livingHexes[i]) { | |
hx = hexXtoscreenX(i, j, hexRectangleWidth, hexRadius); | |
hy = hexYtoscreenY(j, hexHeight, sideLength); | |
if (livingHexes[i][j] == true){ | |
canvasContext.fillStyle = fillColorAlive; | |
} | |
else { | |
canvasContext.fillStyle = fillColorInactive; | |
} | |
drawHexagon(canvasContext, hx, hy, true); | |
} | |
} | |
} | |
function cleanupLiving(canvasContext, livingHexes) { | |
for(var hx in livingHexes) { | |
var newhx = {}; | |
for(var hy in livingHexes[hx]) { | |
if (livingHexes[hx][hy] == true) { | |
newhx[hy] = true; | |
} else { | |
canvasContext.fillStyle = fillColorInactive; | |
drawHexagon(canvasContext, hx, hy, true); | |
} | |
} | |
livingHexes[hx] = newhx; | |
} | |
} | |
function shouldItLive(livingHexes, hx, hy, conditionToLive) { | |
var aroundMe = 0; | |
var around = hexesAround(hx, hy); | |
for (var aroundidx in around) { | |
var ax = around[aroundidx][0], | |
ay = around[aroundidx][1]; | |
if (ax in livingHexes && ay in livingHexes[ax] && livingHexes[ax][ay] == true) { | |
aroundMe += 1; | |
} | |
// alert("livingHexes: " + JSON.stringify(livingHexes) + "hx, hy, ax, ay: " + JSON.stringify(hx) + " " + JSON.stringify(hy) + " " + JSON.stringify(ax) + " " + JSON.stringify(ay) + " aroundMe: " + aroundMe) | |
} | |
if (aroundMe in conditionToLive) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function lifeOfConway(livingHexes) { | |
var toRaise; | |
toRaise = {}; | |
for(var hx in livingHexes) { | |
for(var hy in livingHexes[hx]) { | |
// alert(JSON.stringify(aroundMe) + JSON.stringify(neededToLive)); | |
if (livingHexes[hx][hy] == true) { | |
var around = hexesAround(hx, hy); | |
for (var aroundidx in around) { | |
var ax = around[aroundidx][0], | |
ay = around[aroundidx][1]; | |
if (shouldItLive(livingHexes, ax, ay, neededToRise)) { | |
if (!(ax in toRaise)) { | |
toRaise[ax] = {}; | |
} | |
toRaise[ax][ay] = true; | |
// FIXME: this leads to monotonically rising memory consumption | |
} | |
} | |
} | |
} | |
} | |
var toKill; | |
toKill = {}; | |
for(var hx in livingHexes) { | |
for(var hy in livingHexes[hx]) { | |
// alert(JSON.stringify(aroundMe) + JSON.stringify(neededToLive)); | |
if (livingHexes[hx][hy] == true && !(shouldItLive(livingHexes, hx, hy, neededToLive))) { | |
if (!(hx in toKill)) { | |
toKill[hx] = {}; | |
} | |
toKill[hx][hy] = true; | |
// FIXME: this leads to monotonically rising memory consumption | |
} | |
} | |
} | |
for(var hx in toRaise) { | |
if (!(hx in livingHexes)) { | |
livingHexes[hx] = {}; | |
} | |
for(var hy in toRaise[hx]) { | |
livingHexes[hx][hy] = true; | |
} | |
} | |
// alert("to kill: " + JSON.stringify(toKill)) | |
for(var hx in toKill) { | |
for(var hy in toKill[hx]) { | |
livingHexes[hx][hy] = false; | |
} | |
} | |
} | |
function hexXtoscreenX(hexX, hexY, hexRectangleWidth, hexRadius) { | |
return hexX * hexRectangleWidth + ((hexY % 2) * hexRadius); | |
} | |
function hexYtoscreenY(hexY, hexHeight,sideLength) { | |
return hexY * (hexHeight + sideLength); | |
} | |
function screenXtohexX(screenY, hexHeight, sideLength) { | |
return Math.floor(screenY / (hexHeight + sideLength)); | |
} | |
function screenYtohexY(screenX, hexY, hexRectangleWidth, hexRadius) { | |
return Math.floor((screenX - (hexY % 2) * hexRadius) / hexRectangleWidth); | |
} | |
function drawHexagon(canvasContext, x, y, fill) { | |
var fill = fill || false; | |
canvasContext.beginPath(); | |
canvasContext.moveTo(x + hexRadius, y); | |
canvasContext.lineTo(x + hexRectangleWidth, y + hexHeight); | |
canvasContext.lineTo(x + hexRectangleWidth, y + hexHeight + sideLength); | |
canvasContext.lineTo(x + hexRadius, y + hexRectangleHeight); | |
canvasContext.lineTo(x, y + sideLength + hexHeight); | |
canvasContext.lineTo(x, y + hexHeight); | |
canvasContext.closePath(); | |
if(fill) { | |
canvasContext.fill(); | |
} else { | |
canvasContext.stroke(); | |
} | |
} | |
function renderLoop() {lifeOfConway(livingHexes); setTimeout(renderLoop, 100);} | |
function run() { | |
renderLoop(); | |
} | |
run(); | |
function regularCleanup() { | |
cleanupLiving(ctx, livingHexes); | |
drawBoard(ctx, boardWidth, boardHeight); | |
drawAlive(ctx, livingHexes); | |
} | |
setInterval(regularCleanup, 10000); | |
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" /> | |
<title>Canvas Hexagonal Map</title> | |
<style type="text/css"> | |
canvas { | |
border:0; | |
display:block; | |
margin:0 auto; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id="hexmap" width="660" height="624"></canvas> | |
<script src="hexagons.js"></script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment