JavaScript - 神経衰弱ゲームの作り方 Part 2 - カードをめくって揃える
JavaScript で作る神経衰弱ゲームのコードを完成させます。今回の Part 2 では、クリックでカードを2枚選ぶ方法と、数字が揃った場合と揃わなかった場合で処理を分ける方法を紹介します。
カードを2枚めくって数字を揃えよう
Part 1 に引き続き、JavaScript で作る神経衰弱ゲームのコードを解説します。
- Part 1 では、JavaScript で生成したカードに数字を割り振り、ゲームに使うカードを用意しました。
- Part 2(この記事)では、カードをクリックしてめくれるようにし、数字が揃ったかどうかをチェックします。全部を揃え終わったあとも繰り返し遊べるようにして、ゲームを完成させます。
この記事を読むと分かること
forEach()
addEventListener()
this
キーワードif…else
条件分岐classList.contains()
classList.add()
classList.remove()
setTimeout()
サンプルプロジェクト
改めて、神経衰弱ゲームの動きを確認しておきましょう。
- クリックでカードをめくる
- 一度にめくれるのは2枚まで
- 数字が揃ったら、そのカードはクリックに反応しなくなる
- 数字が揃わなかったら、カードは伏せられる
Part 1 で解説したコード
以下は、神経衰弱ゲームの作り方 Part 1 で解説したコードです。今回は、ここに追加していくコードについて説明していきますよ。
// カードをシャッフルするコード
const numbers = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6];
const cardNumbers = numbers.length;
let cards = null;
// カードを生成する
function createCards() {
const gameContainer = document.querySelector('.game-container');
for (let i = 0; i < cardNumbers; i++) {
const card = document.createElement('div');
card.classList.add('game-card');
gameContainer.appendChild(card);
}
cards = document.querySelectorAll('.game-card');
}
// 配列の数字をシャッフルする
function shuffleNumbers() {
for (let i = cardNumbers - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[numbers[i], numbers[j]] = [numbers[j], numbers[i]];
}
// すべてのカードに数字を割り振る
cards.forEach((card, i) => {
card.textContent = numbers[i]
});
}
createCards();
shuffleNumbers();
Part 1 でカードの準備ができたので、Part 2 ではカードをクリックしてめくれるようにしていきます。JavaScript で神経衰弱ゲームを作るときは、条件分岐の if
文を複数回使います。if
文の基本は以下の記事で解説しているので、あわせてご覧ください。
変数
最初に、変数を追加しましょう。
play
:カードをめくれるかどうかを表す変数です。初めはtrue
で、カードをめくれることを示します。(2枚目を選んだ後は、それ以上カードをめくれないようにするためにfalse
にします)firstCard
/secondCard
:クリックしてめくった2枚のカードを格納する変数です。それぞれをnull
で初期化し、空っぽであることを示します。matchedCount
:揃った数を数える変数です。初めは0
です。
const numbers = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6];
const cardNumbers = numbers.length;
let cards = null;
// カードを2枚までめくれるかどうか(trueまたはfalse)
let play = true;
// カード1枚目
let firstCard = null;
// カード2枚目
let secondCard = null;
// 揃った数
let matchedCount = 0;
クリックしてカードを2枚めくる - flipCard
変数を用意できたら、カードをクリックして2枚までめくれるようにする関数 flipCard
を定義していきます。
// クリックしてカードを2枚めくる
function flipCard() {
// 1. すべてのカードにクリックイベントを追加する
// 2. カードを2枚までめくれるかどうかを確認する
// 3. カードがめくられていないことを確認してめくる
// 4. めくったカードが1枚目か2枚目かで処理を分ける
// 5. カードをクリックできないようにする
}
1. すべてのカードにクリックイベントを追加する
ユーザーのクリックにカードが反応するようにしましょう。すべてのカードにクリックイベントを追加にするために、forEach()
メソッドを使用します。
cards
:生成したすべてのカードを参照します。forEach()
:すべてのカードに対して処理を行います。card
:処理の対象となっているカードを表す変数です。
function flipCard() {
// 1. すべてのカードにクリックイベントを追加する
cards.forEach(card => {
// 2. カードを2枚までめくれるかどうかを確認する
// 3. カードがめくられていないことを確認してめくる
// 4. めくったカードが1枚目か2枚目かで処理を分ける
// 5. カードをクリックできないようにする
});
}
addEventListener()
メソッドを使って、ユーザーがカードをクリックしときに関数が実行されるようにします。
(関数は、アロー関数ではなくfunction
キーワードを使用して書いていきます)
function flipCard() {
// 1. すべてのカードにクリックイベントを追加する
cards.forEach(card => {
// クリックイベントを追加
card.addEventListener('click', function() {
// 2. カードを2枚までめくれるかどうかを確認する
// 3. カードがめくられていないことを確認してめくる
// 4. めくったカードが1枚目か2枚目かで処理を分ける
// 5. カードをクリックできないようにする
});
});
}
続いて、クリックしたときに実行する処理です。3つの if
文でカードの状態をチェックしながら、1枚目、2枚目とカードをめくれるようにしていきますよ。
2. カードを2枚までめくれるかどうかを確認する
神経衰弱ゲームでは、2枚ずつカードをめくって数字を揃えていきます。クリックにすべてのカードが反応して次々にめくることができてしまうと、ゲームになりませんね。これを防ぐために、まずは、カードを2枚までめくれるかどうかの確認をしましょう。そのために使うのが、変数 play
です。
- 「変数
play
がtrue
であれば」という条件をif
文に指定して、一度に2枚までめくれるようにします。
function flipCard() {
cards.forEach(card => {
card.addEventListener('click', function() {
// 2. カードを2枚までめくれるかどうかを確認する
if(play) {
// 3. カードがめくられていないことを確認してめくる
// 4. めくったカードが1枚目か2枚目かで処理を分ける
// 5. カードをクリックできないようにする
}
});
});
}
3. カードがめくられていないことを確認してめくる
次に、めくれるのは伏せてあるカードだけで、既にめくってあるカードはクリックに反応しないようにします。伏せてあるかどうかは、そのカードに show
クラスがついているかいないかで確認しますよ。使用するのは、classList.contains()
メソッドです。
- 「クリックしたカードに
show
クラスが含まれていないなら」という条件をif
文に指定して、まだめくっていないカードだけをめくれるようにします。
function flipCard() {
cards.forEach(card => {
card.addEventListener('click', function() {
if(play) {
// 3. カードがめくられていないことを確認してめくる
if(!this.classList.contains('show')) {
// 4. めくったカードが1枚目か2枚目かで処理を分ける
// 5. カードをクリックできないようにする
}
}
});
});
}
イベントハンドラー内で使われている this
は、イベントが発生して関数を実行している要素(クリックしたカード)を参照します。クリックしたときに実行する関数を function
キーワードを使って書いているのは、この this
を有効にするためです。(アロー関数では、この this
は正しく動きません)
さて、if
文の条件を満たす場合は、クリックしたカードが伏せてあるということですね。classList.add()
メソッドで、そのカードをめくった見た目に変更しましょう。
- クリックしたカードに
show
クラスを追加します。
function flipCard() {
cards.forEach(card => {
card.addEventListener('click', function() {
if(play) {
// 3. カードがめくられていないことを確認してめくる
if(!this.classList.contains('show')) {
// カードをめくる
this.classList.add('show');
// 4. めくったカードが1枚目か2枚目かで処理を分ける
// 5. カードをクリックできないようにする
}
}
});
});
}
4. めくったカードが1枚目か2枚目かで処理を分ける
続いて、クリックしてめくったカードを変数に保存します。後ほど、2枚のカードの数字が揃ったかどうかを、この変数の値でチェックできるようにするためです。めくったカードが1枚目か2枚目かによって if…else
文で処理を分け、それぞれ変数 firstCard
/secondCard
に保存しましょう。
はじめに、めくったカードが1枚目の場合です。1枚目を表す変数 firstCard
は null
で初期化したので、この段階では変数の中身は空っぽ(false
)です。
- 変数
firstCard
がtrue
ではないなら、 - 変数
firstCard
に、クリックしたカードを代入します。
function flipCard() {
cards.forEach(card => {
card.addEventListener('click', function() {
if(play) {
if(!this.classList.contains('show')) {
this.classList.add('show');
// 4. めくったカードが1枚目か2枚目かで処理を分ける
// 1枚目
if(!firstCard) {
firstCard = this;
} else {
// …
// 5. カードをクリックできないようにする
}
}
}
});
});
}
次は else
の部分です。変数 firstCard
に値が代入された後は if(!firstCard)
の条件が満たされなくなるので、else
内の処理を実行します。
- 変数
secondCard
に、クリックしたカードを代入します。
function flipCard() {
cards.forEach(card => {
card.addEventListener('click', function() {
if(play) {
if(!this.classList.contains('show')) {
this.classList.add('show');
// 4. めくったカードが1枚目か2枚目かで処理を分ける
if(!firstCard) {
firstCard = this;
// 2枚目
} else {
secondCard = this;
// 5. カードをクリックできないようにする
}
}
}
});
});
}
5. カードをクリックできないようにする
カードを2枚めくり終わったら、それ以上カードをめくれないようにして次の段階の処理へ移ります。続けて、else
の部分にコードを追加してください。
- 変数
play
をfalse
にします。 - 数字が揃ったかどうかをチェックする関数
matchCards
に移動します。
function flipCard() {
cards.forEach(card => {
card.addEventListener('click', function() {
if(play) {
if(!this.classList.contains('show')) {
this.classList.add('show');
if(!firstCard) {
firstCard = this;
} else {
secondCard = this;
// 5. カードをめくれないようにする
play = false;
// 数字が揃ったかどうかをチェックする
matchCards();
}
}
}
});
});
}
以上で、カードをクリックして2枚までめくれるようにする関数 flipCard
を定義できました。
Part 1 で解説したコードと合わせて関数 flipCard
を呼び出すと、カードを2枚めくることができますよ。(ただし、数字が揃ったかどうかをチェックする関数 matchCards
をまだ定義していないので、現状ではエラー「ReferenceError: Can't find variable: matchCards」が発生します)
数字が揃ったかどうかをチェックする - matchCards
ここからは、2枚目のカードを選んだ後に実行する関数 matchCards
を定義していきます。関数 matchCards
では、カードの数字が揃ったかどうか、カードをすべて揃え終わったかどうかによって処理を分けます。
// 数字が揃ったかどうかをチェックする
function matchCards() {
// 1. 数字が揃った場合
// (1)すべてのカードを揃え終えた場合
// (2)まだ揃っていないカードが残っている場合
// 2. 数字が揃わなかった場合
}
1. 数字が揃った場合
めくった2枚のカード firstCard
/secondCard
の数字が「揃った場合」と「揃わなかった場合」を、if…else
文で条件分岐しましょう。
まずは、数字が揃った場合の処理を書いていきますよ。2枚のカードの数字を textContent
プロパティで取得して比較し、数字が等しいことを if
文の条件とします。
- 1枚目
firstCard
と2枚目secondCard
の数字が等しいなら、 - 揃った数を数える変数
matchedCount
の値を1増やします。
function matchCards() {
// 1. 数字が揃った場合
if(firstCard.textContent === secondCard.textContent) {
// 揃った数を1増やす
matchedCount += 1;
// (1)すべてのカードを揃え終えた場合
// (2)まだ揃っていないカードが残っている場合
// 2.数字が揃わなかった場合
} else {
// …
}
}
数字が揃った場合は、さらに if…else
文で処理を分けます。「すべてのカードを揃え終えた場合」と「まだ揃っていないカードが残っている場合」です。
(1) すべてのカードを揃え終えた場合
すべてのカードを揃え終えたら、再びゲームを開始できるようにしましょう。
- 揃え終わったかどうかは、変数
matchedCount
の値で確認します。配列内の要素数cardNumbers
の半分(今回では6ペア)で揃え終わったということになります。 - 揃え終わったあとしばらく待ってからゲームを再開できるようにするために、
setTimeout()
メソッドで3秒待ちます。
function matchCards() {
if(firstCard.textContent === secondCard.textContent) {
matchedCount += 1;
// (1)すべてのカードを揃え終えた場合
// すべて揃ったことを確認する
if(matchedCount === cardNumbers / 2) {
// 3秒待つ
setTimeout(() => {
// …
}, 3000);
// (2)まだ揃っていないカードが残っている場合
} else {
// …
}
// 2. 数字が揃わなかった場合
} else {
// …
}
}
- カードを揃え終わったとき、すべてのカードはめくられている状態です。
forEach()
メソッドですべてのカードからshow
クラスを削除し、カードを伏せた見た目にします。クラスを削除するために使うのは、classList.remove()
メソッドです。 - 関数
shuffleNumbers
を呼び出し、数字をシャッフルして割り振りし直します。
function matchCards() {
if(firstCard.textContent === secondCard.textContent) {
matchedCount += 1;
// (1)すべてのカードを揃え終えた場合
if(matchedCount === cardNumbers / 2) {
setTimeout(() => {
// すべてのカードを伏せる
cards.forEach(card => {
card.classList.remove('show');
});
// 数字をシャッフルしてすべてのカードに割り振る
shuffleNumbers();
}, 3000);
// (2)まだ揃っていないカードが残っている場合
} else {
// …
}
// 2. 数字が揃わなかった場合
} else {
// …
}
}
- 変数
matchedCount
を0
にして、揃った数をリセットします。 - 関数
readyToFlip
を呼び出します。これは、カードをめくれるよう準備する関数で、このあと定義します。
function matchCards() {
if(firstCard.textContent === secondCard.textContent) {
matchedCount += 1;
// (1)すべてのカードを揃え終えた場合
if(matchedCount === cardNumbers / 2) {
setTimeout(() => {
cards.forEach(card => {
card.classList.remove('show');
});
shuffleNumbers();
// 揃った数をリセットする
matchedCount = 0;
// カードをめくれるよう準備する
readyToFlip();
}, 3000);
// (2)まだ揃っていないカードが残っている場合
} else {
// …
}
// 2. 数字が揃わなかった場合
} else {
// …
}
}
以上で、すべてのカードを揃え終えたあとゲームを再開できるようにするコードができました。
(2)まだ揃っていないカードが残っている場合
めくったカードの数字が揃ったとしても、まだ揃っていないカードが残っている場合はゲームを続行します。
- カードをめくれるよう準備する関数
readyToFlip
を呼び出します。
function matchCards() {
if(firstCard.textContent === secondCard.textContent) {
matchedCount += 1;
if(matchedCount === cardNumbers / 2) {
setTimeout(() => {
cards.forEach(card => {
card.classList.remove('show');
});
shuffleNumbers();
matchedCount = 0;
readyToFlip();
}, 3000);
// (2)まだ揃っていないカードが残っている場合
} else {
//カードをめくれるよう準備する
readyToFlip();
}
// 2. 数字が揃わなかった場合
} else {
// …
}
}
以上で、数字が揃った場合のコードができました。
2. 数字が揃わなかった場合
今度は、数字が揃わなかった場合です。めくったカードを裏返してゲームを続行できるようにしましょう。
- しばらく待ってから次のカードをめくれるようにするために、
setTimeout()
メソッドで 1.5 秒待ちます。 - めくった2枚のカードから
show
クラスを削除して、カードを伏せた見た目にします。 - カードをめくれるよう準備する関数
readyToFlip
を呼び出します。
function matchCards() {
if(firstCard.textContent === secondCard.textContent) {
matchedCount += 1;
if(matchedCount === cardNumbers / 2) {
setTimeout(() => {
cards.forEach(card => {
card.classList.remove('show');
});
shuffleNumbers();
matchedCount = 0;
readyToFlip();
}, 3000);
} else {
readyToFlip();
}
// 2. 数字が揃わなかった場合
} else {
// 1.5秒待つ
setTimeout(() => {
// めくったカードを伏せる
firstCard.classList.remove('show');
secondCard.classList.remove('show');
// カードをめくれるよう準備する
readyToFlip();
}, 1500);
}
}
以上で、数字が揃わなかった場合のコードができました。関数 matchCards
の定義はここまでです。
カードをめくれるよう準備する - readyToFlip
さて、ここからは、上で説明した関数 matchCards
の中にも出てきた readyToFlip
を定義していきますよ。readyToFlip
は、カードをめくれるよう準備する関数です。
- 変数
firstCard
/secondCard
をnull
にし、カードが1枚も選ばれていない状態にします。 - カードをめくれるかどうかを示す変数
play
をtrue
にして、カードを2枚までめくれるようにします。
// カードをめくれるよう準備する
function readyToFlip() {
firstCard = null;
secondCard = null;
play = true;
}
完成コード
Part 1 で解説したコードと合わせて、神経衰弱ゲームのコードが完成です。
//************************
// 神経衰弱ゲームのコード
//************************
// 使用する数字
const numbers = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6];
// 数字の数
const cardNumbers = numbers.length;
// 初めはカードは無し
let cards = null;
// 2枚までめくれるかどうか(trueまたはfalse)
let play = true;
// カード1枚目、カード2枚目
let firstCard = null;
let secondCard = null;
// 揃った数
let matchedCount = 0;
// カードを生成する
function createCards() {
const gameContainer = document.querySelector('.game-container');
//----- 配列の要素と同じ数だけ生成 -----
for (let i = 0; i < cardNumbers; i++) {
const card = document.createElement('div');
card.classList.add('game-card');
gameContainer.appendChild(card);
}
//----- 生成したすべてのカードを変数に格納 -----
cards = document.querySelectorAll('.game-card');
}
// 配列の数字をシャッフルする
function shuffleNumbers() {
//----- Fisher-Yates アルゴリズム ----------
for (let i = cardNumbers - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[numbers[i], numbers[j]] = [numbers[j], numbers[i]];
}
//----- すべてのカードに数字を割り振る ----------
cards.forEach((card, i) => {
card.textContent = numbers[i]
});
}
// クリックしてカードを2枚めくる
function flipCard() {
cards.forEach(card => {
card.addEventListener('click', function() {
//----- カードを2枚までめくれるなら ----------
if(play) {
//----- カードが伏せてあるなら ----------
if(!this.classList.contains('show')) {
this.classList.add('show');
//----- 1枚目または2枚目 ----------
if(!firstCard) {
firstCard = this;
} else {
secondCard = this;
play = false;
//----- 数字が揃ったかどうかをチェックする -----
matchCards();
}
}
}
});
});
}
// 数字が揃ったかどうかをチェックする
function matchCards() {
//----- 数字が揃った場合 ----------
if(firstCard.textContent === secondCard.textContent) {
matchedCount += 1;
//----- すべてのカードを揃え終えた場合 ----------
if(matchedCount === cardNumbers / 2) {
setTimeout(() => {
cards.forEach(card => {
card.classList.remove('show');
});
shuffleNumbers();
matchedCount = 0;
readyToFlip();
}, 3000);
//----- まだ揃っていないカードが残っている場合 ----------
} else {
readyToFlip();
}
//----- 数字が揃わなかった場合 ----------
} else {
setTimeout(() => {
firstCard.classList.remove('show');
secondCard.classList.remove('show');
readyToFlip();
}, 1500);
}
}
// カードをめくれるよう準備する
function readyToFlip() {
firstCard = null;
secondCard = null;
play = true;
}
createCards();
shuffleNumbers();
flipCard();
See the Pen JavaScript - Memory Game by Pyxofy (@pyxofy) on CodePen.
まとめ
今回は、JavaScript で神経衰弱ゲームを作る方法の Part 2 として、カードをクリックして2枚ずつめくり、数字が揃ったかどうかをチェックするコードを紹介しました。
カードをめくったり伏せたりするために、classList.add()
と classList.remove()
でクラスの付け外しをし、カードの見た目を切り替えました。また、一度にめくれるのは2枚までとしたり、数字が揃った場合と揃わなかった場合の処理を分けるために、if
文で条件分岐しました。配列に保存する数字の数を増やせばカードの数も増えるので、お好きなカードの枚数で神経衰弱ゲームを作ってみてくださいね。
最後まで読んでいただき、ありがとうございます。この記事をシェアしてくれると嬉しいです!
SNSで Pyxofy とつながりましょう! LinkedIn・ Threads・Bluesky・ Mastodon・ X (Twitter) @pyxofy・ Facebook