カードめくり:アルゴリズムと実装
「Concentration - 究極の音の記憶ゲーム」のカードめくりは、プレイヤーがカードをクリックして裏から表にめくり、emoji(例:🔥)と関連するサウンド(例:600Hzサイン波)を表示・再生する神経衰弱の中心的な機能です。2枚のカードをめくってペアを判定し、正解ならペアを保持、ハズレなら裏に戻します。このページでは、カードめくりのアルゴリズム、ソースコード、仕組み、他の機能との干渉回避策を詳しく解説します。また、めくりアニメーション(3D回転)のトランジションを、再生ボタンを使って実際に体験しながら比較できます。視覚と聴覚を活用して、カードめくりの技術を深く理解しましょう。
アルゴリズム
目的:最大2枚のカードをめくり、ペアを判定する。裏/表やマッチ状態を正確に管理し、滑らかな3Dアニメーションとサウンドで直感的な操作を提供する。他の機能(シャッフル、コンボ)とスムーズに連携し、競合を防ぐ。
手順:
- 状態チェック:`flipCard`関数で、ゲームがアクティブか(`isGameActive`)、2枚未満か(`flippedCards`)、対象カードが未めくりかつ未マッチかを確認。
- めくりアニメーション:`flipped`クラスを追加し、CSSで3D回転(0.5秒)を適用。emojiを表示。
- サウンド再生:`soundMap`からemojiに対応するサウンド(波形、周波数、再生時間)を取得、`playWave`で再生。
- ペア判定:2枚めくったら`checkMatch`を呼び、`dataset.symbol`でペアを比較。正解なら`matched`クラスを追加、コンボを増加。ハズレなら1秒後に`flipped`クラスを削除。
- クリア判定:`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で制御する方法を学ぶ。