【ゲーム】JavaScript:27 ワードスクランブルゲーム

 「ワードスクランブルゲーム」は、あらかじめ用意された単語をランダムに文字単位でシャッフルし、カード風のUIアニメーションで見せた後にプレイヤーが正しい単語を入力して当てる記憶+語彙クイズです。全単語数分のラウンドに挑戦し、何問正解できるかを競います。

遊び方と操作方法

  1. タイトル画面の「スタート 🚀」ボタンをクリック
  2. カードがランダムに「めくり・戻し」アニメーションでシャッフルされる様子を見る
  3. 表示されたシャッフル後の文字配列をもとに、入力欄に正しい単語を入力
  4. 「判定 ✔️」ボタン、または Enter キーで回答を確定
  5. 正解/不正解のフィードバックが表示されたあと、次のラウンドへ自動で移行
  6. 全ラウンド終了後、最終的な正解数が結果画面に表示

ルール

  • 使用単語は WORDS 配列に定義された全5語
  • 毎ラウンド、未出題の単語から順に出題
  • 各単語は文字単位でシャッフルし、同じ長さのカードを並べて表示
  • プレイヤーはシャッフル後の文字順を見て正解単語を入力
  • 正解数をスコアとしてカウント
  • 最終ラウンド終了後に「正解数 / 総ラウンド数」を表示

🎮ゲームプレイ

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

27 ワードスクランブルゲーム

素材のダウンロード

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

word_scramble_title.pngword_scramble_bg.png

ゲーム画面イメージ

プログラム全文(word_scramble.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('word_scramble_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: 500px;
    }

    /* ===== 非表示クラス ===== */
    .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 0;
      cursor: pointer;
      border: 2px solid #007BFF;
      border-radius: 8px;
      background-color: #007BFF;
      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: #0056b3; transform: translateY(-2px); }
    .btn:active { transform: translateY(0); box-shadow: 0 2px 4px rgba(0,0,0,0.2); }

    /* ===== カード表示(トランプ風) ===== */
    #cards-container {
      display: flex;
      justify-content: center;
      flex-wrap: wrap;
      gap: 10px;
      margin: 20px 0;
    }
    .card {
      width: 60px;
      height: 80px;
      perspective: 600px;
    }
    .card-inner {
      position: relative;
      width: 100%; height: 100%;
      transform-style: preserve-3d;
      transition: transform 0.6s;
    }
    /* 裏面:カード裏地 */
    .card-back, .card-front {
      position: absolute;
      width: 100%; height: 100%;
      backface-visibility: hidden;
      border-radius: 6px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 24px;
      font-weight: bold;
    }
    .card-back {
      background-color: #444;
    }
    .card-front {
      background-color: #fff;
      color: #000;
      transform: rotateY(180deg);
      border: 2px solid #000;
    }
    /* flipped クラスで表面表示 */
    .card.flipped .card-inner {
      transform: rotateY(180deg);
    }

    /* ===== 入力フィールド ===== */
    #guess-input {
      font-size: 18px;
      padding: 8px;
      width: 200px;
      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;
    }

    /* ===== 正答数表示 ===== */
    .score-display {
      margin-top: 10px;
      font-size: 18px;
    }
  </style>
