Skip to content

Instantly share code, notes, and snippets.

@ArneBab
Forked from seeschloss/hexagons.js
Last active August 29, 2015 14:17
Show Gist options
  • Save ArneBab/b955df115ab04079019e to your computer and use it in GitHub Desktop.
Save ArneBab/b955df115ab04079019e to your computer and use it in GitHub Desktop.
8fb2e1d19eba11e40ffaa70425f1585aeef48fe5 0 iJwEAAEIAAYFAlUPNnMACgkQ3M8NswvBBUixLAP/dujl7lJDpdoR97e52NCmANPaNb3cXCVwoZ96RXyFKXMj4sFp/cN321AoVBcThfHTco2NV02rTRQD9ZzrWIlTC412qezijC8hqBle9w9G1w5JWCIaLUX18vz/MECLj5Y/UQU7mvh8Dd7okfJxvDIYKzw1A6IQxd9hB31SS1xgbbA=
b618240f50921f9c8c0da00d55284c740d01d376 6-neighbor-hexfield-almost-done
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);
<!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