はじめに
いよいよ本格的な冬になってきましたね。
外は寒くて家の中にいることも多くなったと思います。そこで今回は、カードゲームの定番の一つの神経衰弱をjsを使って書いていこうと思います。
ソースコード一覧
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>神経衰弱 - CPU対戦</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>神経衰弱 - CPU対戦</h1>
<p id="instructions">プレイヤー vs CPU!カードを2枚めくってペアを揃えましょう。</p>
<div id="score-board">
<span id="player-score">プレイヤー: 0</span> |
<span id="cpu-score">CPU: 0</span>
</div>
<div id="game-board" class="game-board"></div>
<button id="restart-button">リスタート</button>
<script src="script.js"></script>
</body>
</html>
javaScript
const symbols = ["🍎", "🍌", "🍇", "🍓", "🍍", "🍒"];
const cards = [...symbols, ...symbols]; // ペアを作成
const gameBoard = document.getElementById("game-board");
const playerScoreElement = document.getElementById("player-score");
const cpuScoreElement = document.getElementById("cpu-score");
const restartButton = document.getElementById("restart-button");
// 状態管理
let firstCard = null;
let secondCard = null;
let preventClick = false;
let matchedCount = 0;
let playerScore = 0;
let cpuScore = 0;
let isPlayerTurn = true; // プレイヤーのターン管理
// カードをシャッフルする関数
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
shuffle(cards);
// カード生成
function createCards() {
cards.forEach((symbol) => {
const card = document.createElement("div");
card.classList.add("card");
card.dataset.symbol = symbol;
card.addEventListener("click", () => {
if (preventClick || !isPlayerTurn || card.classList.contains("flipped") || card.classList.contains("matched")) {
return;
}
flipCard(card);
if (!firstCard) {
firstCard = card; // 最初のカード
} else {
secondCard = card; // 2枚目のカード
checkMatch();
}
});
gameBoard.appendChild(card);
});
}
// カードを裏返す
function flipCard(card) {
card.classList.add("flipped");
card.textContent = card.dataset.symbol;
}
// カードを裏返す(元に戻す)
function unflipCards(card1, card2) {
setTimeout(() => {
card1.classList.remove("flipped");
card2.classList.remove("flipped");
card1.textContent = "";
card2.textContent = "";
}, 1000);
}
// ペア判定
function checkMatch() {
if (firstCard.dataset.symbol === secondCard.dataset.symbol) {
firstCard.classList.add("matched");
secondCard.classList.add("matched");
// スコア加算
if (isPlayerTurn) {
playerScore++;
playerScoreElement.textContent = `プレイヤー: ${playerScore}`;
} else {
cpuScore++;
cpuScoreElement.textContent = `CPU: ${cpuScore}`;
}
matchedCount += 1;
// ゲーム終了判定
if (matchedCount === cards.length / 2) {
setTimeout(() => {
const winner = playerScore > cpuScore ? "プレイヤーの勝ち!" : "CPUの勝ち!";
alert(`${winner}`);
}, 500);
}
// ペアが揃った場合はターンを維持
resetTurn(false); // ターンを切り替えない
if (!isPlayerTurn) {
setTimeout(cpuTurn, 1000); // CPUがペアを当てたらもう一度カードをめくる
}
} else {
preventClick = true;
unflipCards(firstCard, secondCard);
setTimeout(() => {
resetTurn(true); // ターンを切り替える
}, 1000);
}
}
// ターンをリセット
function resetTurn(switchTurn) {
firstCard = null;
secondCard = null;
preventClick = false;
// ターンを切り替える場合のみフラグを変更
if (switchTurn) {
isPlayerTurn = !isPlayerTurn;
// CPUのターンの場合は自動で動作
if (!isPlayerTurn) {
setTimeout(cpuTurn, 1000);
}
}
}
// CPUのターン
function cpuTurn() {
const availableCards = Array.from(document.querySelectorAll(".card:not(.flipped):not(.matched)"));
if (availableCards.length < 2) return;
// ランダムで2枚のカードを選ぶ
const firstChoice = availableCards[Math.floor(Math.random() * availableCards.length)];
flipCard(firstChoice);
let secondChoice;
do {
secondChoice = availableCards[Math.floor(Math.random() * availableCards.length)];
} while (secondChoice === firstChoice);
setTimeout(() => {
flipCard(secondChoice);
firstCard = firstChoice;
secondCard = secondChoice;
checkMatch();
}, 1000);
}
// ゲームリセット
function resetGame() {
gameBoard.innerHTML = "";
firstCard = null;
secondCard = null;
preventClick = false;
matchedCount = 0;
playerScore = 0;
cpuScore = 0;
isPlayerTurn = true;
playerScoreElement.textContent = "プレイヤー: 0";
cpuScoreElement.textContent = "CPU: 0";
shuffle(cards);
createCards();
}
// 初期化
createCards();
restartButton.addEventListener("click", resetGame);
CSS
/* 基本スタイル */
body {
font-family: 'Arial', sans-serif;
text-align: center;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
h1 {
margin-top: 20px;
color: #333;
}
#instructions {
margin: 10px 0;
font-size: 16px;
color: #555;
}
/* ゲームボード */
.game-board {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 10px;
max-width: 600px;
margin: 20px auto;
}
/* カードスタイル */
.card {
width: 100px;
height: 150px;
background-color: #ccc;
border: 2px solid #333;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
color: transparent;
cursor: pointer;
transition: transform 0.3s, background-color 0.3s;
}
.card.flipped, .card.matched {
background-color: #fff;
color: #000;
cursor: default;
transform: rotateY(180deg);
}
/* リスタートボタン */
#restart-button {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
background-color: #007BFF;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
#restart-button:hover {
background-color: #0056b3;
}
/* スコア表示 */
#score-board {
margin-top: 10px;
font-size: 18px;
color: #333;
}
コードの解説
カードの生成をしている関数では、配列 cards をループして、それぞれのシンボルに対応するカードを1枚ずつ作成します。最初のカードをクリックすると firstCard にセットし、2枚目のカードをクリックすると secondCard にセットし、ペア判定を行います。さらに、生成されたカードをすべて gameBoard に追加し、プレイ可能な状態にします。
ペア判定をしている関数ではdataset.symbol を比較して、2枚のカードが同じシンボルであるかを判定しています。
CPUの行動を設定している関数では、random関数を使用し一枚目のめくるカードを決定し、二枚目のめくるカードを決めるときはdo文を使い一枚目のカードと被らないようにしています。
実行結果
スタート画面
実行中
ペアを当てた時
プレイヤー勝利時
CPU勝利時
まとめ
神経衰弱を作ってみて思ったことはカードのペア判定やCPUのカードのめくり方を定義することがとても難しいと感じました。
また、改善点として、CPUのカードのめくり方をめくられたことのあるカードの配置を記憶し、そのデータをもとにめくるカードを決めるといった機能の追加ができればさらにjsについての理解を深めることができると思いました。