about summary refs log blame commit diff stats
path: root/html/life.html
blob: 7d93a0b6d8e20247f6999ed78f6005107f1cee4c (plain) (tree)
1
2
3
4
5
6
7






                                                                          







                                    
                
                                    










                                            


                            
                     

                                                 
         
                      












                                                 



            

































                                                                                     
































































































































































































































                                                                                                         









                                                                                          





































































                                                                                                          






















                                                                       


                                                             




                                                                                    







































































                                                                                                
<!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>
        * {
            font-size: 1em;
        }
        body {
            margin: 0;
            padding: 0;
            background-color: beige;
        }
        canvas {
            border: beige 1px solid;
        }
        #controls {
            margin-top: 10px;
        }
        #controls button, #controls select {
            margin-right: 10px;
        }
        button {
            border: none;
            padding: 0.25em 1em;
        }
        button:hover {
            cursor: pointer;
        }
        .playButton {
            background-color: rgb(116, 255, 239);
            color: black;
        }
        .pauseButton {
            background-color: rgb(255, 105, 250);
            color: black;
        }
        #contentContainer {
            padding: 2em 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            text-align: left;
        }
        canvas {
            margin-bottom: 3em;
        }
    </style>
</head>
<body>
    <div id="contentContainer">
        <canvas id="gameCanvas" width="400" height="400"></canvas>
        <form id="controls">
            <button id="playButton" class="playButton" type="button">Play</button>
            <button id="pauseButton" class="pauseButton" type="button">Pause</button>
            <button id="clearButton" type="button">Clear</button>
            <label for="speedSlider">Speed:</label>
            <input type="range" id="speedSlider" min="1" max="1000" value="500">
            <span id="speedValue">500</span> ms
            <br>
            <br>
            <label for="patternSelect">Pattern:</label>
            <select id="patternSelect">
                <option value="random">Random</option>
                <option value="glider">Glider</option>
                <option value="lwss">Lightweight Spaceship</option>
                <option value="blinker">Blinker</option>
                <option value="toad">Toad</option>
                <option value="beacon">Beacon</option>
                <option value="block">Block</option>
                <option value="beehive">Beehive</option>
                <option value="loaf">Loaf</option>
                <option value="boat">Boat</option>
                <option value="tub">Tub</option>
                <option value="pulsar">Pulsar</option>
                <option value="pentadecathlon">Pentadecathlon</option>
                <option value="gosper_glider_gun">Gosper Glider Gun</option>
                <option value="diehard">Diehard</option>
                <option value="acorn">Acorn</option>
                <option value="r_pentomino">R-pentomino</option>
                <option value="blinker_pair">Blinker-pair</option>
            </select>
        </form>
        </form>
    </div>
    <script>
        class GameOfLife {
            constructor(rows, cols) {
                this.rows = rows;
                this.cols = cols;
                this.grid = Array.from({ length: rows }, () => Array(cols).fill(0));
            }

            countNeighbors(x, y) {
                const neighborOffsets = [
                    [-1, -1], [-1, 0], [-1, 1],
                    [0, -1],           [0, 1],
                    [1, -1],  [1, 0],  [1, 1]
                ];

                return neighborOffsets.reduce((count, [dx, dy]) => {
                    const nx = x + dx;
                    const ny = y + dy;
                    if (nx >= 0 && ny >= 0 && nx < this.rows && ny < this.cols && this.grid[nx][ny]) {
                        return count + 1;
                    }
                    return count;
                }, 0);
            }

            step() {
                const newGrid = this.grid.map((row, x) =>
                    row.map((cell, y) => {
                        const neighbors = this.countNeighbors(x, y);
                        return neighbors === 3 || (neighbors === 2 && cell) ? 1 : 0;
                    })
                );
                this.grid = newGrid;
            }

            clear() {
                this.grid = Array.from({ length: this.rows }, () => Array(this.cols).fill(0));
            }

            toggleCell(x, y) {
                if (x >= 0 && y >= 0 && x < this.cols && y < this.rows) {
                    this.grid[x][y] = this.grid[x][y] ? 0 : 1;
                }
            }

            setPattern(pattern) {
                this.clear();
                switch(pattern) {
                    case 'glider':
                        this.setGlider();
                        break;
                    case 'lwss':
                        this.setLWSS();
                        break;
                    case 'blinker':
                        this.setBlinker();
                        break;
                    case 'toad':
                        this.setToad();
                        break;
                    case 'beacon':
                        this.setBeacon();
                        break;
                    case 'block':
                        this.setBlock();
                        break;
                    case 'beehive':
                        this.setBeehive();
                        break;
                    case 'loaf':
                        this.setLoaf();
                        break;
                    case 'boat':
                        this.setBoat();
                        break;
                    case 'tub':
                        this.setTub();
                        break;
                    case 'pulsar':
                        this.setPulsar();
                        break;
                    case 'pentadecathlon':
                        this.setPentadecathlon();
                        break;
                    case 'gosper_glider_gun':
                        this.setGosperGliderGun();
                        break;
                    case 'diehard':
                        this.setDiehard();
                        break;
                    case 'acorn':
                        this.setAcorn();
                        break;
                    case 'r_pentomino':
                        this.setRPentomino();
                        break;
                    case 'blinker_pair':
                        this.setBlinkerPair();
                        break;
                    case 'random':
                        this.setRandom();
                        break;
                }
            }

            setGlider() {
                const pattern = [
                    [0, 1, 0],
                    [0, 0, 1],
                    [1, 1, 1]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setLWSS() {
                const pattern = [
                    [0, 1, 1, 1, 1],
                    [1, 0, 0, 0, 1],
                    [0, 0, 0, 0, 1],
                    [1, 0, 0, 1, 0]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setBlinker() {
                const pattern = [
                    [1, 1, 1]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setToad() {
                const pattern = [
                    [0, 1, 1, 1],
                    [1, 1, 1, 0]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setBeacon() {
                const pattern = [
                    [1, 1, 0, 0],
                    [1, 1, 0, 0],
                    [0, 0, 1, 1],
                    [0, 0, 1, 1]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setBlock() {
                const pattern = [
                    [1, 1],
                    [1, 1]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setBeehive() {
                const pattern = [
                    [0, 1, 1, 0],
                    [1, 0, 0, 1],
                    [0, 1, 1, 0]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setLoaf() {
                const pattern = [
                    [0, 1, 1, 0],
                    [1, 0, 0, 1],
                    [0, 1, 0, 1],
                    [0, 0, 1, 0]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setBoat() {
                const pattern = [
                    [1, 1, 0],
                    [1, 0, 1],
                    [0, 1, 0]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setTub() {
                const pattern = [
                    [0, 1, 0],
                    [1, 0, 1],
                    [0, 1, 0]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setPulsar() {
                const pattern = [
                    [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1],
                    [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1],
                    [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1],
                    [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0],
                    [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1],
                    [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1],
                    [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2) - 6, Math.floor(this.cols / 2) - 6);
            }

            setPentadecathlon() {
                const pattern = [
                    [0, 1, 0, 0, 0, 1, 0],
                    [1, 0, 1, 1, 1, 0, 1],
                    [0, 1, 0, 0, 0, 1, 0]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2) - 3);
            }

            setGosperGliderGun() {
                const pattern = [
                [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],
                [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0],
                [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1],
                [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1],
                [1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                [1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0],
                [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],
                [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ];
                this.placePattern(pattern, Math.floor(this.rows / 2) - 9, Math.floor(this.cols / 2) - 17);
            }

            setDiehard() {
                const pattern = [
                    [0, 0, 0, 0, 0, 0, 1],
                    [1, 1, 0, 0, 0, 0, 0],
                    [0, 1, 0, 0, 0, 1, 1],
                    [0, 0, 1, 1, 0, 0, 0]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2) - 3);
            }

            setAcorn() {
                const pattern = [
                    [0, 1, 0, 0, 0, 0, 0],
                    [0, 0, 0, 1, 0, 0, 0],
                    [1, 1, 0, 0, 1, 1, 1]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2) - 3);
            }

            setRPentomino() {
                const pattern = [
                    [0, 1, 1],
                    [1, 1, 0],
                    [0, 1, 0]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2));
            }

            setBlinkerPair() {
                const pattern = [
                    [1, 1, 1],
                    [0, 0, 0],
                    [1, 1, 1]
                ];
                this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2) - 1);
            }

            setRandom() {
                for (let x = 0; x < this.rows; x++) {
                    for (let y = 0; y < this.cols; y++) {
                        this.grid[x][y] = Math.random() < 0.3 ? 1 : 0;
                    }
                }
            }

            placePattern(pattern, offsetX, offsetY) {
                for (let x = 0; x < pattern.length; x++) {
                    for (let y = 0; y < pattern[x].length; y++) {
                        if (offsetX + x < this.rows && offsetY + y < this.cols) {
                            this.grid[offsetX + x][offsetY + y] = pattern[x][y];
                        }
                    }
                }
            }
        }

        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const cellSize = 20;
        const rows = Math.floor(canvas.height / cellSize);
        const cols = Math.floor(canvas.width / cellSize);

        const game = new GameOfLife(rows, cols);
        let speed = 500;
        let lastRenderTime = 0;
        let isPlaying = false;

        canvas.addEventListener('mousemove', handleMouseMove);
        canvas.addEventListener('mouseout', handleMouseOut);

        let hoveredCell = null; // stores what cell is hovered over

        // Find the cell being hovered hover and and redraw the grid
        function handleMouseMove(event) {
            const rect = canvas.getBoundingClientRect();
            const x = event.clientX - rect.left;
            const y = event.clientY - rect.top;
            const gridX = Math.floor(x / cellSize);
            const gridY = Math.floor(y / cellSize);
            hoveredCell = { x: gridX, y: gridY };
            drawGrid(hoveredCell); // Redraw grid with highlighted cell
        }

        // remove highlight when mouse leaves the cell
        function handleMouseOut() {
            hoveredCell = null;
            drawGrid(); 
        }

        function drawGrid(hoveredCell = null) {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            for (let x = 0; x < game.rows; x++) {
                for (let y = 0; y < game.cols; y++) {
                    if (hoveredCell && hoveredCell.x === y && hoveredCell.y === x) {
                        ctx.fillStyle = '#74FFEF';
                    } else {
                        ctx.fillStyle = game.grid[x][y] ? '#2d2d2d' : 'beige';
                    }
                    ctx.fillRect(y * cellSize, x * cellSize, cellSize, cellSize);
                    ctx.strokeStyle = 'lightgrey';
                    ctx.strokeRect(y * cellSize, x * cellSize, cellSize, cellSize);
                }
            }
        }

        function simulate(currentTime) {
            if (!isPlaying) {
                requestAnimationFrame(simulate);
                return;
            }

            if (currentTime - lastRenderTime >= speed) {
                game.step();
                drawGrid();
                lastRenderTime = currentTime;
            }
            requestAnimationFrame(simulate);
        }

        function getMousePosition(event) {
            const rect = canvas.getBoundingClientRect();
            const x = Math.floor((event.clientY - rect.top) / cellSize);
            const y = Math.floor((event.clientX - rect.left) / cellSize);
            return { x, y };
        }

        function toggleCell(event) {
            const { x, y } = getMousePosition(event);
            game.toggleCell(x, y);
            drawGrid();
        }

        document.getElementById('speedSlider').addEventListener('input', (event) => {
            speed = event.target.value;
            document.getElementById('speedValue').textContent = speed;
        });

        document.getElementById('clearButton').addEventListener('click', () => {
            game.clear();
            drawGrid();
        });

        document.getElementById('playButton').addEventListener('click', function() {
            isPlaying = true;
            this.classList.add('playButton');
            document.getElementById('pauseButton').classList.remove('pauseButton');
        });

        document.getElementById('pauseButton').addEventListener('click', function() {           
            isPlaying = false;
            this.classList.add('pauseButton');
            document.getElementById('playButton').classList.remove('playButton');
        });

        document.getElementById('patternSelect').addEventListener('change', (event) => {
            game.setPattern(event.target.value);
            drawGrid();
        });

        canvas.addEventListener('click', toggleCell);
        canvas.addEventListener('contextmenu', (event) => {
            event.preventDefault();
            toggleCell(event);
        });

        drawGrid();
        requestAnimationFrame(simulate);
    </script>
</body>
</html>