• 投稿カテゴリー:Html / JavaScript / 技術 / 開発
  • 投稿の最終変更日:12月 21, 2024

AIの助けを借りてテトリスを作ってみました!

使用AIはCopilotとGemini

まずは完成したゲーム

AIへの指示(プロンプト)を書く。実際の開発では要件定義と言うらしい。

要件が曖昧だとAIも不完全なものを出力するため、ここが重要。

細かいロジックの実装(設計とコーディング)はAIが良い感じにやってくれる。

プロンプト

テトリスのJavaScript実装:

基本操作:

左右キーでブロックの移動

スペースキーでブロックの回転

下キーでブロック落下の加速

ゲームボード:

マス目は横10、縦20

行がすべて埋まったら該当行を削除し、行分詰める

最上段のマスをブロックが超えたらゲームオーバー

フレームレートは3fps

ブロックの形と色:

I、J、L、O、S、T、Z型のブロック

各ブロックごとに色分け

スコア計算:

1列消すごとに100点追加

10秒ごとに基礎スコアに100点追加

追加機能:

次のブロックをボードの右に表示し、その下にSCOREを表示

ブロック設置後に0.5秒のインターバルを設ける

操作方法をフッターに表示

完成したコード

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>JavaScript Tetris</title>
    <style>
        body {
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .container {
            display: flex;
        }

        canvas {
            border: 1px solid black;
            margin-top: 10px;
        }

        #next-container {
            margin-left: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        footer {
            margin-top: 20px;
        }
    </style>
</head>

<body>
    <h1>Tetris</h1>
    <div class="container">
        <canvas id="gameBoard" width="300" height="600"></canvas>
        <div id="next-container">
            <p>Next Block:</p>
            <canvas id="nextBlock" width="100" height="100"></canvas>
            <p>SCORE: <span id="score">0</span></p>
        </div>
    </div>
    <footer>
        <p>左右キーでブロックの移動 / スペースキーでブロックの回転 / 下キーでブロック落下の加速</p>
    </footer>
    <script>
        // ゲームボードのキャンバスとコンテキストを取得
        const canvas = document.getElementById('gameBoard');
        const context = canvas.getContext('2d');

        // 次のブロック表示用のキャンバスとコンテキストを取得
        const nextCanvas = document.getElementById('nextBlock');
        const nextContext = nextCanvas.getContext('2d');

        // スコア表示用の要素を取得
        const scoreElement = document.getElementById('score');

        // 定数の定義
        const ROWS = 20;
        const COLS = 10;
        const BLOCK_SIZE = 30;
        const FPS = 3;
        const COLORS = ['cyan', 'blue', 'orange', 'yellow', 'green', 'purple', 'red'];

        // ブロックの形状を定義
        const SHAPES = [
            [[1, 1, 1, 1]], // I
            [[1, 0, 0], [1, 1, 1]], // J
            [[0, 0, 1], [1, 1, 1]], // L
            [[1, 1], [1, 1]], // O
            [[0, 1, 1], [1, 1, 0]], // S
            [[0, 1, 0], [1, 1, 1]], // T
            [[1, 1, 0], [0, 1, 1]] // Z
        ];

        // ゲームボードを初期化(すべてのセルを0にする)
        let board = Array.from({ length: ROWS }, () => Array(COLS).fill(0));

        // 現在のブロックと次のブロックをランダムに設定
        let currentShape = getRandomShape();
        let nextShape = getRandomShape();
        let score = 0;
        let gameInterval;

        // ランダムなブロックを取得する関数
        function getRandomShape() {
            const shapeIndex = Math.floor(Math.random() * SHAPES.length);
            return { shape: SHAPES[shapeIndex], color: COLORS[shapeIndex], x: COLS / 2 - 1, y: 0 };
        }

        // ゲームボードを描画する関数
        function drawBoard() {
            context.clearRect(0, 0, canvas.width, canvas.height);
            for (let row = 0; row < ROWS; row++) {
                for (let col = 0; col < COLS; col++) {
                    if (board[row][col]) {
                        context.fillStyle = COLORS[board[row][col] - 1];
                        context.fillRect(col * BLOCK_SIZE, row * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
                        context.strokeRect(col * BLOCK_SIZE, row * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
                    }
                }
            }
            // 現在のブロックを描画
            drawShape(currentShape);
        }

        // ブロックを描画する関数
        function drawShape(shape) {
            shape.shape.forEach((row, y) => {
                row.forEach((cell, x) => {
                    if (cell) {
                        context.fillStyle = shape.color;
                        context.fillRect((shape.x + x) * BLOCK_SIZE, (shape.y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
                        context.strokeRect((shape.x + x) * BLOCK_SIZE, (shape.y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
                    }
                });
            });
        }

        // 次のブロックを描画する関数
        function drawNextShape() {
            nextContext.clearRect(0, 0, nextCanvas.width, nextCanvas.height);
            nextShape.shape.forEach((row, y) => {
                row.forEach((cell, x) => {
                    if (cell) {
                        nextContext.fillStyle = nextShape.color;
                        nextContext.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
                        nextContext.strokeRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
                    }
                });
            });
        }

        // ブロックを移動する関数
        function moveShape(offsetX, offsetY) {
            const newShape = { ...currentShape, x: currentShape.x + offsetX, y: currentShape.y + offsetY };
            if (!isCollision(newShape)) {
                currentShape = newShape;
            } else if (offsetY === 1) {
                mergeShape();
                currentShape = nextShape;
                nextShape = getRandomShape();
                drawNextShape();
                if (isCollision(currentShape)) {
                    clearInterval(gameInterval);
                    alert('Game Over!');
                }
            }
            drawBoard(); // ゲームボードの再描画
        }

        // ブロックを回転させる関数
        function rotateShape() {
            const rotatedShape = {
                ...currentShape,
                shape: currentShape.shape[0].map((_, index) => currentShape.shape.map(row => row[index]).reverse())
            };
            if (!isCollision(rotatedShape)) {
                currentShape = rotatedShape;
                drawBoard(); // ゲームボードの再描画
            }
        }

        // 衝突判定の関数
        function isCollision(shape) {
            return shape.shape.some((row, y) =>
                row.some((cell, x) =>
                    cell &&
                    (shape.x + x < 0 || shape.x + x >= COLS || shape.y + y >= ROWS || board[shape.y + y] && board[shape.y + y][shape.x + x])
                )
            );
        }

        // ブロックをゲームボードに統合する関数
        function mergeShape() {
            currentShape.shape.forEach((row, y) => {
                row.forEach((cell, x) => {
                    if (cell) {
                        board[currentShape.y + y][currentShape.x + x] = COLORS.indexOf(currentShape.color) + 1;
                    }
                });
            });
            clearLines();
        }

        // 行をクリアする関数
        function clearLines() {
            let clearedLines = 0;
            board = board.filter(row => {
                if (row.every(cell => cell)) {
                    clearedLines++;
                    return false;
                }
                return true;
            });
            while (board.length < ROWS) {
                board.unshift(Array(COLS).fill(0));
            }
            score += clearedLines * 100;
            scoreElement.textContent = score;
        }

        // キーボードイベントリスナーを追加
        document.addEventListener('keydown', event => {
            if (['ArrowLeft', 'ArrowRight', 'ArrowDown', ' '].includes(event.key)) {
                event.preventDefault(); // 矢印キーとスペースキーのデフォルト動作を無効化
            }
            switch (event.key) {
                case 'ArrowLeft':
                    moveShape(-1, 0);
                    break;
                case 'ArrowRight':
                    moveShape(1, 0);
                    break;
                case 'ArrowDown':
                    moveShape(0, 1);
                    break;
                case ' ':
                    rotateShape();
                    break;
            }
        });

        // ゲームループを設定
        gameInterval = setInterval(() => {
            moveShape(0, 1);
        }, 1000 / FPS);

        drawNextShape();
        drawBoard();
    </script>
</body>

</html>

昔からあるゲームではあるけれど、ゼロから組むには難易度が高いと思う。

AIを使うと5分ばかりでできるだけでなくコードの解説もしてくれるため学習教材として優秀!