【ゲーム】JavaScript:03 スネークゲーム

「🐍 スネークゲーム 🍎」は、矢印キーでヘビを操作してキャンバス上のフルーツを食べ、ヘビをどこまで長く伸ばせるかを競うクラシックなミニゲームです。ヘビが画面外や自分の体にぶつかるとゲームオーバーとなり、中央の「🔄 リスタート」ボタンで再挑戦できます。

ゲームの遊び方

 ページを開くと自動でゲームがスタートします。キーボードの↑↓←→(矢印キー)でヘビの頭を操作し、ランダムに配置されるフルーツ絵文字(🍎🍌🍇…)を食べるとヘビが1マス伸びます。フルーツを食べるたびに次のフルーツが別の位置に現れ、できるだけ長くプレイを続けましょう。

ルール

  • ヘビの頭がキャンバス外に出るか、自分の胴体と重なるとゲームオーバー。
  • 食べたときだけヘビが長くなり、それ以外は一歩進むごとに尻尾が1マス短くなって移動します。
  • キー入力は直前の進行方向と逆方向には変更できず、操作ミス防止の仕組みが入っています。
  • ゲームオーバー後に表示される「🔄 リスタート」ボタンで再挑戦できます。

🎮ゲームプレイ

以下のリンク先から実際にプレイできます。

03 スネークゲーム

素材のダウンロード

以下のリンクから使用する素材をダウンロードできます。

snake_bg.png

ゲーム画面イメージ

プログラム全文(snake.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>
        /* リセット */
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            background: url('snake_bg.png') no-repeat center center fixed;
            background-size: cover;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            font-family: 'Arial', sans-serif;
        }
        #game-container {
            position: relative;
            text-align: center;
            width: 600px; /* コンテナ幅を固定 */
            margin: 0 auto; /* 水平中央寄せ */
        }
        /* タイトル */
        h1 {
            font-size: 2.5rem;
            color: #fff;
            background-color: rgba(0, 0, 0, 0.6);
            text-align: center; /* タイトル文字を中央揃え */
            padding: 10px 20px;
            border-radius: 8px;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.7);
            margin-bottom: 10px;
        }
        /* ゲームキャンバス */
        canvas {
            background-color: rgba(0, 0, 0, 0.5);
            border: 2px solid #fff;
            border-radius: 8px;
        }
        /* リスタートボタン中央配置 */
        #restart-btn {
            position: absolute;
            top: 50%; left: 50%;
            transform: translate(-50%, -50%);
            padding: 12px 24px;
            font-size: 1rem;
            border: none;
            border-radius: 5px;
            background-color: #28a745;
            color: #fff;
            cursor: pointer;
            display: none;
            box-shadow: 0 4px 8px rgba(0,0,0,0.3);
        }
        #restart-btn:hover { background-color: #218838; }
    </style>
</head>
<body>
    <div id="game-container">
        <!-- タイトル -->
        <h1>🐍 スネークゲーム 🍒</h1>
        <!-- ゲーム用キャンバス: 横を広げました -->
        <canvas id="gameCanvas" width="600" height="400"></canvas>
        <!-- リスタートボタン -->
        <button id="restart-btn" onclick="startGame()">🔄 リスタート</button>
    </div>
    <script>
        /*** グローバル変数 ***/
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const cellSize = 20;                // セルのサイズ
        const cols = canvas.width / cellSize;
        const rows = canvas.height / cellSize;
        const fruits = ['🍎','🍌','🍇','🍓','🍉','🍍','🍒','🍐','🍊','🍋']; // フルーツ絵文字
        const headEmoji = '😃';             // スネーク頭部の絵文字
        const bodyEmoji = '🟩';             // スネーク胴体の絵文字
        let snake;                          // スネークの座標リスト
        let direction;                      // 現在の移動方向
        let fruit;                          // 現在のエサ
        let gameLoop;                       // ゲームループID

        /**
         * ゲーム開始処理
         */
        function startGame() {
            // 初期化
            snake = [{ x: Math.floor(cols/2), y: Math.floor(rows/2) }];
            direction = { x: 1, y: 0 };
            placeFruit();
            document.getElementById('restart-btn').style.display = 'none';
            document.addEventListener('keydown', handleKey);
            clearInterval(gameLoop);
            gameLoop = setInterval(updateGame, 150);
        }

        /**
         * エサをランダム配置
         */
        function placeFruit() {
            fruit = {
                x: Math.floor(Math.random() * cols),
                y: Math.floor(Math.random() * rows),
                emoji: fruits[Math.floor(Math.random() * fruits.length)]
            };
        }

        /**
         * キー入力ハンドラ
         */
        function handleKey(e) {
            switch(e.key) {
                case 'ArrowUp':    if (direction.y===0) direction = {x:0,y:-1}; break;
                case 'ArrowDown':  if (direction.y===0) direction = {x:0,y:1};  break;
                case 'ArrowLeft':  if (direction.x===0) direction = {x:-1,y:0}; break;
                case 'ArrowRight': if (direction.x===0) direction = {x:1,y:0};  break;
            }
        }

        /**
         * ゲーム更新処理
         */
        function updateGame() {
            const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
            if (head.x < 0 || head.x >= cols || head.y < 0 || head.y >= rows ||
                snake.some(seg => seg.x===head.x && seg.y===head.y)) {
                return gameOver();
            }
            snake.unshift(head);
            if (head.x===fruit.x && head.y===fruit.y) {
                placeFruit();
            } else {
                snake.pop();
            }
            drawGame();
        }

        /**
         * 描画処理
         */
        function drawGame() {
            ctx.clearRect(0,0,canvas.width,canvas.height);
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.font = `${cellSize}px serif`;
            snake.forEach((seg, idx) => {
                const emoji = idx===0 ? headEmoji : bodyEmoji;
                ctx.fillText(emoji, seg.x*cellSize + cellSize/2, seg.y*cellSize + cellSize/2);
            });
            ctx.fillText(fruit.emoji, fruit.x*cellSize + cellSize/2, fruit.y*cellSize + cellSize/2);
        }

        /**
         * ゲームオーバー処理
         */
        function gameOver() {
            clearInterval(gameLoop);
            document.removeEventListener('keydown', handleKey);
            document.getElementById('restart-btn').style.display = 'block';
        }

        // 初回ゲーム開始
        startGame();
    </script>
