見出し画像

お気楽プログラミング:「さめがめ」ゲームを Claude 3.5 Sonnet で作る

空き時間に、昔懐かしい「さめがめ」を Claude 3.5 Sonnet で作ってみました。

作成に際して最初に与えたのは Wikipedia の「ルール」のセクションだけです。
その段階でも 6x6 で一応遊べるものが生成されたのですが、それに加えて、消去時のアニメーション、再実行ボタン、盤の大きさの選択、効果音の付与などを一つずつ行って行き、最終的には以下の結果を得ました。
途中実行時にエラーが出たりしましたが、そのときのエラーを Claude に与えることでエラーを取り除くことができました。
トータルの作成所要時間は7分ほどですね。この記事を書いている時間の方が長いです(笑)

以下のソース(HTML)を適当な場所にsamegame.html といった名前で保存して、そのファイルをブラウザで開くと遊べます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>さめがめ(サイズ選択・効果音付き)</title>
    <style>
        #game-container {
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        #game-board {
            display: grid;
            gap: 2px;
            margin-bottom: 20px;
        }
        .cell {
            width: 40px;
            height: 40px;
            border: 1px solid #000;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 20px;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        .cell.removing {
            transform: scale(0);
            opacity: 0;
        }
        #start-over, #size-select {
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
            margin: 10px;
        }
        #controls {
            display: flex;
            justify-content: center;
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
    <div id="game-container">
        <h1>さめがめ(サイズ選択・効果音付き)</h1>
        <div id="controls">
            <select id="size-select">
                <option value="6">S (6x6)</option>
                <option value="12">M (12x12)</option>
                <option value="18">L (18x18)</option>
            </select>
            <button id="start-over">Start Over</button>
        </div>
        <div id="game-board"></div>
        <p>スコア: <span id="score">0</span></p>
    </div>
    <script>
        let BOARD_SIZE = 6;
        const COLORS = ['🔴', '🔵', '🟢', '🟡', '🟣'];
        let board = [];
        let score = 0;
        let audioContext;

        function initializeBoard() {
            board = [];
            for (let i = 0; i < BOARD_SIZE; i++) {
                board[i] = [];
                for (let j = 0; j < BOARD_SIZE; j++) {
                    board[i][j] = COLORS[Math.floor(Math.random() * COLORS.length)];
                }
            }
        }

        function renderBoard() {
            const gameBoard = document.getElementById('game-board');
            gameBoard.innerHTML = '';
            gameBoard.style.gridTemplateColumns = `repeat(${BOARD_SIZE}, 40px)`;
            for (let i = 0; i < BOARD_SIZE; i++) {
                for (let j = 0; j < BOARD_SIZE; j++) {
                    const cell = document.createElement('div');
                    cell.className = 'cell';
                    cell.textContent = board[i][j];
                    cell.onclick = () => handleClick(i, j);
                    gameBoard.appendChild(cell);
                }
            }
        }

        async function handleClick(row, col) {
            const color = board[row][col];
            const group = findGroup(row, col, color);
            if (group.length > 1) {
                await removeGroupWithAnimation(group);
                dropTiles();
                shiftColumns();
                score += group.length * 10;
                document.getElementById('score').textContent = score;
                renderBoard();
                if (isGameOver()) {
                    alert('ゲームオーバー!最終スコア: ' + score);
                }
            }
        }

        function findGroup(row, col, color, group = []) {
            if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE || board[row][col] !== color) {
                return group;
            }
            const key = `${row},${col}`;
            if (group.includes(key)) return group;
            group.push(key);
            findGroup(row - 1, col, color, group);
            findGroup(row + 1, col, color, group);
            findGroup(row, col - 1, color, group);
            findGroup(row, col + 1, color, group);
            return group;
        }

        function removeGroupWithAnimation(group) {
            return new Promise(resolve => {
                const cells = document.querySelectorAll('.cell');
                group.forEach(key => {
                    const [row, col] = key.split(',').map(Number);
                    const index = row * BOARD_SIZE + col;
                    cells[index].classList.add('removing');
                });

                playPopSound();

                setTimeout(() => {
                    group.forEach(key => {
                        const [row, col] = key.split(',').map(Number);
                        board[row][col] = null;
                    });
                    resolve();
                }, 300);
            });
        }

        function playPopSound() {
            if (!audioContext) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
            }
            
            const oscillator = audioContext.createOscillator();
            const gainNode = audioContext.createGain();
            
            oscillator.type = 'sine';
            oscillator.frequency.setValueAtTime(440, audioContext.currentTime); // 440Hz = A4
            oscillator.frequency.exponentialRampToValueAtTime(880, audioContext.currentTime + 0.1); // 880Hz = A5
            
            gainNode.gain.setValueAtTime(1, audioContext.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.1);
            
            oscillator.connect(gainNode);
            gainNode.connect(audioContext.destination);
            
            oscillator.start();
            oscillator.stop(audioContext.currentTime + 0.1);
        }

        function dropTiles() {
            for (let col = 0; col < BOARD_SIZE; col++) {
                let writeRow = BOARD_SIZE - 1;
                for (let row = BOARD_SIZE - 1; row >= 0; row--) {
                    if (board[row][col] !== null) {
                        board[writeRow][col] = board[row][col];
                        writeRow--;
                    }
                }
                while (writeRow >= 0) {
                    board[writeRow][col] = null;
                    writeRow--;
                }
            }
        }

        function shiftColumns() {
            let writeCol = 0;
            for (let col = 0; col < BOARD_SIZE; col++) {
                if (board[BOARD_SIZE - 1][col] !== null) {
                    if (writeCol !== col) {
                        for (let row = 0; row < BOARD_SIZE; row++) {
                            board[row][writeCol] = board[row][col];
                            board[row][col] = null;
                        }
                    }
                    writeCol++;
                }
            }
        }

        function isGameOver() {
            for (let i = 0; i < BOARD_SIZE; i++) {
                for (let j = 0; j < BOARD_SIZE; j++) {
                    if (board[i][j] && findGroup(i, j, board[i][j]).length > 1) {
                        return false;
                    }
                }
            }
            return true;
        }

        function startOver() {
            score = 0;
            document.getElementById('score').textContent = score;
            initializeBoard();
            renderBoard();
        }

        function changeBoardSize() {
            BOARD_SIZE = parseInt(document.getElementById('size-select').value);
            startOver();
        }

        document.getElementById('start-over').addEventListener('click', startOver);
        document.getElementById('size-select').addEventListener('change', changeBoardSize);

        initializeBoard();
        renderBoard();
    </script>
</body>
</html>

なおトップの画像は ChatGPT に Wikipedia の「さめがめ」の記事を読ませて生成させたものです。


この記事が気に入ったらサポートをしてみませんか?