about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--js/life/index.html31
-rw-r--r--js/life/life.js87
2 files changed, 118 insertions, 0 deletions
diff --git a/js/life/index.html b/js/life/index.html
new file mode 100644
index 0000000..d197ad2
--- /dev/null
+++ b/js/life/index.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Conway's Game of Life</title>
+  <style>
+    body, html {
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+    }
+    canvas {
+      display: block;
+    }
+    #controls {
+      position: absolute;
+      top: 10px;
+      right: 10px;
+      z-index: 10;
+    }
+  </style>
+</head>
+<body>
+  <div id="controls">
+    <button id="startPauseButton">Start</button>
+  </div>
+  <canvas id="gameCanvas"></canvas>
+  <script src="life.js"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/js/life/life.js b/js/life/life.js
new file mode 100644
index 0000000..38d0972
--- /dev/null
+++ b/js/life/life.js
@@ -0,0 +1,87 @@
+'use strict'
+
+const b = {
+    curry: function (fn) {
+        const curried = (...args) => {
+            if (args.length >= fn.length)
+                return fn(...args)
+            else
+                return (...rest) => curried(...args, ...rest)
+        }
+        return curried
+    },
+    pipe: (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value),
+};
+
+const canvas = document.getElementById('gameCanvas');
+const ctx = canvas.getContext('2d');
+const startPauseButton = document.getElementById('startPauseButton');
+
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+const cellSize = 10;
+const rows = Math.floor(canvas.height / cellSize);
+const cols = Math.floor(canvas.width / cellSize);
+
+let grid = initializeGrid();
+let animationId = null;
+
+function initializeGrid() {
+  return Array.from({ length: rows }, () => Array.from({ length: cols }, () => Math.random() > 0.8 ? 1 : 0));
+}
+
+const drawCell = b.curry((ctx, cellSize, x, y, cell) => {
+  ctx.fillStyle = cell ? 'black' : 'white';
+  ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
+  ctx.strokeStyle = 'gray';
+  ctx.strokeRect(x * cellSize, y * cellSize, cellSize, cellSize);
+});
+
+const drawGrid = (ctx, cellSize, grid) => {
+  ctx.clearRect(0, 0, canvas.width, canvas.height);
+  grid.forEach((row, y) => row.forEach((cell, x) => drawCell(ctx, cellSize, x, y, cell)));
+};
+
+const getNeighbors = (grid, x, y) => [
+  grid[y - 1]?.[x - 1], grid[y - 1]?.[x], grid[y - 1]?.[x + 1],
+  grid[y]?.[x - 1], /* cell */ grid[y]?.[x + 1],
+  grid[y + 1]?.[x - 1], grid[y + 1]?.[x], grid[y + 1]?.[x + 1]
+].filter(Boolean).reduce((acc, val) => acc + val, 0);
+
+const nextCellState = b.curry((cell, neighbors) => (cell && neighbors === 2) || neighbors === 3 ? 1 : 0);
+
+const nextGridState = (grid) => grid.map((row, y) => row.map((cell, x) => nextCellState(cell, getNeighbors(grid, x, y))));
+
+const animate = () => {
+  drawGrid(ctx, cellSize, grid);
+  grid = nextGridState(grid);
+  animationId = requestAnimationFrame(animate);
+};
+
+const toggleAnimation = () => {
+  if (animationId) {
+    cancelAnimationFrame(animationId);
+    animationId = null;
+    startPauseButton.textContent = 'Start';
+  } else {
+    animate();
+    startPauseButton.textContent = 'Pause';
+  }
+};
+
+startPauseButton.addEventListener('click', toggleAnimation);
+
+canvas.addEventListener('click', (event) => {
+  const rect = canvas.getBoundingClientRect();
+  const x = Math.floor((event.clientX - rect.left) / cellSize);
+  const y = Math.floor((event.clientY - rect.top) / cellSize);
+  grid[y][x] = grid[y][x] ? 0 : 1;
+  drawGrid(ctx, cellSize, grid);
+});
+
+const startGame = b.pipe(initializeGrid, (grid) => {
+  drawGrid(ctx, cellSize, grid);
+  return grid;
+});
+grid = startGame();
\ No newline at end of file