Pente experiment

https://svonberg.org/wp-content/uploads/2025/11/pente13.html

<!DOCTYPE html>
<html lang=”en”>
<head>
    <meta charset=”UTF-8″>
    <meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
    <title>Pente Master</title>
    <style>
        :root {
            –bg-color: #f0f4f8;
            –board-color: #e3c076;
            –board-line: #4b3621;
            –p1-color: #1a1a1a;    /* Black */
            –p2-color: #f2f2f2;    /* White */
            –accent: #3b82f6;
            –text-main: #1f2937;
        }

        * { box-sizing: border-box; margin: 0; padding: 0; user-select: none; -webkit-user-select: none; }

        body {
            font-family: -apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto, Helvetica, Arial, sans-serif;
            background-color: var(–bg-color);
            color: var(–text-main);
            display: flex;
            flex-direction: column;
            align-items: center;
            min-height: 100vh;
            overflow: hidden;
        }

        header {
            width: 100%;
            padding: 1rem;
            background: white;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            text-align: center;
            z-index: 10;
            display: flex;
            flex-direction: column;
            gap: 10px;
            align-items: center;
            max-width: 900px;
        }

        h1 { font-size: 1.5rem; font-weight: 700; color: var(–accent); }

        .controls {
            display: flex;
            gap: 0.5rem;
            align-items: center;
            flex-wrap: wrap;
            justify-content: center;
        }

        select, button {
            padding: 0.5rem 0.8rem;
            border: 1px solid #e5e7eb;
            border-radius: 0.5rem;
            background: white;
            font-size: 0.9rem;
            cursor: pointer;
            transition: all 0.2s;
        }

        button:hover, select:hover { border-color: var(–accent); color: var(–accent); }
        select:disabled { background: #f3f4f6; color: #9ca3af; cursor: not-allowed; border-color: #e5e7eb; }

        button.primary {
            background: var(–accent);
            color: white;
            border: none;
        }
        button.primary:hover { background: #2563eb; }

        /* Game Info Panel */
        .game-info {
            display: flex;
            width: 100%;
            max-width: 600px;
            justify-content: space-around;
            padding: 1rem;
            font-weight: 600;
        }

        .player-card {
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 0.5rem 1rem;
            border-radius: 0.5rem;
            background: white;
            min-width: 120px;
            border: 2px solid transparent;
            transition: all 0.3s;
        }

        .player-card.active {
            border-color: var(–accent);
            box-shadow: 0 4px 6px rgba(59, 130, 246, 0.1);
            transform: translateY(-2px);
        }

        .stone-indicator {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            box-shadow: 0 2px 4px rgba(0,0,0,0.3);
            margin-bottom: 4px;
            border: 1px solid rgba(0,0,0,0.1);
        }
        .p1-stone { background: var(–p1-color); }
        .p2-stone { background: var(–p2-color); }

        .captures { font-size: 0.8rem; color: #6b7280; margin-top: 4px; }
        .captures span { font-weight: bold; color: var(–text-main); font-size: 1rem; }

        /* Game Area */
        #game-container {
            flex: 1;
            display: flex;
            justify-content: center;
            align-items: center;
            width: 100%;
            padding: 10px;
            position: relative;
        }

        canvas {
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
            border-radius: 4px;
            cursor: crosshair;
            background-color: var(–board-color);
        }

        /* Modal & Toast */
        #modal-overlay {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.5); display: none;
            justify-content: center; align-items: center; z-index: 50;
            backdrop-filter: blur(2px);
        }
        .modal {
            background: white; padding: 2rem; border-radius: 1rem;
            text-align: center; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
            max-width: 90%; width: 400px;
        }
        .modal h2 { font-size: 2rem; margin-bottom: 1rem; color: var(–text-main); }
        .modal p { margin-bottom: 1.5rem; color: #4b5563; }

        #toast {
            position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
            background: #374151; color: white; padding: 10px 20px;
            border-radius: 20px; font-size: 0.9rem; opacity: 0;
            transition: opacity 0.3s; pointer-events: none; z-index: 100;
        }

        @media (min-width: 650px) {
            header { flex-direction: row; justify-content: space-between; padding: 1rem 2rem; }
            .controls { justify-content: flex-end; }
        }
    </style>
</head>
<body>

<header>
    <h1>Pente</h1>
    <div class=”controls”>
        <select id=”grid-size” title=”Board Size”>
            <option value=”9″>9×9 (Small)</option>
            <option value=”13″>13×13 (Medium)</option>
            <option value=”19″ selected>19×19 (Standard)</option>
        </select>
       
        <select id=”game-mode” title=”Game Mode”>
            <option value=”hvh”>Human vs Human</option>
            <option value=”hvai” selected>Human vs AI</option>
            <option value=”aiva”>AI vs AI</option>
        </select>

        <select id=”play-as” title=”Play As (For Human vs AI)”>
            <option value=”1″>P1 (Black)</option>
            <option value=”2″>P2 (White)</option>
            <option value=”random”>Random</option>
        </select>

        <button class=”primary” id=”reset-btn”>New Game</button>
    </div>
</header>

<div class=”game-info”>
    <div class=”player-card active” id=”p1-card”>
        <div class=”stone-indicator p1-stone”></div>
        <div id=”p1-name”>Player 1</div>
        <div class=”captures”>Pairs: <span id=”p1-captures”>0</span>/5</div>
    </div>
    <div class=”player-card” id=”p2-card”>
        <div class=”stone-indicator p2-stone”></div>
        <div id=”p2-name”>CPU 1</div>
        <div class=”captures”>Pairs: <span id=”p2-captures”>0</span>/5</div>
    </div>
</div>

<div id=”game-container”>
    <canvas id=”board”></canvas>
</div>

<div id=”toast”>Notification</div>

<div id=”modal-overlay”>
    <div class=”modal”>
        <h2 id=”winner-title”>Wins!</h2>
        <p id=”win-reason”>Reason</p>
        <button class=”primary” id=”modal-reset”>Play Again</button>
    </div>
</div>

<script>
/**
* PENTE GAME LOGIC
*/

let BOARD_SIZE = 19;
const EMPTY = 0;
const P1 = 1; // Black
const P2 = 2; // White

// State
let board = [];
let currentPlayer = P1;
let gameActive = false;
let p1Captures = 0;
let p2Captures = 0;
let winningStones = [];
let lastMove = null;

// Configuration
let p1IsHuman = true;
let p2IsHuman = false;

// Canvas
const canvas = document.getElementById(‘board’);
const ctx = canvas.getContext(‘2d’);
let cellSize = 30;
let boardPadding = 30;

// DOM
const p1Card = document.getElementById(‘p1-card’);
const p2Card = document.getElementById(‘p2-card’);
const p1NameEl = document.getElementById(‘p1-name’);
const p2NameEl = document.getElementById(‘p2-name’);
const p1ScoreEl = document.getElementById(‘p1-captures’);
const p2ScoreEl = document.getElementById(‘p2-captures’);
const modeSelect = document.getElementById(‘game-mode’);
const gridSizeSelect = document.getElementById(‘grid-size’);
const playAsSelect = document.getElementById(‘play-as’);
const resetBtn = document.getElementById(‘reset-btn’);
const modalOverlay = document.getElementById(‘modal-overlay’);
const modalReset = document.getElementById(‘modal-reset’);
const toastEl = document.getElementById(‘toast’);

// AI Config
const AI_DELAY = 600;
let aiTimeout = null;

/* — INITIALIZATION — */

function initGame() {
    // 1. Setup Grid
    BOARD_SIZE = parseInt(gridSizeSelect.value);
    board = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(EMPTY));
   
    // 2. Setup Roles & Names
    const mode = modeSelect.value;
    let userPref = playAsSelect.value;

    // Handle Random preference
    if (mode === ‘hvai’ && userPref === ‘random’) {
        userPref = Math.random() < 0.5 ? ‘1’ : ‘2’;
        showToast(userPref === ‘1’ ? “Random: You are Player 1” : “Random: You are Player 2”);
    }

    if (mode === ‘hvh’) {
        p1IsHuman = true;
        p2IsHuman = true;
        p1NameEl.innerText = “Player 1”;
        p2NameEl.innerText = “Player 2”;
    } else if (mode === ‘aiva’) {
        p1IsHuman = false;
        p2IsHuman = false;
        p1NameEl.innerText = “CPU 1”;
        p2NameEl.innerText = “CPU 2”;
    } else { // hvai
        if (userPref === ‘1’) {
            p1IsHuman = true;
            p2IsHuman = false;
            p1NameEl.innerText = “Player 1”;
            p2NameEl.innerText = “CPU 2”;
        } else {
            p1IsHuman = false;
            p2IsHuman = true;
            p1NameEl.innerText = “CPU 1”;
            p2NameEl.innerText = “Player 2”;
        }
    }

    // 3. Reset State
    currentPlayer = P1;
    p1Captures = 0;
    p2Captures = 0;
    lastMove = null;
    winningStones = [];
    gameActive = true;
   
    updateScoreUI();
    toggleActiveCard();
    modalOverlay.style.display = ‘none’;
    clearTimeout(aiTimeout);

    resizeCanvas();

    // 4. Check if first player is AI
    checkAiTurn();
}

