ガイドメニュー

カードめくり:アルゴリズムと実装

「Concentration - 究極の音の記憶ゲーム」のカードめくりは、プレイヤーがカードをクリックして裏から表にめくり、emoji(例:🔥)と関連するサウンド(例:600Hzサイン波)を表示・再生する神経衰弱の中心的な機能です。2枚のカードをめくってペアを判定し、正解ならペアを保持、ハズレなら裏に戻します。このページでは、カードめくりのアルゴリズム、ソースコード、仕組み、他の機能との干渉回避策を詳しく解説します。また、めくりアニメーション(3D回転)のトランジションを、再生ボタンを使って実際に体験しながら比較できます。視覚と聴覚を活用して、カードめくりの技術を深く理解しましょう。

アルゴリズム

目的:最大2枚のカードをめくり、ペアを判定する。裏/表やマッチ状態を正確に管理し、滑らかな3Dアニメーションとサウンドで直感的な操作を提供する。他の機能(シャッフル、コンボ)とスムーズに連携し、競合を防ぐ。

手順

  1. 状態チェック:`flipCard`関数で、ゲームがアクティブか(`isGameActive`)、2枚未満か(`flippedCards`)、対象カードが未めくりかつ未マッチかを確認。
  2. めくりアニメーション:`flipped`クラスを追加し、CSSで3D回転(0.5秒)を適用。emojiを表示。
  3. サウンド再生:`soundMap`からemojiに対応するサウンド(波形、周波数、再生時間)を取得、`playWave`で再生。
  4. ペア判定:2枚めくったら`checkMatch`を呼び、`dataset.symbol`でペアを比較。正解なら`matched`クラスを追加、コンボを増加。ハズレなら1秒後に`flipped`クラスを削除。
  5. クリア判定:`matchedPairs`が全ペア数に達したらゲームクリア、演出とスコア保存を実行。

ソースコード

以下は、カードめくりの主要コード(`game.js`)です。完全版は`game.js`を参照してください。コードは`white-space: pre-wrap`で整形済み、改行やインデントがブラウザで正確に表示されます。

async function flipCard(card) { if (!isGameActive || flippedCards.length >= 2 || flippedCards.includes(card) || card.classList.contains("matched")) return; card.classList.add("flipped"); const sound = soundMap[card.dataset.symbol]; playWave(sound.type, sound.frequency, sound.duration, 0.2); flippedCards.push(card); if (flippedCards.length === 2) { attempts++; await checkMatch(); } } async function checkMatch() { const [card1, card2] = flippedCards; if (card1.dataset.symbol === card2.dataset.symbol) { card1.classList.add("matched"); card2.classList.add("matched"); matchedPairs++; comboCount++; flippedCards = []; message.textContent = "ナイス!もう一ペア!"; matchOverlay.style.display = "block"; setTimeout(() => matchOverlay.style.display = "none", 1000); if (comboCount >= 2) { playComboSound(comboCount); comboOverlay.textContent = `${comboCount} COMBO!`; comboOverlay.style.display = "block"; setTimeout(() => comboOverlay.style.display = "none", 1000); } if (matchedPairs === sizeConfigs[currentSize].pairs) { isGameActive = false; stopTimer(); const elapsed = (Date.now() - startTime) / 1000; const score = calculateScore(attempts, elapsed, sizeConfigs[currentSize].pairs, comboCount); message.textContent = `クリア!試行回数: ${attempts} | 時間: ${Math.floor(elapsed)}秒 | コンボ: ${comboCount} | スコア: ${score.score}`; clearOverlay.style.display = "block"; playVictorySound(); saveHighScore(currentSize, currentMode, score); setTimeout(() => clearOverlay.style.display = "none", 2000); } } else { comboCount = 0; setTimeout(() => { card1.classList.remove("flipped"); card2.classList.remove("flipped"); flippedCards = []; message.textContent = "ハズレ!もう一度!"; }, 1000); } }

CSSアニメーション(`guide.css`):3Dめくりを実現。

