【ゲーム】JavaScript:28 ハングマンゲーム

 「🔤 ハングマンゲーム 🔤」は、コンピューターがランダムに選んだ単語を _ _ _ _ _ のように隠して表示し、ユーザーが一文字ずつアルファベットを入力して単語を当てていく古典的な推理ゲームです。間違えるたびに残り試行回数が減り、6 回の間違いでゲームオーバーになります。全ての文字を当てれば勝利です。

遊び方と操作方法

  1. スタート:タイトル画面の「スタート 🚀」ボタンをクリック
  2. 単語表示:選ばれた単語の文字数分のアンダースコアが表示される
  3. 文字入力:入力欄にアルファベット1文字を入力し、「判定 ✔️」または Enter キーで確定
  4. 推理:正しければ表示が更新され、間違えれば残り試行回数が1減る
  5. 勝利/敗北:すべての文字を当てれば勝利、残り試行回数が0になると敗北
  6. 再挑戦:結果画面の「タイトルに戻る 🔄」でいつでも再挑戦可能

ルール

  • 使用できる試行回数は最大 6 回
  • 同じ文字を2回以上入力しても試行回数にはカウントされないが、無効入力として注意メッセージを表示
  • アルファベット大文字のみ有効
  • 正解時は即「正解!」、不正解時は「不正解…」のフィードバック
  • 最後まで当てれば「🎉 勝利! 🎉」、試行回数が尽きれば「💀 敗北 💀」

🎮ゲームプレイ

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

28 ハングマンゲーム

素材のダウンロード

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

hangman_title.pnghangman_bg.png

ゲーム画面イメージ

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

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>🔤 ハングマンゲーム 🔤</title>
  <style>
    /* ===== 全体背景設定 ===== */
    body {
      margin: 0;
      padding: 0;
      font-family: 'Arial', sans-serif;
      background: url('hangman_bg.png') no-repeat center center fixed;
      background-size: cover;
    }

    /* ===== 中央オーバーレイ共通スタイル ===== */
    .overlay {
      position: absolute;
      top: 50%; left: 50%;
      transform: translate(-50%, -50%);
      background-color: rgba(255,255,255,0.9);
      padding: 20px;
      border-radius: 10px;
      text-align: center;
      width: 90%; max-width: 540px;
    }

    /* 非表示クラス */
    .hidden { display: none; }

    /* タイトル画像 */
    .title-image {
      display: block;
      margin: 0 auto 10px;
      max-width: 80%; height: auto;
    }

    /* 汎用ボタン */
    .btn {
      display: inline-block;
      padding: 12px 24px;
      font-size: 18px;
      font-weight: bold;
      margin: 15px 5px;
      cursor: pointer;
      border: 2px solid #dc3545;
      border-radius: 8px;
      background-color: #dc3545;
      color: #fff;
      box-shadow: 0 4px 6px rgba(0,0,0,0.2);
      transition: background-color 0.2s, transform 0.1s;
    }
    .btn:hover { background-color: #c82333; transform: translateY(-2px); }
    .btn:active { transform: translateY(0); box-shadow: 0 2px 4px rgba(0,0,0,0.2); }

    /* 表示エリア */
    #word-display {
      font-size: 32px;
      letter-spacing: 8px;
      margin: 20px 0;
    }
    #used-letters {
      margin: 10px 0;
      font-size: 18px;
    }
    #attempts {
      margin: 10px 0;
      font-size: 18px;
    }

    /* 入力フィールド */
    #letter-input {
      font-size: 18px;
      padding: 6px;
      width: 50px;
      text-align: center;
      border: 2px solid #343a40;
      border-radius: 5px;
    }

    /* メッセージ */
    .message {
      margin-top: 15px;
      padding: 10px;
      background-color: rgba(0,0,0,0.7);
      color: #fff;
      border-radius: 5px;
      min-height: 1.5em;
    }
  </style>