function checkAiTurn() {
    if (!gameActive) return;
   
    const isHumanTurn = (currentPlayer === P1 && p1IsHuman) || (currentPlayer === P2 && p2IsHuman);
   
    if (!isHumanTurn) {
        clearTimeout(aiTimeout);
        aiTimeout = setTimeout(aiTurn, AI_DELAY);
    }
}

function resizeCanvas() {
    const container = document.getElementById(‘game-container’);
    const size = Math.min(container.clientWidth, container.clientHeight);
    const dpr = window.devicePixelRatio || 1;
   
    const availableSize = size – 20;
    cellSize = Math.floor(availableSize / (BOARD_SIZE + 1));
    if (cellSize < 14) cellSize = 14;
   
    const canvasSize = cellSize * (BOARD_SIZE + 1);
   
    canvas.width = canvasSize * dpr;
    canvas.height = canvasSize * dpr;
    canvas.style.width = `${canvasSize}px`;
    canvas.style.height = `${canvasSize}px`;
   
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    boardPadding = cellSize;
   
    drawBoard();
}

/* — DRAWING — */

function drawBoard() {
    ctx.fillStyle = ‘#e3c076’;
    ctx.fillRect(0, 0, canvas.width, canvas.height); // Clear whole canvas

    // Grid
    ctx.strokeStyle = ‘#4b3621’;
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    for (let i = 0; i < BOARD_SIZE; i++) {
        const pos = boardPadding + i * cellSize;
        ctx.moveTo(pos, boardPadding);
        ctx.lineTo(pos, boardPadding + (BOARD_SIZE – 1) * cellSize);
        ctx.moveTo(boardPadding, pos);
        ctx.lineTo(boardPadding + (BOARD_SIZE – 1) * cellSize, pos);
    }
    ctx.stroke();

    // Stars
    const points = getStarPoints(BOARD_SIZE);
    ctx.fillStyle = ‘#4b3621’;
    for (let r of points) {
        for (let c of points) {
            const x = boardPadding + c * cellSize;
            const y = boardPadding + r * cellSize;
            ctx.beginPath();
            ctx.arc(x, y, cellSize * 0.12, 0, Math.PI * 2);
            ctx.fill();
        }
    }

    // Stones
    for (let r = 0; r < BOARD_SIZE; r++) {
        for (let c = 0; c < BOARD_SIZE; c++) {
            if (board[r][c] !== EMPTY) {
                drawStone(r, c, board[r][c]);
            }
        }
    }

    // Last Move
    if (lastMove) {
        const x = boardPadding + lastMove.c * cellSize;
        const y = boardPadding + lastMove.r * cellSize;
        ctx.fillStyle = ‘rgba(255, 0, 0, 0.5)’;
        ctx.beginPath();
        ctx.arc(x, y, cellSize * 0.15, 0, Math.PI * 2);
        ctx.fill();
    }

    // Win Line
    if (winningStones.length > 0) {
        ctx.strokeStyle = ‘rgba(50, 255, 50, 0.8)’;
        ctx.lineWidth = 4;
        ctx.lineCap = ’round’;
        ctx.beginPath();
        const start = winningStones[0];
        const end = winningStones[winningStones.length – 1];
        ctx.moveTo(boardPadding + start.c * cellSize, boardPadding + start.r * cellSize);
        ctx.lineTo(boardPadding + end.c * cellSize, boardPadding + end.r * cellSize);
        ctx.stroke();
    }
}