.card-sample { width: 50px; height: 50px; perspective: 1000px; } .card-inner { width: 100%; height: 100%; transition: transform 0.5s ease; transform-style: preserve-3d; position: relative; } .card-inner.flipped { transform: rotateY(180deg); } .card-front, .card-back { width: 100%; height: 100%; position: absolute; backface-visibility: hidden; display: flex; justify-content: center; align-items: center; border-radius: 6px; color: white; } .card-front { background-color: #007bff; } .card-back { background-color: #28a745; transform: rotateY(180deg); }

アニメーションを体感する

カードめくりの3D回転アニメーションは、CSSトランジションを活用し、プレイヤーに直感的な操作感を提供します。以下では、異なるトランジション効果(3D回転、スライド、フェード)を比較し、視覚的に理解できるようにサンプルを用意しました。ボタンをクリックして、各アニメーションを体験してください。表(青、🔥)と裏(緑、⭐)の変化に注目し、効果の違いを体感しましょう。各アニメーションには、めくり音(600Hzサイン波)が付加され、聴覚も刺激します。

🔥
🔥
🔥
  • 3D回転(ゲーム使用):カードをY軸で180度回転し、裏から表へ切り替える。ゲームの標準めくり効果で、直感的な操作感を提供。
  • スライド:カードを右にスライドして表示。シンプルで軽量な代替効果。
  • フェード:カードをフェードイン/アウト。柔らかい視覚効果だが、めくり感は弱い。

解説

  • 3D回転:`transform: rotateY(180deg)`と`transform-style: preserve-3d`で立体的なめくりを実現。`backface-visibility: hidden`で裏面を非表示にし、滑らかな視覚効果を提供。ゲームでは、直感性と視覚的魅力が最適。
  • スライド:`transform: translateX(100%)`で右に移動、シンプルで軽量。ゲームでは使用しないが、代替案として高速な表示が可能。
  • フェード:`opacity`を0~1で変化させ、柔らかい効果を演出。めくり感が弱いため、ゲームでは不採用。

ボタンを押してアニメーションを比較し、3D回転がゲームに最適な理由(直感性、視覚的魅力)を体感してください。この体験を通じて、トランジションの選択がユーザー体験にどう影響するかを理解できます。

機能の仕組み

データ構造

  • `cards`:全カードの配列、DOM要素を保持。各カードは`dataset.symbol`でemojiを、`classList`で状態(`flipped`、`matched`)を管理。
  • `flippedCards`:現在めくられたカード(最大2枚)の配列、ペア判定に使用。
  • `matchedPairs`:揃ったペア数、クリア判定に使用。
  • `soundMap`:emojiごとのサウンド設定(波形、周波数、再生時間)を保持。

制御フロー

  • 初期化:`createBoard`で`cards`と`flippedCards`をリセット、カードを動的に生成。
  • クリック処理:`flipCard`で状態チェック後、クラス追加とサウンド再生を実行。
  • ペア判定:`checkMatch`で2枚の`dataset.symbol`を比較、正解/ハズレを分岐。
  • クリア処理:全ペア揃いでスコア計算、保存、クリア演出をトリガー。

干渉回避の工夫

他の機能との競合を防ぐための対策を以下に示します:

  • シャッフルとの連携:`isGameActive`でシャッフル中はクリックを無効化、シャッフル後の表/裏状態を保持。めくりアニメーション(0.5秒)はシャッフルスライド(0.5秒)と独立。
  • コンボとの連携:`comboCount`は`checkMatch`内で更新、コンボ表示(`comboOverlay`)は独立した`div`でめくりアニメーションと層を分離。
  • サウンドとの連携:サウンド再生(0.3~0.4秒)は即時実行、めくりアニメーションと並行して問題なし。短い再生時間で重複を軽減。
  • バグ対策:初期の位置ずれ(`perspective`による視差)は`position: absolute`で解消、インデックスずれは`Array.from`で動的取得。

学習ポイント

  • 状態管理:`flippedCards`や`matchedPairs`でカード状態を正確に追跡する方法を学ぶ。
  • CSSトランジション:3D回転アニメーションの実装と、視覚効果の最適化を理解。
  • イベント駆動:クリックイベントのハンドリングと、状態チェックによる不正操作防止を習得。
  • 非同期処理:ハズレ時の待機(`setTimeout`)をPromiseで制御する方法を学ぶ。