</head>
<body>
  <!-- タイトル画面 -->
  <div id="title-screen" class="overlay">
    <img src="word_scramble_title.png" alt="ワードスクランブルタイトル" class="title-image">
    <h1>🃏 ワードスクランブルゲーム 🃏</h1>
    <p>コンピューターが単語を選び、文字をランダムにシャッフルします。</p>
    <p>カードのような札が回転してシャッフルされる様子を見て、</p>
    <p>正しい単語を入力して当ててください。</p>
    <button id="start-btn" class="btn">スタート 🚀</button>
  </div>

  <!-- ゲーム画面 -->
  <div id="game-screen" class="overlay hidden">
    <!-- カード群表示 -->
    <div id="cards-container"></div>
    <!-- ユーザー入力欄と判定ボタン -->
    <input type="text" id="guess-input" placeholder="単語を入力">
    <button id="submit-btn" class="btn">判定 ✔️</button>
    <!-- フィードバックメッセージ -->
    <div id="message" class="message">準備中…</div>
    <!-- 現在のスコア表示 -->
    <div class="score-display">正解数: <span id="score">0</span></div>
  </div>

  <!-- 終了画面 -->
  <div id="end-screen" class="overlay hidden">
    <h2>ゲーム終了 🎉</h2>
    <p id="final-message">正解数: <span id="final-score">0</span> / <span id="total-rounds">0</span></p>
    <button id="restart-btn" class="btn">タイトルに戻る 🔄</button>
  </div>

  <script>
    // ================================================
    // 単語リスト設定
    // ================================================
    const WORDS = [
      'APPLE', 'BANANA', 'ORANGE', 'GRAPE', 'MANGO'
    ];
    const TOTAL_ROUNDS = WORDS.length; // ラウンド数

    // ================================================
    // ゲーム状態変数
    // ================================================
    let currentRound = 0;
    let score = 0;
    let currentWord = '';
    let shuffled = [];

    // ================================================
    // 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 submitBtn    = document.getElementById('submit-btn');
    const restartBtn   = document.getElementById('restart-btn');
    const cardsContainer = document.getElementById('cards-container');
    const guessInput   = document.getElementById('guess-input');
    const messageDiv   = document.getElementById('message');
    const scoreSpan    = document.getElementById('score');
    const finalScore   = document.getElementById('final-score');
    const totalRounds  = document.getElementById('total-rounds');

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

    /**
     * ゲーム開始処理
     */
    function startGame() {
      // 状態リセット
      currentRound = 0;
      score = 0;
      scoreSpan.textContent = score;
      totalRounds.textContent = TOTAL_ROUNDS;
      titleScreen.classList.add('hidden');
      endScreen.classList.add('hidden');
      gameScreen.classList.remove('hidden');
      nextRound(); // ラウンド開始
    }

    /**
     * 次のラウンド準備
     */
    function nextRound() {
      if (currentRound >= TOTAL_ROUNDS) {
        endGame();
        return;
      }
      // 単語決定とシャッフル
      currentWord = WORDS[currentRound];
      shuffled = shuffleArray(currentWord.split(''));
      // カード要素生成
      renderCards();
      // アニメーション実行
      animateShuffle(() => {
        // 終了後入力受付
        messageDiv.textContent = '単語を入力して「判定」を押してください';
        guessInput.value = '';
        guessInput.focus();
      });
    }

    /**
     * 配列をシャッフル
     */
    function shuffleArray(arr) {
      const a = arr.slice();
      for (let i = a.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [a[i], a[j]] = [a[j], a[i]];
      }
      return a;
    }

    /**
     * カード要素をDOMに描画
     */
    function renderCards() {
      cardsContainer.innerHTML = '';
      shuffled.forEach(letter => {
        // card要素構築
        const card = document.createElement('div'); card.className = 'card';
        const inner = document.createElement('div'); inner.className = 'card-inner';
        const back = document.createElement('div'); back.className = 'card-back';
        const front = document.createElement('div'); front.className = 'card-front';
        front.textContent = letter;
        inner.append(back, front);
        card.append(inner);
        cardsContainer.append(card);
      });
      messageDiv.textContent = 'シャッフル中…';
    }

    /**
     * シャッフルアニメーション
     * @param {Function} callback アニメ完了後に実行
     */
    function animateShuffle(callback) {
      const cards = Array.from(document.querySelectorAll('.card'));
      let count = 0;
      const maxFlips = 10;
      const interval = setInterval(() => {
        // ランダムなカードをひっくり返す
        const idx = Math.floor(Math.random() * cards.length);
        cards[idx].classList.toggle('flipped');
        count++;
        if (count >= maxFlips) {
          clearInterval(interval);
          // 全て表側に
          cards.forEach(c => c.classList.add('flipped'));
          setTimeout(callback, 600);
        }
      }, 150);
    }

    /**
     * ユーザー判定処理
     */
    function handleGuess() {
      const guess = guessInput.value.trim().toUpperCase();
      if (!guess) return;
      if (guess === currentWord) {
        score++;
        scoreSpan.textContent = score;
        messageDiv.textContent = '正解!';
      } else {
        messageDiv.textContent = `不正解… 正解: ${currentWord}`;
      }
      currentRound++;
      // 次ラウンドへ
      setTimeout(nextRound, 1000);
    }

    /**
     * ゲーム終了処理
     */
    function endGame() {
      gameScreen.classList.add('hidden');
      finalScore.textContent = score;
      endScreen.classList.remove('hidden');
    }

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


アルゴリズムの流れ

ステップ処理内容主な関数・命令
1. タイトル表示タイトル画面を表示し、他画面を非表示showTitle()
2. ゲーム開始状態リセット・画面切替・第1ラウンド呼び出しstartGame()
3. 単語シャッフルcurrentWordを文字配列に→shuffleArray()でランダム並び替えshuffleArray()
4. カード描画.card要素を生成し、裏面と表面(文字)を構築renderCards()
5. シャッフル演出setIntervalでランダムにflippedクラスを付け外しするanimateShuffle()
6. 入力判定入力値を大文字化し正解と比較→スコア更新&フィードバック表示handleGuess()
7. 次ラウンドor終了currentRoundを進め、終了判定→nextRound() or endGame()呼出handleGuess()endGame()

関数の詳しい解説

関数名説明
showTitle()各画面の .hidden クラスを制御し、タイトル画面のみを表示
startGame()変数 (currentRound, score) をリセット、スコア欄/総ラウンド数表示を初期化、ゲーム画面へ移行
shuffleArray(arr)Fisher–Yates アルゴリズムで配列要素をランダムに並び替え
renderCards().card 要素を生成し、シャッフル済み文字を表面にセット
animateShuffle(cb)指定回数ランダムなカードに flipped クラスを付与し、裏表をひっくり返してシャッフル演出
handleGuess()入力欄の文字列を大文字化して正解と比較、スコア加算/フィードバック表示、次ラウンド呼出
endGame()ゲーム画面を非表示、終了画面を表示し最終スコアを更新

改造のポイント

  • 単語リスト追加
    WORDS 配列に単語を追加すれば出題語が増加。多様なジャンルに対応可能。
  • カード枚数変更
    単語長に応じて .card サイズを動的調整し、長い単語も見切れずに表示できるように。
  • アニメーション強化
    CSS keyframes や JavaScript でカードの回転速度・色変化をカスタムして、より派手に。
  • 制限時間機能
    各ラウンドにタイマーを設置し、時間内に回答しないと不正解扱いにして緊張感をアップ。
  • ハイスコア記録
    localStorage に正解数やクリアタイムを保存し、タイトル画面でハイスコアを表示。

アドバイス:カードをクリック式にしたり、ドラッグ&ドロップで文字を並べ替えるインタラクションを追加すると、より直感的でゲーム性の高いUIになります。さらに、正解時にアニメーションや効果音を足すと、爽快感が増してリプレイ性が向上します!