function getStarPoints(size) {
    if (size === 19) return [3, 9, 15];
    if (size === 13) return [3, 6, 9];
    if (size === 9) return [2, 4, 6];
    return [];
}

function drawStone(r, c, type) {
    const x = boardPadding + c * cellSize;
    const y = boardPadding + r * cellSize;
    const radius = cellSize * 0.42;

    ctx.beginPath();
    ctx.arc(x, y, radius, 0, Math.PI * 2);
   
    const grad = ctx.createRadialGradient(x – radius/3, y – radius/3, radius/10, x, y, radius);
    if (type === P1) {
        grad.addColorStop(0, ‘#555’);
        grad.addColorStop(1, ‘#000’);
    } else {
        grad.addColorStop(0, ‘#fff’);
        grad.addColorStop(1, ‘#d1d5db’);
    }
   
    ctx.fillStyle = grad;
    ctx.fill();
   
    ctx.shadowColor = ‘rgba(0,0,0,0.2)’;
    ctx.shadowBlur = 2;
    ctx.shadowOffsetX = 1;
    ctx.shadowOffsetY = 1;
    ctx.stroke();
    ctx.shadowColor = ‘transparent’;
}

/* — INPUT & LOGIC — */

canvas.addEventListener(‘mousedown’, handleInput);
canvas.addEventListener(‘touchstart’, (e) => {
    e.preventDefault();
    handleInput(e.touches[0]);
}, {passive: false});

function handleInput(e) {
    if (!gameActive) return;
   
    // Check if it is human turn
    const isHumanTurn = (currentPlayer === P1 && p1IsHuman) || (currentPlayer === P2 && p2IsHuman);
    if (!isHumanTurn) return;

    const rect = canvas.getBoundingClientRect();
    const dpr = window.devicePixelRatio || 1;
   
    const cssX = e.clientX – rect.left;
    const cssY = e.clientY – rect.top;
   
    // Convert to logical coordinates within canvas space
    const x = cssX * (canvas.width / rect.width) / dpr;
    const y = cssY * (canvas.height / rect.height) / dpr;

    const c = Math.round((x – boardPadding) / cellSize);
    const r = Math.round((y – boardPadding) / cellSize);

    if (isValidMove(r, c)) {
        makeMove(r, c);
    }
}

function isValidMove(r, c) {
    return r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && board[r][c] === EMPTY;
}

function makeMove(r, c) {
    board[r][c] = currentPlayer;
    lastMove = {r, c};
   
    // 1. Check Captures
    const captured = checkCaptures(r, c, currentPlayer);
    if (captured > 0) {
        if (currentPlayer === P1) p1Captures += captured;
        else p2Captures += captured;
        updateScoreUI();
        showToast(`${getName(currentPlayer)} captured ${captured} pair(s)!`);
       
        if ((currentPlayer === P1 && p1Captures >= 5) || (currentPlayer === P2 && p2Captures >= 5)) {
            endGame(currentPlayer, “5 Pairs Captured”);
            drawBoard();
            return;
        }
    }

    // 2. Check 5-in-a-row
    const winningLine = checkWin(r, c, currentPlayer);
    if (winningLine) {
        winningStones = winningLine;
        endGame(currentPlayer, “5 Stones in a Row”);
        drawBoard();
        return;
    }

    drawBoard();

    // Switch Turn
    currentPlayer = currentPlayer === P1 ? P2 : P1;
    toggleActiveCard();

    // Trigger Next
    checkAiTurn();
}

function getName(player) {
    return player === P1 ? p1NameEl.innerText : p2NameEl.innerText;
}

function toggleActiveCard() {
    if (currentPlayer === P1) {
        p1Card.classList.add(‘active’);
        p2Card.classList.remove(‘active’);
    } else {
        p1Card.classList.remove(‘active’);
        p2Card.classList.add(‘active’);
    }
}

function updateScoreUI() {
    p1ScoreEl.innerText = p1Captures;
    p2ScoreEl.innerText = p2Captures;
}

function showToast(msg) {
    toastEl.innerText = msg;
    toastEl.style.opacity = 1;
    setTimeout(() => { toastEl.style.opacity = 0; }, 2000);
}

function endGame(winner, reason) {
    gameActive = false;
    const winnerName = getName(winner);
    document.getElementById(‘winner-title’).innerText = `${winnerName} Wins!`;
    document.getElementById(‘win-reason’).innerText = reason;
    modalOverlay.style.display = ‘flex’;
}

/* — MECHANICS — */

const DIRECTIONS = [
    {dr: 0, dc: 1}, {dr: 1, dc: 0}, {dr: 1, dc: 1}, {dr: 1, dc: -1}
];

function checkCaptures(r, c, player) {
    const opponent = player === P1 ? P2 : P1;
    let totalCaptured = 0;

    for (let d of DIRECTIONS) {
        // Forward
        if (getCell(r + d.dr, c + d.dc) === opponent &&
            getCell(r + 2 * d.dr, c + 2 * d.dc) === opponent &&
            getCell(r + 3 * d.dr, c + 3 * d.dc) === player) {
            board[r + d.dr][c + d.dc] = EMPTY;
            board[r + 2 * d.dr][c + 2 * d.dc] = EMPTY;
            totalCaptured++;
        }
        // Backward
        if (getCell(r – d.dr, c – d.dc) === opponent &&
            getCell(r – 2 * d.dr, c – 2 * d.dc) === opponent &&
            getCell(r – 3 * d.dr, c – 3 * d.dc) === player) {
            board[r – d.dr][c – d.dc] = EMPTY;
            board[r – 2 * d.dr][c – 2 * d.dc] = EMPTY;
            totalCaptured++;
        }
    }
    return totalCaptured;
}

function checkWin(r, c, player) {
    for (let d of DIRECTIONS) {
        let stones = [{r,c}];
        let i = 1;
        while (getCell(r + i * d.dr, c + i * d.dc) === player) {
            stones.push({r: r + i * d.dr, c: c + i * d.dc}); i++;
        }
        let j = 1;
        while (getCell(r – j * d.dr, c – j * d.dc) === player) {
            stones.push({r: r – j * d.dr, c: c – j * d.dc}); j++;
        }
        if (stones.length >= 5) return stones;
    }
    return null;
}

function getCell(r, c) {
    if (r < 0 || r >= BOARD_SIZE || c < 0 || c >= BOARD_SIZE) return -1;
    return board[r][c];
}

/* — AI — */

// Scores
const S_WIN = 1000000;
const S_BLK_WIN = 500000;
const S_WIN_CAP = 200000;
const S_BLK_WIN_CAP = 150000;
const S_CAP = 10000;
const S_BLK_CAP = 8000;
const S_OPEN4 = 5000;
const S_OPEN3 = 1000;

function aiTurn() {
    if (!gameActive) return;
   
    const center = Math.floor(BOARD_SIZE / 2);
   
    // First move optimization
    let stones = 0;
    for(let r=0;r<BOARD_SIZE;r++) for(let c=0;c<BOARD_SIZE;c++) if(board[r][c]) stones++;
    if (stones === 0) { makeMove(center, center); return; }

    const moves = filterRelevantMoves();
    let bestMove = null;
    let maxScore = -Infinity;

    // If no relevant moves (rare), pick random empty
    if (moves.length === 0) {
        const all = getPossibleMoves();
        if (all.length > 0) makeMove(all[0].r, all[0].c);
        return;
    }

    for (let move of moves) {
        const score = evaluateMove(move.r, move.c, currentPlayer, center);
        const finalScore = score + Math.random() * 10; // Variation

        if (finalScore > maxScore) {
            maxScore = finalScore;
            bestMove = move;
        }
    }

    if (bestMove) makeMove(bestMove.r, bestMove.c);
}

function filterRelevantMoves() {
    const occupied = [];
    for(let r=0; r<BOARD_SIZE; r++) {
        for(let c=0; c<BOARD_SIZE; c++) {
            if(board[r][c] !== EMPTY) occupied.push({r,c});
        }
    }
   
    const relevantSet = new Set();
    const range = 2;

    for (let stone of occupied) {
        for (let dr = -range; dr <= range; dr++) {
            for (let dc = -range; dc <= range; dc++) {
                const nr = stone.r + dr;
                const nc = stone.c + dc;
                if (nr >= 0 && nr < BOARD_SIZE && nc >= 0 && nc < BOARD_SIZE && board[nr][nc] === EMPTY) {
                    relevantSet.add(`${nr},${nc}`);
                }
            }
        }
    }
    return Array.from(relevantSet).map(s => {
        const [r, c] = s.split(‘,’).map(Number);
        return {r, c};
    });
}

function getPossibleMoves() {
    let m = [];
    for (let r=0;r<BOARD_SIZE;r++) for (let c=0;c<BOARD_SIZE;c++) if (board[r][c]===EMPTY) m.push({r,c});
    return m;
}

function evaluateMove(r, c, me, center) {
    const opp = me === P1 ? P2 : P1;
    let score = 0;

    const dist = Math.abs(r – center) + Math.abs(c – center);
    score -= dist * 2;

    // Offensive
    board[r][c] = me;
    if (checkWin(r, c, me)) score += S_WIN;
    const caps = countCaptures(r, c, me);
    if (caps > 0) {
        score += S_CAP * caps;
        const myCaps = me === P1 ? p1Captures : p2Captures;
        if (myCaps + caps >= 5) score += S_WIN_CAP;
    }
    score += analyzePatterns(r, c, me);
    board[r][c] = EMPTY;
   
    // Defensive
    board[r][c] = opp;
    if (checkWin(r, c, opp)) score += S_BLK_WIN;
    const oppCaps = countCaptures(r, c, opp);
    if (oppCaps > 0) {
        score += S_BLK_CAP * oppCaps;
        const enCaps = opp === P1 ? p1Captures : p2Captures;
        if (enCaps + oppCaps >= 5) score += S_BLK_WIN_CAP;
    }
    score += analyzePatterns(r, c, opp) * 0.9;
    board[r][c] = EMPTY;
   
    return score;
}

function countCaptures(r, c, player) {
    const opponent = player === P1 ? P2 : P1;
    let count = 0;
    for (let d of DIRECTIONS) {
        if (getCell(r + d.dr, c + d.dc) === opponent &&
            getCell(r + 2 * d.dr, c + 2 * d.dc) === opponent &&
            getCell(r + 3 * d.dr, c + 3 * d.dc) === player) count++;
        if (getCell(r – d.dr, c – d.dc) === opponent &&
            getCell(r – 2 * d.dr, c – 2 * d.dc) === opponent &&
            getCell(r – 3 * d.dr, c – 3 * d.dc) === player) count++;
    }
    return count;
}

function analyzePatterns(r, c, player) {
    let score = 0;
    for (let d of DIRECTIONS) {
        let line = [];
        for(let k=-4; k<=4; k++) line.push(getCell(r + k*d.dr, c + k*d.dc));
        const s = line.map(x => x===player?’X’:(x===EMPTY?’.’:’O’)).join(”);
        if (s.includes(‘.XXXX.’)) score += S_OPEN4;
        else if (s.includes(‘OXXXX.’) || s.includes(‘.XXXXO’)) score += S_OPEN4 * 0.5;
        if (s.includes(‘.XXX.’)) score += S_OPEN3;
        if (s.includes(‘.X.XX.’) || s.includes(‘.XX.X.’)) score += S_OPEN3 * 0.9;
    }
    return score;
}

/* — EVENTS — */

// Toggle Play As select based on mode
modeSelect.addEventListener(‘change’, () => {
    if (modeSelect.value === ‘hvai’) {
        playAsSelect.disabled = false;
    } else {
        playAsSelect.disabled = true;
    }
    initGame();
});

gridSizeSelect.addEventListener(‘change’, initGame);
playAsSelect.addEventListener(‘change’, initGame);
resetBtn.addEventListener(‘click’, initGame);
modalReset.addEventListener(‘click’, initGame);
window.addEventListener(‘resize’, resizeCanvas);
window.onload = () => {
    resizeCanvas();
    initGame();
};

</script>
</body>
</html>

Leave a Reply