【ゲーム】JavaScript:29 マスターマインドゲーム

 「🎯 マスターマインドゲーム 🎯」は、コンピューターが4色のピンを隠し持ち、その配列をプレイヤーが推理するクラシックなボードゲームです。プレイヤーは色の組み合わせをスロットにセットし、「ヒット(赤ピン)」と「ブロー(白ピン)」のヒントをもとに正解のコードを当てます。

遊び方・操作方法

  1. タイトル画面の「スタート 🚀」ボタンをクリック
  2. スロット(4つの丸枠)をクリックして選択
  3. 下のカラーパレットから色を選ぶと、選択中のスロットに色が入る
  4. 4つすべて埋めたら「判定 ✔️」ボタンをクリック
  5. ヒット数(色と位置が一致)とブロー数(色のみ一致)が赤・白の小さなピンで表示される
  6. ヒントをもとにスロットを塗り直し、正解を目指す
  7. 赤ヒットが4になるまで繰り返し
  8. クリア後に試行回数と評価コメントが表示される

ルール

  • コード長:4ピン
  • 使用色:6色(赤・緑・青・黄・紫・オレンジ)
  • 試行回数制限:なし(何回でも挑戦可能)
  • ヒント
    ・赤ピン…位置も色も正解
    ・白ピン…色は正解だが位置が異なる
  • 勝利条件:ヒットが4になるとクリア
  • 終了後:試行回数に応じて「天才ですね!」などの評価コメント

🎮ゲームプレイ

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

29 マスターマインドゲーム

素材のダウンロード

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

mastermind_title.pngmastermind_bg.png

ゲーム画面イメージ

