【ゲーム】JavaScript:13 神経衰弱

 「🧠 神経衰弱(メモリー)ゲーム 🧠」は、裏向きのカードをめくって同じ数字をペアにして取り除き、制限時間内にすべてのペアを見つける、一人用の記憶力ゲームです。

遊び方と操作方法

  • 「▶️ ゲーム開始」ボタンを押すとカードがシャッフルされ、6×6 の盤面に裏向きで配置されます。
  • カードを1枚クリックして表向きにめくり、続けてもう1枚をめくります。
    ・数字が一致すればそのペアは盤面から消え、スコアが+10 加算されます。
    ・不一致なら1秒後に自動で裏向きに戻ります。
  • 残り時間は60秒。タイマーが0になるか、すべてのペアを見つけた時点でゲーム終了。

ルール

  • 全26ペア(数字1~13×4スートから2枚ずつ)のカードがランダム配置。
  • 同じ数字のペアを連続でめくると成功。
  • ペア成功でスコア+10、失敗でペナルティはなし。
  • 制限時間内にすべてのペアを揃えられればクリア、時間切れはゲームオーバー。

🎮ゲームプレイ

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

13 神経衰弱

素材のダウンロード

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

nervous_breakdownk_bg.pngnervous_breakdownk_start.png

ゲーム画面イメージ

プログラム全文(nervous_breakdown.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>
    /* =====================
       全体レイアウト & 背景
    ===================== */
    body {
      margin: 0;
      padding: 0;
      font-family: 'Verdana', sans-serif;
      background: url('nervous_breakdownk_bg.png') no-repeat center center fixed;
      background-size: cover;
      color: #fff;
      display: flex;
      flex-direction: column;
      align-items: center;
      min-height: 100vh;
    }
    /* =====================
       共通見出しスタイル
    ===================== */
    h1, h2 {
      background: rgba(0,0,0,0.7);
      padding: 10px 20px;
      border-radius: 8px;
      text-shadow: 2px 2px 6px #000;
    }
    h1 {
      font-size: 3rem;
      margin: 20px 0;
    }
    h2 {
      font-size: 1.8rem;
      margin: 10px 0;
    }

    /* =====================
       ホーム画面スタイル
    ===================== */
    #home-screen {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      flex: 1;
      width: 100%;
    }
    #home-screen img {
      width: 300px;
      height: auto;
      margin-bottom: 20px;
      border: 4px solid rgba(255,255,255,0.8);
      border-radius: 8px;
    }
    #home-screen p {
      background: rgba(0,0,0,0.8);
      padding: 12px 24px;
      border-radius: 6px;
      font-size: 1.8rem; /* フォントサイズ大きく */
      color: #fff;
      text-shadow: 1px 1px 4px #000;
      margin-bottom: 20px;
    }

    /* =====================
       ゲーム画面スタイル
    ===================== */
    #game-screen {
      display: none;
      flex-direction: column;
      align-items: center;
      flex: 1;
      width: 100%;
    }
    #game-screen.active {
      display: flex;
    }
    #controls {
      display: flex;
      gap: 20px;
      margin-bottom: 20px;
    }
    /* タイマー & スコア表示 */
    #timer, #score {
      background: rgba(0,0,0,0.7);
      padding: 8px 16px;
      border-radius: 6px;
      font-size: 1.4rem;
      text-shadow: 1px 1px 4px #000;
    }
    /* ゲームメッセージエリア */
    #gameMessage {
      background: rgba(255,0,0,0.8);
      padding: 10px 20px;
      border-radius: 6px;
      font-size: 1.4rem;
      text-shadow: 1px 1px 4px #000;
      margin: 10px 0;
      display: none;
    }

    /* =====================
       ボード & カード配置
    ===================== */
    #game-board {
      display: grid;
      grid-template-columns: repeat(6, 100px);
      gap: 15px;
      justify-content: center;
      margin-bottom: 20px;
    }
    .card {
      position: relative;
      width: 100px;
      height: 140px;
      perspective: 1000px;
      cursor: pointer;
    }
    .card-inner {
      width: 100%;
      height: 100%;
      position: absolute;
      transform-style: preserve-3d;
      transition: transform 0.5s;
    }
    .flipped .card-inner {
      transform: rotateY(180deg);
    }
    .front, .back {
      position: absolute;
      width: 100%;
      height: 100%;
      backface-visibility: hidden;
      border-radius: 8px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      box-shadow: 0 4px 8px rgba(0,0,0,0.3);
    }
    .front {
      background: #fff;
      color: #333;
      font-size: 1.6rem;
      font-weight: bold;
      transform: rotateY(180deg);
      line-height: 1.2;
    }
    .front .suit {
      font-size: 2rem;
      margin-bottom: 5px;
    }
    /* 裏面の可視性を高める */
    .back {
      background: rgba(0,102,204,0.8) url('') no-repeat center/cover;
      border: 2px solid rgba(255,255,255,0.8);
    }

    /* =====================
       ボタンスタイル
    ===================== */
    button {
      padding: 12px 24px;
      margin: 5px;
      font-size: 1.2rem;
      font-weight: bold;
      color: #fff;
      background: #2d89ef;
      border: none;
      border-radius: 8px;
      box-shadow: 0 4px 6px rgba(0,0,0,0.3);
      transition: background 0.3s, transform 0.2s;
      cursor: pointer;
    }
    button:hover {
      background: #1b5fa7;
      transform: scale(1.05);
    }
  </style>