</head>
<body>
  <!-- タイトル画面 -->
  <div id="title-screen" class="overlay">
    <img src="hangman_title.png" alt="ハングマンタイトル" class="title-image">
    <h1>🔤 ハングマンゲーム 🔤</h1>
    <p>コンピューターが単語を選び、_ のみを表示します。</p>
    <p>一文字ずつ入力して、単語にその文字が含まれるか推理してください。</p>
    <p>間違えは 6 回まで。すべて当てれば勝利、0 になると敗北です。</p>
    <button id="start-btn" class="btn">スタート 🚀</button>
  </div>

  <!-- ゲーム画面 -->
  <div id="game-screen" class="overlay hidden">
    <!-- 単語表示 -->
    <div id="word-display">_ _ _ _ _</div>
    <!-- 使った文字表示 -->
    <div id="used-letters">使った文字: <span id="letters">なし</span></div>
    <!-- 残り試行回数 -->
    <div id="attempts">残り試行回数: <span id="remaining">6</span></div>
    <!-- 入力と判定 -->
    <input type="text" id="letter-input" maxlength="1" placeholder="A">
    <button id="guess-btn" class="btn">判定 ✔️</button>
    <!-- フィードバックメッセージ -->
    <div id="message" class="message">準備中…</div>
  </div>

  <!-- 終了画面 -->
  <div id="end-screen" class="overlay hidden">
    <h2 id="end-title">結果</h2>
    <p id="final-message">単語: <span id="final-word"></span></p>
    <button id="restart-btn" class="btn">タイトルに戻る 🔄</button>
  </div>

  <script>
    // ===== 単語リスト =====
    const WORDS = ['JAVASCRIPT','PYTHON','HTML','CSS','NODE'];
    const MAX_WRONG = 6; // 最大間違い回数

    // ===== ゲーム状態 =====
    let answer = '';
    let displayArr = [];
    let wrongCount = 0;
    let usedLetters = [];

    // ===== DOM要素 =====
    const titleScreen = document.getElementById('title-screen');
    const gameScreen  = document.getElementById('game-screen');
    const endScreen   = document.getElementById('end-screen');
    const startBtn    = document.getElementById('start-btn');
    const restartBtn  = document.getElementById('restart-btn');
    const wordDisplay = document.getElementById('word-display');
    const lettersSpan = document.getElementById('letters');
    const remainingSpan = document.getElementById('remaining');
    const letterInput = document.getElementById('letter-input');
    const guessBtn    = document.getElementById('guess-btn');
    const messageDiv  = document.getElementById('message');
    const endTitle    = document.getElementById('end-title');
    const finalMessage = document.getElementById('final-message');
    const finalWord   = document.getElementById('final-word');

    /** タイトル表示 */
    function showTitle() {
      titleScreen.classList.remove('hidden');
      gameScreen.classList.add('hidden');
      endScreen.classList.add('hidden');
    }

    /** ゲーム開始 */
    function startGame() {
      // 初期化
      answer = WORDS[Math.floor(Math.random()*WORDS.length)];
      displayArr = Array(answer.length).fill('_');
      wrongCount = 0;
      usedLetters = [];
      // UI 更新
      wordDisplay.textContent = displayArr.join(' ');
      lettersSpan.textContent = 'なし';
      remainingSpan.textContent = MAX_WRONG - wrongCount;
      messageDiv.textContent = '文字を入力してください';
      letterInput.value = '';
      letterInput.focus();
      // 画面切り替え
      titleScreen.classList.add('hidden');
      endScreen.classList.add('hidden');
      gameScreen.classList.remove('hidden');
    }

    /** 推測処理 */
    function handleGuess() {
      const letter = letterInput.value.trim().toUpperCase();
      if (!letter || usedLetters.includes(letter) || !/^[A-Z]$/.test(letter)) {
        messageDiv.textContent = '有効な未使用の一文字を入力してください';
        letterInput.value = '';
        return;
      }
      usedLetters.push(letter);
      lettersSpan.textContent = usedLetters.join(', ');
      let hit = false;
      // 答えに含まれるかチェック
      for (let i=0; i<answer.length; i++) {
        if (answer[i] === letter) {
          displayArr[i] = letter;
          hit = true;
        }
      }
      wordDisplay.textContent = displayArr.join(' ');
      if (!hit) {
        wrongCount++;
        remainingSpan.textContent = MAX_WRONG - wrongCount;
      }
      letterInput.value = '';
      letterInput.focus();
      // 勝利判定
      if (!displayArr.includes('_')) {
        endGame(true);
      } else if (wrongCount >= MAX_WRONG) {
        endGame(false);
      } else {
        messageDiv.textContent = hit ? '正解!' : '不正解…';
      }
    }

    /** 終了処理 */
    function endGame(won) {
      gameScreen.classList.add('hidden');
      endScreen.classList.remove('hidden');
      if (won) {
        endTitle.textContent = '🎉 勝利! 🎉';
        finalMessage.textContent = `正解: ${answer}`;
      } else {
        endTitle.textContent = '💀 敗北 💀';
        finalMessage.textContent = `答えは ${answer} でした`;
      }
      finalWord.textContent = '';
    }

    // ===== イベントリスナー =====
    document.addEventListener('DOMContentLoaded', () => {
      startBtn.addEventListener('click', startGame);
      guessBtn.addEventListener('click', handleGuess);
      letterInput.addEventListener('keyup', e => {
        if (e.key === 'Enter') handleGuess();
      });
      restartBtn.addEventListener('click', showTitle);
      showTitle();
    });
  </script>