</body>
</html>

アルゴリズムの流れ

ステップ処理内容
初期化startGame() でヘビを中央に配置、方向を右向きにセット、フルーツをランダム配置、キーリスナー登録、ゲームループ開始
フルーツ配置placeFruit() でキャンバス内のランダムなセル位置に絵文字を選んで配置
キー入力処理handleKey() で矢印キー入力を監視し、上下左右移動を禁止する反転防止ロジック付きで direction を更新
ゲーム更新updateGame() で次のヘビの頭位置を計算。衝突チェック(壁 or 自己体当たり)→Game Over。フルーツに当たれば伸長、なければ尻尾を削除→描画
描画drawGame() でキャンバスをクリアし、ヘビとフルーツを絵文字で描画
ゲームオーバーgameOver() でループ停止、キーリスナー解除、リスタートボタン表示

主要な組み込みメソッド

メソッド説明
setInterval()一定間隔(150ms)で updateGame() を呼び出す
clearInterval()前のループを停止
addEventListener('keydown')矢印キー入力を監視し、移動方向を制御
canvas.getContext('2d')2D描画コンテキスト取得
ctx.fillText()絵文字をキャンバス上の指定座標に描画
Array.some()衝突判定のため、ヘビの配列を走査

関数の詳細

関数名機能・処理内容
startGame()ゲーム開始前の初期化:・ヘビ配列・方向リセット・フルーツ配置・キー入力リスナー登録・ゲームループ起動・リスタートボタン非表示
placeFruit()フルーツをキャンバス内のランダムなセル位置に配置し、絵文字をランダム選択
handleKey(e)矢印キーに応じて direction を上下左右に更新。ただし進行方向と逆向き移動は無効化
updateGame()ヘビの次位置を計算し、壁・自己衝突チェック→GameOverフルーツ衝突で伸長、そうでなければ尻尾を削除→drawGame()
drawGame()キャンバスクリア後、ヘビの頭と胴体、フルーツをセルごとに絵文字描画
gameOver()ゲームループ停止、キーリスナー解除、リスタートボタン表示

改造のポイント

  • 速度調整setInterval(updateGame, 150) の第2引数を小さくして速度アップ、大きくしてスローダウン
  • キャンバスサイズ変更<canvas>width/height を変更し、cellSize を調整
  • 絵文字カスタマイズfruitsheadEmojibodyEmoji を任意の絵文字や画像に置き換え
  • スコア表示:ヘビの長さをスコアとして画面上にテキスト表示するUIを追加
  • 壁貫通モード:壁との衝突判定を省略し、反対側にワープさせるロジックに変更
  • タッチ操作対応:スマホ向けに画面タップやスワイプで方向を操作できるイベント追加
  • 効果音追加:フルーツ取得やGameOver時に new Audio(...).play() を呼び出す音声演出

これらのポイントを参考に、自分好みのスネークゲームにカスタマイズしてみてください!