Skip to content

Instantly share code, notes, and snippets.

@bfeitknecht
Last active March 24, 2026 15:27
Show Gist options
  • Select an option

  • Save bfeitknecht/b4a7b46efa8b99ba9fd4920a1480ce2b to your computer and use it in GitHub Desktop.

Select an option

Save bfeitknecht/b4a7b46efa8b99ba9fd4920a1480ce2b to your computer and use it in GitHub Desktop.
SVG runs GOL with JS

SVG runs GOL with JS

Conway's Game of Life in SVG

Apparently, scalable vector graphics files can run JavaScript via <script> tags. It probably runs Doom too but I've never implemented Conway's Game of Life so that's what you get instead. You need to download and open in your own browser to see it in action due to security settings from GitHub disabling these tags to run.

curl -LO "https://gist.githubusercontent.com/bfeitknecht/b4a7b46efa8b99ba9fd4920a1480ce2b/raw/bb13b22e81468505a37098bef2c95e3bb7959b0a/gol.svg"
open -a Safari.app gol.svg
Display the source blob
Display the rendered blob
Raw
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 720 392"
width="100%"
height="100%"
preserveAspectRatio="xMidYMid meet"
style="cursor:crosshair"
>
<rect width="100%" height="100%" fill="#000" rx="6" />
<foreignObject x="0" y="0" width="720" height="32">
<div
xmlns="http://www.w3.org/1999/xhtml"
style="display:flex; align-items:center; height:32px; padding:0 12px; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif; color:#fff; box-sizing:border-box;"
>
<span style="font-weight:bold; font-size:14px;">game of life</span>
<span style="flex:1" />
<span id="gen" style="font-size:11px; margin-right:16px;">generation 0</span>
<span id="pop" style="font-size:11px; margin-right:16px;">population 0</span>
<span id="clear" style="font-size:11px; cursor:pointer; margin-right:16px;">clear</span>
<span id="rand" style="font-size:11px; cursor:pointer; margin-right:16px;">randomize</span>
<span
id="step"
style="font-size:11px; cursor:pointer; margin-right:16px; display:none;"
>step</span>
<span id="ctrl" style="font-size:11px; cursor:pointer;">pause</span>
</div>
</foreignObject>
<g id="board" transform="translate(0,32)">
<defs>
<pattern id="g" width="12" height="12" patternUnits="userSpaceOnUse">
<rect width="12" height="12" fill="#000" stroke="#111" stroke-width="0.5" />
</pattern>
</defs>
<rect width="720" height="360" fill="url(#g)" />
</g>
<script type="text/javascript">
// <![CDATA[
(function() {
var COLS = 60, ROWS = 30, CELL = 12, TOP = 32;
var FPS = 10;
var ALIVE = '#fff', DEAD = '#000';
var cur = new Uint8Array(COLS * ROWS);
var nxt = new Uint8Array(COLS * ROWS);
var generation = 0;
for (var i = 0; i < cur.length; i++) cur[i] = Math.random() < 0.15 ? 1 : 0;
var NS = 'http://www.w3.org/2000/svg';
var board = document.getElementById('board');
var genLabel = document.getElementById('gen');
var popLabel = document.getElementById('pop');
var rects = new Array(COLS * ROWS);
for (var r = 0; r < ROWS; r++) {
for (var c = 0; c < COLS; c++) {
var el = document.createElementNS(NS, 'rect');
el.setAttribute('x', c * CELL + 0.5);
el.setAttribute('y', r * CELL + 0.5);
el.setAttribute('width', CELL - 0.5);
el.setAttribute('height', CELL - 0.5);
el.setAttribute('fill', cur[r * COLS + c] ? ALIVE : DEAD);
board.appendChild(el);
rects[r * COLS + c] = el;
}
}
function neighbors(c, r) {
var n = 0;
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
n += cur[((r + dr + ROWS) % ROWS) * COLS + ((c + dc + COLS) % COLS)];
}
}
return n;
}
function step() {
for (var r = 0; r < ROWS; r++) {
for (var c = 0; c < COLS; c++) {
var n = neighbors(c, r), k = r * COLS + c;
nxt[k] = cur[k] ? ((n === 2 || n === 3) ? 1 : 0) : (n === 3 ? 1 : 0);
}
}
var t = cur; cur = nxt; nxt = t;
generation++;
}
function render() {
for (var i = 0; i < cur.length; i++) {
var color = cur[i] ? ALIVE : DEAD;
if (rects[i].getAttribute('fill') !== color) rects[i].setAttribute('fill', color);
}
var population = 0;
for (var j = 0; j < cur.length; j++) population += cur[j];
genLabel.textContent = 'generation ' + generation;
popLabel.textContent = 'population ' + population;
}
function cellAt(evt) {
var svg = board.ownerSVGElement;
var pt = svg.createSVGPoint();
var src = evt.touches ? evt.touches[0] : evt;
pt.x = src.clientX; pt.y = src.clientY;
var ctm = svg.getScreenCTM();
if (!ctm) return null;
var loc = pt.matrixTransform(ctm.inverse());
var c = Math.floor(loc.x / CELL), r = Math.floor((loc.y - TOP) / CELL);
if (r < 0) return null;
if (c < 0 || c >= COLS || r < 0 || r >= ROWS) return null;
return [c, r];
}
function spawn(evt) {
var p = cellAt(evt);
if (!p) return;
cur[p[1] * COLS + p[0]] = 1;
render();
}
var dragging = false;
var svg = board.ownerSVGElement;
svg.addEventListener('mousedown', function(e) { dragging = true; spawn(e); });
svg.addEventListener('mousemove', function(e) { if (dragging) spawn(e); });
svg.addEventListener('mouseup', function() { dragging = false; });
svg.addEventListener('mouseleave', function() { dragging = false; });
svg.addEventListener('touchstart', function(e) { e.preventDefault(); dragging = true; spawn(e); }, {passive:false});
svg.addEventListener('touchmove', function(e) { e.preventDefault(); if (dragging) spawn(e); }, {passive:false});
svg.addEventListener('touchend', function() { dragging = false; });
var paused = false;
var ctrl = document.getElementById('ctrl');
var stepBtn = document.getElementById('step');
var clearBtn = document.getElementById('clear');
var randBtn = document.getElementById('rand');
ctrl.addEventListener('click', function(e) {
e.stopPropagation();
paused = !paused;
ctrl.textContent = paused ? 'play' : 'pause';
stepBtn.style.display = paused ? '' : 'none';
});
stepBtn.addEventListener('click', function(e) {
e.stopPropagation();
step();
render();
});
clearBtn.addEventListener('click', function(e) {
e.stopPropagation();
for (var i = 0; i < cur.length; i++) cur[i] = 0;
generation = 0;
render();
});
randBtn.addEventListener('click', function(e) {
e.stopPropagation();
for (var i = 0; i < cur.length; i++) cur[i] = Math.random() < 0.15 ? 1 : 0;
generation = 0;
render();
});
setInterval(function() {
if (!paused) { step(); render(); }
}, 1000 / FPS);
render();
})();
// ]]>
</script>
</svg>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment