【ゲーム】JavaScript:53 もぐらたたきゲーム

 「モグラたたきゲーム」は、制限時間内に出現するモグラ(🦝)をできるだけ多く叩いて得点を競うシンプルかつ爽快なアクションゲームです。背景には草原をイメージした画像が敷かれ、全9つの穴からランダムにモグラが飛び出します。叩くごとに効果音が鳴り、視覚的・聴覚的に楽しめる設計です。

遊び方・操作方法

  1. タイトル画面で「スタート」ボタンをクリック。
  2. ゲーム画面に切り替わると、残り時間(30秒)とスコア(初期0点)が表示されます。
  3. 9つの穴の中からランダムに現れた🦝を、マウスでクリック(タップ)して叩きます。
  4. 叩くたびにスコアが1点ずつ増加します。
  5. 制限時間が0秒になると自動的にゲーム終了画面に遷移し、最終スコアが表示されます。

ルール

  • 制限時間は30秒
  • 出現中のモグラをクリックすると+1点
  • モグラは一度に1匹だけ出現し、出現時間はランダム(約850~1,500ms)。
  • モグラを叩くと即座に消え、次の出現まで短時間インターバルがあります。
  • 時間切れで自動終了。ゲーム終了画面からタイトルに戻れます。

🎮ゲームプレイ

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

53 もぐらたたきゲーム

素材のダウンロード

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

whack_mole_title.pngwhack_mole_bg.png

ゲーム画面イメージ