</head>
<body>
  <!-- ホーム画面 -->
  <div id="home-screen">
    <img src="nervous_breakdownk_start.png" alt="スタート画面"/>
    <h1>🧠 神経衰弱ゲーム 🧠</h1>
    <p>同じ数字のカードをめくってペアを作ろう!</p>
    <button onclick="startGame()">▶️ ゲーム開始</button>
  </div>

  <!-- ゲーム画面 -->
  <div id="game-screen">
    <h1>🧠 神経衰弱 🧠</h1>
    <div id="controls">
      <div id="timer">⏱ 残り時間: 60秒</div>
      <div id="score">⭐️ スコア: 0</div>
    </div>
    <div id="gameMessage"></div>
    <div id="game-board"></div>
    <button onclick="goHome()">🏠 タイトルに戻る</button>
  </div>

  <script>
    // --- 要素取得 ---
    const homeScreen   = document.getElementById('home-screen');
    const gameScreen   = document.getElementById('game-screen');
    const board        = document.getElementById('game-board');
    const timerDisp    = document.getElementById('timer');
    const scoreDisp    = document.getElementById('score');
    const messageArea  = document.getElementById('gameMessage');

    // --- ゲーム変数 ---
    const suits        = ['♠️','♥️','♦️','♣️'];
    const numbers      = [...Array(13).keys()].map(n=>n+1);
    let deck           = [];
    let firstCard      = null;
    let lockBoard      = false;
    let timer          = null;
    let timeLeft       = 60;
    let score          = 0;
    let countdownId    = null;

    /**
     * ゲーム開始時
     */
    function startGame() {
      // ホーム→ゲーム画面
      homeScreen.style.display = 'none';
      gameScreen.classList.add('active');
      initGame();
      startTimer();
    }

    /**
     * タイトルに戻る
     */
    function goHome() {
      clearInterval(timer);
      clearInterval(countdownId);
      gameScreen.classList.remove('active');
      homeScreen.style.display = 'flex';
    }

    /**
     * 初期化
     */
    function initGame() {
      board.innerHTML         = '';
      firstCard               = null;
      lockBoard               = false;
      timeLeft                = 60;
      score                   = 0;
      scoreDisp.textContent   = `⭐️ スコア: ${score}`;
      messageArea.style.display = 'none';
      // デッキ生成:各数字ペアに違う2つのマークを割り当て
      deck = [];
      numbers.forEach(n => {
        const copy = suits.slice();
        // ランダムに2つのスートを選択
        for (let i=0; i<2; i++) {
          const idx = Math.floor(Math.random()*copy.length);
          deck.push({ value: n, suit: copy.splice(idx,1)[0] });
        }
      });
      // シャッフル
      deck.sort(() => 0.5 - Math.random());
      // カード配置
      deck.forEach(cardData => {
        const card = document.createElement('div');
        card.className     = 'card';
        card.dataset.value = cardData.value;
        card.dataset.suit  = cardData.suit;

        const inner = document.createElement('div');
        inner.className = 'card-inner';

        const front = document.createElement('div');
        front.className = 'front';
        // マーク + 数字
        const s = document.createElement('div');
        s.className = 'suit';
        s.textContent = cardData.suit;
        const v = document.createElement('div');
        v.textContent = cardData.value;
        front.append(s, v);

        const back = document.createElement('div');
        back.className = 'back';

        inner.append(front, back);
        card.append(inner);
        card.addEventListener('click', flipCard);
        board.append(card);
      });
      timerDisp.textContent = `⏱ 残り時間: ${timeLeft}秒`;
    }

    /**
     * タイマー開始
     */
    function startTimer() {
      clearInterval(timer);
      timer = setInterval(() => {
        timeLeft--;
        timerDisp.textContent = `⏱ 残り時間: ${timeLeft}秒`;
        if (timeLeft <= 0) {
          clearInterval(timer);
          showGameOver('⏰ 時間切れ!ゲームオーバー!');
        }
      }, 1000);
    }

    /**
     * カードをめくる
     */
    function flipCard() {
      if (lockBoard || this === firstCard) return;
      this.classList.add('flipped');
      if (!firstCard) {
        firstCard = this;
        return;
      }
      // 2枚目比較
      const a = this.dataset.value, b = firstCard.dataset.value;
      if (a === b) {
        setTimeout(() => {
          this.classList.add('hidden');
          firstCard.classList.add('hidden');
          score += 10;
          scoreDisp.textContent = `⭐️ スコア: ${score}`;
          resetBoard();
          checkWin();
        }, 500);
      } else {
        lockBoard = true;
        setTimeout(() => {
          this.classList.remove('flipped');
          firstCard.classList.remove('flipped');
          resetBoard();
        }, 1000);
      }
    }

    /**
     * リセット
     */
    function resetBoard() {
      [ firstCard, lockBoard ] = [ null, false ];
    }

    /**
     * クリア判定
     */
    function checkWin() {
      const hiddenCount = document.querySelectorAll('.card.hidden').length;
      if (hiddenCount === deck.length) {
        clearInterval(timer);
        showGameOver(`🎉 クリア!おめでとう!スコア: ${score}`);
      }
    }

    /**
     * ゲームオーバー/クリアメッセージ & 10秒後タイトルへ
     */
    function showGameOver(text) {
      let cnt = 10;
      messageArea.style.display = 'block';
      messageArea.textContent = `${text} ${cnt}秒後にタイトルへ戻ります`;
      countdownId = setInterval(() => {
        cnt--;
        if (cnt > 0) {
          messageArea.textContent = `${text} ${cnt}秒後にタイトルへ戻ります`;
        } else {
          clearInterval(countdownId);
          goHome();
        }
      }, 1000);
    }
  </script>