プログラム全文(mastermind.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('mastermind_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: 600px; /* 幅設定 */
    }
    /* 非表示クラス */
    .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: 10px 5px;
      cursor: pointer;
      border: 2px solid #28a745;
      border-radius: 8px;
      background-color: #28a745;
      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: #218838; transform: translateY(-2px); }
    .btn:active { transform: translateY(0); box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
    /* パレットとスロット */
    #palette { display: flex; justify-content: center; gap: 10px; margin: 10px 0; }
    .color-btn { width: 40px; height: 40px; border-radius: 50%; cursor: pointer; border: 2px solid #333; }
    /* カラーオプション */
    .red    { background-color: #e74c3c; }
    .green  { background-color: #2ecc71; }
    .blue   { background-color: #3498db; }
    .yellow { background-color: #f1c40f; }
    .purple { background-color: #9b59b6; }
    .orange { background-color: #e67e22; }
    /* 推測スロット */
    #slots { display: flex; justify-content: center; gap: 10px; margin: 10px 0; }
    .slot { width: 40px; height: 40px; border: 2px dashed #333; border-radius: 50%; }
    .slot.filled { border-style: solid; }
    /* 履歴表示 */
    #history { margin-top: 15px; max-height: 200px; overflow-y: auto; }
    .row { display: flex; align-items: center; justify-content: center; margin-bottom: 8px; }
    .guess { display: flex; gap: 5px; margin-right: 15px; }
    .peg { width: 15px; height: 15px; display: inline-block; border-radius: 50%; margin: 2px; }
    .hit   { background-color: #e74c3c; } /* 赤ヒント */
    .blow  { background-color: #fff; border:1px solid #333; } /* 白ヒント */
    /* メッセージ */
    .message {
      margin-top: 10px;
      padding: 8px;
      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="mastermind_title.png" alt="マスターマインドタイトル" class="title-image">
    <h1>🎯 マスターマインドゲーム 🎯</h1>
    <!-- ゲーム説明 -->
    <p>色の組み合わせを推理して、4本のピンの配置を当てましょう。</p>
    <p>【ヒット】位置も色も正しいピンには赤いヒントピンが立ちます。</p>
    <p>【ブロー】色は正しいが位置が違うピンには白いヒントピンが立ちます。</p>
    <p>赤いヒントピンが4本立つまで挑戦!</p>
    <!-- ゲーム開始ボタン -->
    <button id="start-btn" class="btn">スタート 🚀</button>
  </div>

  <!-- ゲーム画面 -->
  <div id="game-screen" class="overlay hidden">
    <!-- 試行回数表示 -->
    <h2>試行回数: <span id="attempts">0</span></h2>
    <!-- スロット領域 -->
    <div id="slots"></div>
    <!-- カラーパレット -->
    <div id="palette"></div>
    <!-- クリア&判定ボタン -->
    <button id="clear-btn" class="btn">クリア</button>
    <button id="guess-btn" class="btn">判定 ✔️</button>
    <!-- 履歴表示 -->
    <div id="history"></div>
    <!-- 状態メッセージ -->
    <div id="message" class="message">ピンを選んでスロットを埋めてください</div>
  </div>

  <!-- 終了画面 -->
  <div id="end-screen" class="overlay hidden">
    <h2 id="end-title">結果発表</h2>
    <p id="eval-message"></p>
    <button id="restart-btn" class="btn">タイトルに戻る 🔄</button>
  </div>

  <script>
    // ===== 設定 =====
    const COLORS = ['red','green','blue','yellow','purple','orange']; // 選択可能な色リスト
    const CODE_LENGTH = 4; // コード長

    // ===== ゲーム状態 =====
    let secretCode = [];    // コンピュータが生成する秘密コード配列
    let attempts = 0;       // 試行回数カウント
    let currentGuess = [];  // プレイヤーの現在の予想配列
    let selectedSlot = null;// 選択中のスロット番号

    // ===== 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 clearBtn     = document.getElementById('clear-btn');
    const guessBtn     = document.getElementById('guess-btn');
    const restartBtn   = document.getElementById('restart-btn');
    const slotsDiv     = document.getElementById('slots');
    const paletteDiv   = document.getElementById('palette');
    const historyDiv   = document.getElementById('history');
    const messageDiv   = document.getElementById('message');
    const attemptsSpan = document.getElementById('attempts');
    const endTitle     = document.getElementById('end-title');
    const evalMessage  = document.getElementById('eval-message');

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

    /**
     * ゲーム開始処理
     */
    function startGame() {
      // 秘密コード生成
      secretCode = generateCode();
      // 状態初期化
      attempts = 0;
      attemptsSpan.textContent = attempts;
      historyDiv.innerHTML = '';
      messageDiv.textContent = 'ピンを選んでスロットを埋めてください';
      // UI初期化
      buildSlots();
      buildPalette();
      // 画面切り替え
      titleScreen.classList.add('hidden');
      endScreen.classList.add('hidden');
      gameScreen.classList.remove('hidden');
    }

    // DOM読み込み後にイベント登録
    document.addEventListener('DOMContentLoaded', () => {
      startBtn.addEventListener('click', startGame);        // スタート
      clearBtn.addEventListener('click', () => { 
        buildSlots(); messageDiv.textContent = 'クリアしました';
      });
      guessBtn.addEventListener('click', handleGuess);      // 判定
      restartBtn.addEventListener('click', showTitle);      // リスタート
      showTitle(); // 初期表示はタイトル
    });

    /**
     * 秘密コード(ランダム)を生成
     */
    function generateCode() {
      const arr = [];
      for (let i = 0; i < CODE_LENGTH; i++) {
        const idx = Math.floor(Math.random() * COLORS.length);
        arr.push(COLORS[idx]);
      }
      console.log('Secret Code:', arr); // デバッグ用ログ
      return arr;
    }

    /**
     * ブランクスロットを生成
     */
    function buildSlots() {
      slotsDiv.innerHTML = '';
      currentGuess = [];
      selectedSlot = null;
      for (let i = 0; i < CODE_LENGTH; i++) {
        const slot = document.createElement('div');
        slot.className = 'slot';
        slot.dataset.index = i;
        slot.addEventListener('click', () => selectSlot(i));
        slotsDiv.append(slot);
      }
    }

    /**
     * カラーパレットを生成
     */
    function buildPalette() {
      paletteDiv.innerHTML = '';
      COLORS.forEach(color => {
        const btn = document.createElement('div');
        btn.className = `color-btn ${color}`;
        btn.dataset.color = color;
        btn.addEventListener('click', () => chooseColor(color));
        paletteDiv.append(btn);
      });
    }

    /**
     * スロットを選択状態にする
     */
    function selectSlot(index) {
      selectedSlot = index;
      // 枠線で選択中を可視化
      document.querySelectorAll('.slot').forEach(s => s.style.borderColor = '#333');
      document.querySelector(`.slot[data-index='${index}']`).style.borderColor = '#007BFF';
    }

    /**
     * 選択したスロットに色を配置
     */
    function chooseColor(color) {
      if (selectedSlot === null) {
        messageDiv.textContent = 'まずスロットを選択してください';
        return;
      }
      const slot = document.querySelector(`.slot[data-index='${selectedSlot}']`);
      slot.style.backgroundColor = color;
      slot.classList.add('filled');
      currentGuess[selectedSlot] = color;
      // 次のスロット選択へ
      selectSlot((selectedSlot + 1) % CODE_LENGTH);
    }

    /**
     * プレイヤーの予想を判定
     */
    function handleGuess() {
      // 未配置チェック
      if (currentGuess.includes(undefined)) {
        messageDiv.textContent = 'すべてのスロットに色を配置してください';
        return;
      }
      attempts++; // 試行回数加算
      attemptsSpan.textContent = attempts;

      // ヒット&ブロー判定
      let codeCopy = secretCode.slice();
      let guessCopy = currentGuess.slice();
      let hit = 0, blow = 0;
      // ヒット判定(位置&色)
      guessCopy.forEach((g,i) => {
        if (g === codeCopy[i]) {
          hit++;
          codeCopy[i] = guessCopy[i] = null; // 二重カウント防止
        }
      });
      // ブロー判定(色のみ)
      guessCopy.forEach(g => {
        if (g && codeCopy.includes(g)) {
          blow++;
          codeCopy[codeCopy.indexOf(g)] = null;
        }
      });

      // 判定結果を履歴に表示
      const row = document.createElement('div'); row.className = 'row';
      const guessDiv = document.createElement('div'); guessDiv.className = 'guess';
      currentGuess.forEach(c => {
        const d = document.createElement('div');
        d.className = `color-btn ${c}`;
        d.style.width = d.style.height = '30px';
        d.style.border = '1px solid #333';
        guessDiv.append(d);
      }); row.append(guessDiv);
      for (let i = 0; i < hit; i++) { const p = document.createElement('div'); p.className = 'peg hit'; row.append(p); }
      for (let i = 0; i < blow; i++) { const p = document.createElement('div'); p.className = 'peg blow'; row.append(p); }
      historyDiv.prepend(row);

      // ゲーム終了判定
      if (hit === CODE_LENGTH) {
        endGame();
      } else {
        messageDiv.textContent = `ヒット: ${hit}、ブロー: ${blow}`;
      }
    }

    /**
     * ゲーム終了処理
     */
    function endGame() {
      gameScreen.classList.add('hidden');
      endScreen.classList.remove('hidden');
      endTitle.textContent = '🎉 正解! 🎉';
      // 評価コメント
      let comment = '';
      if (attempts <= 4) comment = '天才ですね!';
      else if (attempts <= 8) comment = '素晴らしいです!';
      else if (attempts <= 12) comment = '良い成績です!';
      else comment = 'もっと練習しましょう!';
      evalMessage.textContent = `${attempts} 回で正解しました。${comment}`;
    }
  </script>
</body>
</html>

アルゴリズムの流れ

ステップ処理内容主な関数/命令
1. タイトル表示タイトル画面だけ表示、他画面を隠すshowTitle()
2. ゲーム開始秘密コード生成、状態変数初期化、UIクリア、ゲーム画面表示startGame()
3. 秘密コード生成COLORS からランダムに 4 色を選択(重複可)generateCode()
4. スロット/パレット構築HTML要素を動的生成してクリックイベントを設定buildSlots(), buildPalette()
5. スロット選択・色配置スロットクリックで selectedSlot を設定→パレットで色を選ぶとスロット背景色に反映selectSlot(), chooseColor()
6. 推測判定currentGuesssecretCode をコピー→ヒット判定→ブロー判定→結果を履歴へ表示handleGuess()
7. 終了判定ヒット数が 4 でクリア → endGame() → 試行回数に応じた評価コメントを表示handleGuess()endGame()

関数の詳しい解説

関数名説明
showTitle()タイトル画面表示、他画面を非表示
startGame()ゲーム状態の初期化(秘密コード・試行回数・履歴など)、UIクリア、buildSlots()buildPalette()
generateCode()ランダムに 4 色選ぶ秘密コードを配列で返却
buildSlots()4 つの空スロットを動的に生成、クリックで選択可能に
buildPalette()カラーパレットを生成し、色選択時にスロットへ反映
selectSlot(i)スロットを選択中としてハイライト、selectedSlot に保存
chooseColor(c)選択中スロットに色を設定し、次のスロットを暗黙選択
handleGuess()ヒット/ブロー判定ロジック、履歴行の生成・挿入、終了判定
endGame()ゲーム画面を隠し、結果画面に切り替え、評価メッセージを生成

改造のポイント

  • 色数・コード長変更
    COLORS 配列の長さや CODE_LENGTH を変えるだけで、難易度を自在に調整可能です。
  • 重複禁止ルール
    秘密コード生成時に同じ色を使わないように splice で配列から削除すれば、本家ルールに近づきます。
  • ビジュアル演出強化
    ヒントピンをリアルに配置するミニボードを追加したり、優勝時にアニメーションを入れると演出UP。
  • 試行回数制限
    試行回数に上限(例:10 回)を設け、超過時は敗北とする仕組みも実装可能です。
  • ヒント機能
    「残り何色正解」「特定の色は含まれていない」など補助ヒントを追加して、学習モードを作るのも面白いです。
  • ランキング機能
    localStorage で最少試行回数やクリア時間を保存し、ハイスコアを表示する仕組みを入れるとリプレイ性が高まります。

アドバイス:秘密コードを伏せる代わりに「🔒」アイコンを使ったり、スロットをドラッグ&ドロップで埋めるインタラクションを加えると、より直感的で楽しい UI になります。また、敗北時に正解コードを一気にアニメーションで見せるなど、リッチな演出を検討してみてください!