</body>
</html>

アルゴリズムの流れ

ステップ処理内容関数/命令
1. タイトル表示タイトル画面を表示し、ゲーム画面・終了画面を非表示showTitle()
2. ゲーム開始単語選択→表示配列初期化→UI初期化→ゲーム画面表示startGame()
3. 入力検証入力が未使用かつA–Z1文字か→失敗時は注意メッセージhandleGuess()
4. Hit/Blow判定正解文字の位置を更新→不命中なら wrongCount++handleGuess()
5. UI更新_ _ A _ _ 表示/使った文字一覧更新/残試行回数更新/フィードバック表示handleGuess()
6. 勝利 or 敗北全文字が当たれば勝利、間違いが6回に達すると敗北endGame(true/false)
7. 終了画面表示結果タイトルと正解単語を表示し、終了画面を表示endGame()

関数の詳しい解説

関数名説明
showTitle()タイトル画面のみ表示し、他の .overlay.hidden で非表示に
startGame()- ランダム単語選択 (WORDS[...])
- displayArr_ で初期化
- wrongCount, usedLetters リセット
- UI(単語表示/使った文字/残試行回数/メッセージ)初期化
- タイトル画面を隠しゲーム画面を表示
handleGuess()- 入力検証(未使用かつA–Z一文字)
- usedLetters.push()→一覧更新
- 単語全体をループしHit(位置も文字も一致)を displayArr に反映
- Hit でなければ wrongCount++
- UI(単語表示欄・残試行回数・フィードバック)更新
- 勝利/敗北判定して endGame() 呼出
endGame(won)- ゲーム画面を非表示に、終了画面を表示
- won===true で勝利タイトル・正解単語、false で敗北タイトル・正解単語を表示

改造のポイント

  • 単語リストの拡張
    WORDS 配列に単語を追加し、出題語を増やすとバリエーションアップ
  • 難易度調整
    MAX_WRONG を増減して許容間違い数を変更
    ・単語の長さを変えて難易度を操作
  • ビジュアル演出
    ・間違えたたびにハングマンのイラストを段階的に表示する機能を追加
    ・正解/不正解時に効果音やアニメーションを追加
  • タイマー導入
    ・回答に制限時間を設け、時間切れで不正解扱いに
  • ランキング/スコア保存
    localStorage にクリアタイムや間違い回数を保存し、ハイスコアを表示
  • マルチワード対応
    ・単語ではなくフレーズや複数単語を扱えるように改修

アドバイス:ハングマンのビジュアル成長に合わせたアニメーションを実装すると、ゲーム性がぐっと高まります。さらに、出題単語をカテゴリー別(プログラミング用語、動物、食べ物など)に分け、選べるようにすることで、学習アプリとしての利用価値も向上します。