diff options
-rw-r--r-- | js/life/index.html | 31 | ||||
-rw-r--r-- | js/life/life.js | 87 |
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 |