プログラム全文(whack_mole.html)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>🔨 モグラたたきゲーム 🔨</title>
  <style>
    html, body {
      height: 100%;
      margin: 0;
      padding: 0;
      font-family: 'Yu Gothic', 'Meiryo', sans-serif;
    }
    body {
      background: url('whack_mole_bg.png') no-repeat center center fixed;
      background-size: cover;
      width: 100vw;
      height: 100vh;
      overflow: auto;
    }
    .container {
      width: 800px;
      margin: 40px auto;
      background: rgba(255,255,255,0.88);
      border-radius: 20px;
      box-shadow: 0 4px 24px rgba(0,0,0,0.12);
      padding-bottom: 20px;
      min-height: 600px;
      position: relative;
    }
    /* タイトル画像(下余白を広げる) */
    .title-img {
      display: block;
      margin: 20px auto 10px auto; /* タイトル直下なのでマージン調整 */
      width: 400px;
      max-width: 80%;
      height: auto;
    }
    h1 {
      text-align: center;
      font-size: 2.3em;
      margin: 0.7em 0 0.15em 0;
      color: #804800;
      text-shadow: 2px 2px 6px #fff;
    }
    .rule-section {
      background: rgba(255,255,210,0.98);
      border-radius: 14px;
      margin: 32px 32px 12px 32px;
      padding: 16px 24px;
      box-shadow: 0 2px 8px rgba(128,64,0,0.09);
    }
    .rule-title {
      text-align: center;
      font-weight: bold;
      font-size: 1.5em;
      margin-bottom: 10px;
      color: #be7500;
    }
    .rule-text {
      text-align: left;
      font-size: 1.12em;
      line-height: 1.65;
      color: #544100;
      letter-spacing: 0.01em;
    }
    .btn {
      display: block;
      margin: 28px auto 0 auto;
      padding: 14px 50px;
      font-size: 1.3em;
      border: none;
      border-radius: 28px;
      background: linear-gradient(90deg, #ffd700, #ffb300);
      color: #4a3300;
      font-weight: bold;
      box-shadow: 0 2px 8px #ccbb33a0;
      cursor: pointer;
      transition: background 0.2s;
    }
    .btn:hover { background: linear-gradient(90deg, #ffe36b, #ffca47); }
    .game-area {
      width: 680px;
      margin: 0 auto;
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: repeat(3, 1fr);
      gap: 38px;
      margin-top: 30px;
      margin-bottom: 28px;
      min-height: 350px;
      pointer-events: auto;
    }
    /* 穴(flexで中央寄せ) */
    .hole {
      width: 140px;
      height: 120px;
      background: #e4c387;
      border-radius: 50% 50% 60% 60%/60% 60% 80% 80%;
      position: relative;
      box-shadow: 0 6px 30px #8c775777 inset, 0 1px 12px #bea98b44;
      cursor: pointer;
      overflow: hidden;
      display: flex;
      align-items: center; /* ←中央寄せ */
      justify-content: center; /* ←中央寄せ */
      transition: box-shadow 0.18s;
    }
    .hole:active { box-shadow: 0 2px 12px #705a3477 inset, 0 1px 12px #bea98b44; }
    /* モグラ(タヌキ絵文字) */
    .mole {
      font-size: 54px;
      line-height: 1;
      z-index: 2;
      user-select: none;
      cursor: pointer;
      transition: transform 0.14s cubic-bezier(0.42,0,0.58,1);
      /* 穴の中央に固定 */
      position: static;
      /* flex子要素なので上下中央配置OK */
    }
    .hit-effect {
      animation: hit 0.3s;
    }
    @keyframes hit {
      0% { transform: scale(1.2) rotate(-8deg);}
      100% { transform: scale(1) rotate(0deg);}
    }
    .score-timer-bar {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 16px 48px 8px 48px;
      font-size: 1.28em;
      font-weight: bold;
      color: #4b2b00;
      background: rgba(255,235,120,0.85);
      border-radius: 14px;
      width: 680px;
      margin: 18px auto 0 auto;
      box-shadow: 0 1px 8px #eacb4f66;
    }
    .message-box {
      text-align: center;
      background: rgba(255,255,210,0.99);
      font-size: 2em;
      color: #a26a00;
      font-weight: bold;
      border-radius: 16px;
      box-shadow: 0 4px 24px #ffb70033;
      width: 80%;
      max-width: 480px;
      margin: 40px auto 0 auto;
      padding: 38px 18px;
      position: relative;
      z-index: 2;
    }
    .center-btn { margin: 24px auto 0 auto; }
    @media (max-width: 860px) {
      .container, .game-area, .score-timer-bar { width: 98vw; min-width: 0; }
      .game-area { gap: 2vw; }
      .score-timer-bar { padding: 10px 10vw 4px 10vw; }
    }
  </style>
</head>
<body>
  <div class="container" id="main-container">
    <!-- ここに動的に画面を描画 -->
  </div>

  <script>
    // =======================
    // 画面描画の状態管理
    // =======================
    let score = 0;
    let timeLeft = 30; // 秒
    let gameTimer = null;
    let moleTimers = [];
    let isGameActive = false;

    const HOLE_COUNT = 9;

    // タイトル画面
    function showTitleScreen() {
      isGameActive = false;
      clearTimers();
      document.getElementById('main-container').innerHTML = `
        <h1>🔨 モグラたたきゲーム 🔨</h1>
        <img src="whack_mole_title.png" class="title-img" alt="モグラたたきタイトル">
        <div class="rule-section">
          <div class="rule-title">🎲 遊び方・ルール 🎲</div>
          <div class="rule-text">
            ・制限時間30秒で、できるだけ多くのモグラを叩こう!<br>
            ・穴から出ているモグラ(🦝)をクリックすると1点ゲット!<br>
            ・モグラはランダムな穴から、一定時間ごとに出てくるよ。<br>
            ・たたくごとに効果音が鳴ります。<br>
            ・制限時間が0秒になるとゲーム終了!スコアが表示されます。
          </div>
        </div>
        <button class="btn" id="start-btn">スタート</button>
      `;
      document.getElementById('start-btn').onclick = startGame;
    }

    // ゲーム画面
    function showGameScreen() {
      document.getElementById('main-container').innerHTML = `
        <h1>🔨 モグラたたきゲーム 🔨</h1>
        <div class="score-timer-bar">
          <span>スコア:<span id="score-num">${score}</span></span>
          <span>残り時間:<span id="timer-num">${timeLeft}</span> 秒</span>
        </div>
        <div class="game-area" id="game-area"></div>
      `;
      drawHoles();
    }

    // 穴を描画
    function drawHoles() {
      const gameArea = document.getElementById('game-area');
      gameArea.innerHTML = '';
      for (let i = 0; i < HOLE_COUNT; i++) {
        const holeDiv = document.createElement('div');
        holeDiv.className = 'hole';
        holeDiv.dataset.idx = i;
        gameArea.appendChild(holeDiv);
      }
    }

    // ゲームスタート
    function startGame() {
      score = 0;
      timeLeft = 30;
      isGameActive = true;
      showGameScreen();
      updateScoreAndTimer();
      gameTimer = setInterval(() => {
        timeLeft--;
        updateScoreAndTimer();
        if (timeLeft <= 0) endGame();
      }, 1000);
      setTimeout(spawnMole, 600);
    }

    // スコアとタイマー表示更新
    function updateScoreAndTimer() {
      const scoreNum = document.getElementById('score-num');
      const timerNum = document.getElementById('timer-num');
      if (scoreNum) scoreNum.textContent = score;
      if (timerNum) timerNum.textContent = timeLeft;
    }

    // モグラ出現
    function spawnMole() {
      if (!isGameActive) return;
      const idx = Math.floor(Math.random() * HOLE_COUNT);
      const holes = document.getElementsByClassName('hole');
      if (holes[idx].querySelector('.mole')) {
        setTimeout(spawnMole, 250);
        return;
      }
      // 🦝 を中央に
      const moleDiv = document.createElement('div');
      moleDiv.className = 'mole';
      moleDiv.textContent = '🦝';
      moleDiv.onclick = () => whackMole(moleDiv);
      holes[idx].appendChild(moleDiv);

      const hideTimeout = setTimeout(() => {
        if (moleDiv.parentNode) moleDiv.parentNode.removeChild(moleDiv);
      }, 850 + Math.random() * 650);

      moleTimers.push(hideTimeout);

      setTimeout(spawnMole, 480 + Math.random() * 400);
    }

    // モグラたたき
    function whackMole(moleDiv) {
      if (!isGameActive) return;
      score++;
      updateScoreAndTimer();
      moleDiv.classList.add('hit-effect');
      playHitSound();
      setTimeout(() => {
        if (moleDiv.parentNode) moleDiv.parentNode.removeChild(moleDiv);
      }, 130);
    }

    // 効果音
    function playHitSound() {
      try {
        const ctx = new(window.AudioContext || window.webkitAudioContext)();
        const o = ctx.createOscillator();
        const g = ctx.createGain();
        o.type = "triangle";
        o.frequency.value = 480 + Math.random() * 100;
        g.gain.value = 0.22;
        o.connect(g);
        g.connect(ctx.destination);
        o.start();
        g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.13);
        o.stop(ctx.currentTime + 0.13);
        setTimeout(() => ctx.close(), 200);
      } catch (e) {}
    }

    // ゲーム終了
    function endGame() {
      isGameActive = false;
      clearTimers();
      showEndScreen();
    }

    // タイマー・モグラ消去
    function clearTimers() {
      if (gameTimer) clearInterval(gameTimer);
      moleTimers.forEach(t => clearTimeout(t));
      moleTimers = [];
    }

    // 終了画面
    function showEndScreen() {
      document.getElementById('main-container').innerHTML = `
        <h1>🔨 モグラたたきゲーム 🔨</h1>
        <img src="title.png" class="title-img" alt="モグラたたきタイトル">
        <div class="message-box">
          ゲーム終了!<br>
          <span style="font-size:1.2em;color:#ff8c00;">あなたのスコア:${score} 点</span>
        </div>
        <button class="btn center-btn" id="back-title-btn">タイトル画面に戻る</button>
      `;
      document.getElementById('back-title-btn').onclick = showTitleScreen;
    }

    // 初期表示
    showTitleScreen();
  </script>
</body>
</html>

アルゴリズムの流れ

手順処理内容
1showTitleScreen() でタイトル画面を描画し、開始ボタンに startGame を紐付け
2ボタン押下で startGame() → スコア・時間を初期化し、ゲーム画面を描画
31秒ごとにタイマーを減少させ、時間切れ判定で endGame() を実行
4spawnMole() でランダムな穴にモグラ要素を生成・表示し、消去タイマーを設定
5さらにランダム間隔で再帰的に spawnMole() を呼び出し
6モグラクリックで whackMole() → スコア加算・エフェクト・効果音再生・消去
7endGame() ですべてのタイマーをクリアし、終了画面を描画
8終了画面の「タイトル画面に戻る」で再び showTitleScreen() を呼び出し状態をリセット

関数の詳細解説

関数名機能概要詳細説明
showTitleScreen()タイトル画面の描画ルール説明+スタートボタンを表示し、ゲーム状態を初期化・停止
startGame()ゲーム開始処理スコアとタイマーを初期化し、ゲーム画面へ切替。モグラ生成&タイマー開始
showGameScreen()ゲーム画面の描画ヘッダー・スコアタイマーバー・9つの穴エリアを配置
drawHoles()ホール(穴)要素の生成0~8 のインデックスを保持した .hole 要素を 3×3 グリッドに追加
spawnMole()モグラ出現/再帰呼び出しモグラ要素を一時生成し、表示後にランダム時間後に自動で消去
whackMole(div)モグラを叩くアクションスコア加算+クリックエフェクト+効果音再生+要素の削除
playHitSound()クリック時の効果音生成Web Audio API で三角波オシレーターを作成し、短時間鳴動
updateScoreAndTimer()スコア&タイマー表示の更新対応する DOM 要素を書き換え
endGame()ゲーム終了処理ゲームフラグOFF・タイマー解放・終了画面表示
clearTimers()全タイマー(interval/timeout)削除進行中のゲームループと残存するモグラ消去待ちタイマーをクリア
showEndScreen()終了画面の描画最終スコア表示+タイトル画面に戻るボタン設置

改造のポイント

  • 難易度調整:モグラの出現間隔を縮めたり、出現時間を短くして上級者向けモードを追加。
  • ビジュアル変更:🦝 の絵文字を好みのキャラクター画像に置き換え。穴や背景を季節ごとに差し替えても◎。
  • ランキング機能:ローカルストレージを利用して、ハイスコアランキングを実装するとリプレイ率が向上。
  • タッチ対応:モバイル端末で操作しやすいよう、タップ時のエフェクトを強化。
  • 複数難易度:制限時間やモグラのスピードを変える「Easy/Normal/Hard」ボタンを追加し、初心者〜上級者まで楽しめる設計に。

アドバイス
まずは基本機能を安定させ、次にUI/UXを改善することでユーザー満足度が大きく向上します。ランキングやシェア機能を足せば、友達同士で競い合える楽しいゲームに仕上がります!