</body>
</html>

アルゴリズムの流れ

ステップ関数/命令内容
ゲーム開始startGame()ホーム画面非表示・ゲーム画面表示・initGame()startTimer() 呼び出し
タイトルに戻るgoHome()タイマー・カウントダウン停止・画面切り替え
初期化initGame()デッキ生成・シャッフル・カード配置・変数リセット・UI 初期状態更新
タイマー開始startTimer()1 秒ごとに timeLeft-- → タイマー表示更新 → 0 秒で showGameOver() 呼び出し
カードめくりflipCard()カード裏→表面に回転アニメ → 1枚目 or 2枚目を区別 → 成否で処理分岐
ボード状態リセットresetBoard()firstCardlockBoard をクリア
クリア判定checkWin()全カードが hidden クラス付きか確認 → クリアなら showGameOver() 呼び出し
ゲームオーバー表示showGameOver(text)メッセージ表示 → 10秒後に goHome() でタイトル画面に自動遷移

関数の役割

関数名役割
startGame()タイトル→ゲーム画面への切換え、初期化&タイマー開始
goHome()ゲーム中断してホーム画面に戻す(タイマー・カウント停止)
initGame()カードデータ生成(各数字2枚×4スート)→シャッフル→盤面にカード要素を配置
startTimer()制限時間のカウントダウン管理。0秒でゲームオーバー
flipCard()ユーザークリックでカードをめくるロジック。ペア判定→成功/失敗処理
resetBoard()1ペア処理後または失敗後に内部状態をクリア
checkWin()すべてのペアが揃ったかを確認し、クリア時に結果表示
showGameOver()クリア/ゲームオーバー共通の演出。メッセージを出し、数秒後に自動でタイトルに戻す

改造のポイント

  • 難易度調整:カード枚数(例:4×4、8×8)、制限時間を変えて難易度アップ/ダウン。
  • テーマ切替:トランプ以外のイラスト(動物、キャラクターなど)のカードに差し替え。
  • スコア機能拡張:ペア発見までの時間でボーナス、連続正解ボーナスやハイスコア保存機能を追加。
  • サウンドエフェクト:めくり音、ペア成功音、失敗音などで遊び心を演出。
  • マルチプレイヤーモード:複数人で交互にめくり合う対戦モードを実装。
  • 視覚効果:カードのゆれアニメ、クリアアニメーション、背景エフェクトでさらに華やかに。

 この基本実装をベースに、ぜひオリジナル要素を盛り込んで、楽しい「神経衰弱」アプリを完成させてください!