記憶力テスト!どきどきカードマッチング
katayamawp
記憶力テスト:カードマッチング
マッチ数:
0
0
試行回数:
0
0
経過時間:
0
秒
0
秒
コード公開
CSS
#memory-match-game * {
box-sizing: border-box;
font-family: "Hiragino Kaku Gothic Pro", "メイリオ", sans-serif;
}
.game-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
}
.game-header {
text-align: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #8e9eab;
}
.game-header h2 {
color: #2c3e50;
margin-bottom: 15px;
font-size: 28px;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8);
}
.game-info {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.stats-container {
display: flex;
gap: 15px;
}
.stat-item,
.timer-container {
background-color: white;
padding: 8px 15px;
border-radius: 50px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
font-weight: bold;
}
.stat-label {
margin-right: 5px;
color: #34495e;
}
.stat-value {
color: #e74c3c;
}
.difficulty-selector {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.difficulty-btn {
background-color: #ecf0f1;
border: 2px solid #bdc3c7;
padding: 8px 15px;
border-radius: 50px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.difficulty-btn:hover {
background-color: #d5dbdb;
}
.difficulty-btn.selected {
background-color: #3498db;
color: white;
border-color: #2980b9;
box-shadow: 0 2px 8px rgba(52, 152, 219, 0.3);
}
.game-board {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-bottom: 20px;
perspective: 1000px;
}
.card {
position: relative;
height: 0;
padding-bottom: 125%;
cursor: pointer;
transform-style: preserve-3d;
transition: transform 0.6s;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.card:hover {
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
.card.flipped {
transform: rotateY(180deg);
}
.card-face {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
user-select: none;
}
.card-front {
background: linear-gradient(45deg, #2c3e50, #4ca1af);
color: white;
transform: rotateY(0deg);
}
.card-back {
background-color: white;
transform: rotateY(180deg);
}
.card.matched .card-back {
background-color: #d4f7d4;
box-shadow: 0 0 15px rgba(46, 204, 113, 0.5);
}
.game-controls {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 20px;
}
.btn {
padding: 10px 25px;
border-radius: 50px;
cursor: pointer;
font-size: 16px;
transition: all 0.3s ease;
font-weight: bold;
border: none;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.primary-btn {
background-color: #2ecc71;
color: white;
}
.primary-btn:hover {
background-color: #27ae60;
transform: translateY(-2px);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
}
.secondary-btn {
background-color: #3498db;
color: white;
}
.secondary-btn:hover {
background-color: #2980b9;
transform: translateY(-2px);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
}
.btn:active {
transform: translateY(1px);
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1);
}
.btn:disabled {
background-color: #95a5a6;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.message-box {
text-align: center;
padding: 20px;
border-radius: 10px;
font-weight: bold;
margin-top: 20px;
font-size: 18px;
animation: fadeIn 0.5s ease;
}
.success-message {
background-color: #d4f7d4;
color: #27ae60;
border: 2px solid #27ae60;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
.card.no-match {
animation: shake 0.5s;
}
@keyframes shake {
0%,
100% {
transform: translateX(0) rotateY(180deg);
}
20%,
60% {
transform: translateX(-5px) rotateY(180deg);
}
40%,
80% {
transform: translateX(5px) rotateY(180deg);
}
}
.card.match-found {
animation: pulse 0.5s;
}
.hidden {
display: none;
}
/* レスポンシブ対応 */
@media (max-width: 600px) {
.game-info {
flex-direction: column;
align-items: center;
}
.game-board {
grid-template-columns: repeat(3, 1fr);
}
.difficulty-btn {
font-size: 12px;
padding: 6px 12px;
}
}
JS
document.addEventListener('DOMContentLoaded', function() {
// ゲーム設定
const gameConfig = {
cardSymbols: ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🦁', '🐯', '🐨', '🐸', '🐵', '🐺', '🦄', '🐴', '🦋', '🐝', '🐌', '🐞', '🦀', '🐙', '🦐', '🐠'],
gridSizes: {
'3x2': {
cols: 3,
rows: 2
},
'4x3': {
cols: 4,
rows: 3
},
'4x4': {
cols: 4,
rows: 4
},
'6x4': {
cols: 6,
rows: 4
},
'6x6': {
cols: 6,
rows: 6
},
},
currentGrid: '4x3'
};
// DOM要素
const gameBoard = document.getElementById('gameBoard');
const startButton = document.getElementById('startButton');
const restartButton = document.getElementById('restartButton');
const matchesElement = document.getElementById('matches');
const attemptsElement = document.getElementById('attempts');
const timerElement = document.getElementById('timer');
const messageBox = document.getElementById('messageBox');
const difficultyButtons = document.querySelectorAll('.difficulty-btn');
// ゲーム状態
let gameState = {
cards: [],
flippedCards: [],
matches: 0,
attempts: 0,
gameActive: false,
timerInterval: null,
elapsedTime: 0,
totalPairs: 0,
};
// 難易度選択
difficultyButtons.forEach(btn => {
btn.addEventListener('click', () => {
if (!gameState.gameActive) {
difficultyButtons.forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
gameConfig.currentGrid = btn.dataset.grid;
initGame(); // 難易度変更時にゲームを再初期化
}
});
});
// ゲーム初期化
function initGame() {
// ゲームボードをクリア
gameBoard.innerHTML = '';
// グリッドサイズの設定
const {
cols,
rows
} = gameConfig.gridSizes[gameConfig.currentGrid];
gameBoard.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
// ゲーム状態をリセット
gameState.matches = 0;
gameState.attempts = 0;
gameState.elapsedTime = 0;
gameState.flippedCards = [];
gameState.totalPairs = (cols * rows) / 2;
// UI更新
matchesElement.textContent = gameState.matches;
attemptsElement.textContent = gameState.attempts;
timerElement.textContent = gameState.elapsedTime;
// カードの準備
const cardPairs = prepareCards(cols * rows);
gameState.cards = cardPairs;
// カードの配置
createCards(cardPairs);
// メッセージボックスを隠す
messageBox.classList.add('hidden');
// ボタン状態の更新
startButton.disabled = false;
restartButton.disabled = true;
}
// カードの準備(シンボルの選択とシャッフル)
function prepareCards(totalCards) {
const pairs = totalCards / 2;
const selectedSymbols = gameConfig.cardSymbols.slice(0, pairs);
// ペアを作成
let cardPairs = [];
selectedSymbols.forEach(symbol => {
cardPairs.push({
id: Math.random().toString(36).substr(2, 9),
symbol: symbol,
matched: false
}, {
id: Math.random().toString(36).substr(2, 9),
symbol: symbol,
matched: false
});
});
// シャッフル
for (let i = cardPairs.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[cardPairs[i], cardPairs[j]] = [cardPairs[j], cardPairs[i]];
}
return cardPairs;
}
// カードの作成と配置
function createCards(cards) {
cards.forEach((card, index) => {
const cardElement = document.createElement('div');
cardElement.className = 'card';
cardElement.dataset.id = card.id;
// カードの表面(裏側)
const cardFront = document.createElement('div');
cardFront.className = 'card-face card-front';
cardFront.textContent = '?';
// カードの裏面(表側、シンボルが見える)
const cardBack = document.createElement('div');
cardBack.className = 'card-face card-back';
cardBack.textContent = card.symbol;
cardElement.appendChild(cardFront);
cardElement.appendChild(cardBack);
// クリックイベント
cardElement.addEventListener('click', () => {
if (gameState.gameActive && !cardElement.classList.contains('flipped') && gameState.flippedCards.length < 2) {
flipCard(cardElement, card);
}
});
gameBoard.appendChild(cardElement);
});
}
// カードをめくる
function flipCard(cardElement, card) {
// カードをめくる
cardElement.classList.add('flipped');
gameState.flippedCards.push({
element: cardElement,
card: card
});
// 2枚めくった場合
if (gameState.flippedCards.length === 2) {
gameState.attempts++;
attemptsElement.textContent = gameState.attempts;
const card1 = gameState.flippedCards[0].card;
const card2 = gameState.flippedCards[1].card;
// マッチしたかチェック
if (card1.symbol === card2.symbol) {
// マッチした場合
gameState.matches++;
matchesElement.textContent = gameState.matches;
// カードを「マッチした」状態にする
gameState.flippedCards.forEach(fc => {
fc.element.classList.add('matched');
fc.element.classList.add('match-found');
fc.card.matched = true;
setTimeout(() => {
fc.element.classList.remove('match-found');
}, 500);
});
gameState.flippedCards = [];
// すべてのペアを見つけた場合
if (gameState.matches === gameState.totalPairs) {
endGame();
}
} else {
// マッチしなかった場合
gameState.flippedCards.forEach(fc => {
fc.element.classList.add('no-match');
});
// 少し待ってからカードを裏返す
setTimeout(() => {
gameState.flippedCards.forEach(fc => {
fc.element.classList.remove('flipped');
fc.element.classList.remove('no-match');
});
gameState.flippedCards = [];
}, 1000);
}
}
}
// ゲーム開始
function startGame() {
gameState.gameActive = true;
startButton.disabled = true;
restartButton.disabled = false;
// タイマー開始
gameState.timerInterval = setInterval(() => {
gameState.elapsedTime++;
timerElement.textContent = gameState.elapsedTime;
}, 1000);
// 難易度ボタンを無効化
difficultyButtons.forEach(btn => {
btn.disabled = true;
});
}
// ゲーム終了
function endGame() {
gameState.gameActive = false;
clearInterval(gameState.timerInterval);
// 難易度ボタンを有効化
difficultyButtons.forEach(btn => {
btn.disabled = false;
});
// 終了メッセージを表示
const accuracy = ((gameState.matches / gameState.attempts) * 100).toFixed(1);
messageBox.textContent = `おめでとうございます!全てのペアを見つけました!n時間: ${gameState.elapsedTime}秒 / 正確率: ${accuracy}%`;
messageBox.className = 'message-box success-message';
messageBox.classList.remove('hidden');
// ボタン状態を更新
startButton.disabled = true;
}
// イベントリスナー
startButton.addEventListener('click', startGame);
restartButton.addEventListener('click', initGame);
// 初期化
initGame();
});