|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Memory Match</title> |
|
<style> |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
min-height: 100vh; |
|
background: linear-gradient(45deg, #1a1a1a, #2d2d2d); |
|
font-family: 'Segoe UI', sans-serif; |
|
color: #fff; |
|
} |
|
|
|
.game-container { |
|
padding: 2rem; |
|
} |
|
|
|
.controls { |
|
display: flex; |
|
gap: 2rem; |
|
margin-bottom: 2rem; |
|
align-items: center; |
|
} |
|
|
|
.stats { |
|
background: rgba(255, 255, 255, 0.1); |
|
padding: 1rem 2rem; |
|
border-radius: 10px; |
|
font-size: 1.2rem; |
|
} |
|
|
|
select { |
|
padding: 0.5rem 1rem; |
|
border-radius: 5px; |
|
background: #3a3a3a; |
|
color: #fff; |
|
border: none; |
|
cursor: pointer; |
|
} |
|
|
|
.grid { |
|
display: grid; |
|
gap: 1rem; |
|
perspective: 1000px; |
|
} |
|
|
|
.card { |
|
width: 100px; |
|
height: 100px; |
|
position: relative; |
|
transform-style: preserve-3d; |
|
transition: transform 0.6s; |
|
cursor: pointer; |
|
} |
|
|
|
.card.flipped { |
|
transform: rotateY(180deg); |
|
} |
|
|
|
.card-front, .card-back { |
|
position: absolute; |
|
width: 100%; |
|
height: 100%; |
|
backface-visibility: hidden; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-size: 2rem; |
|
border-radius: 10px; |
|
box-shadow: 0 0 10px rgba(0,0,0,0.3); |
|
} |
|
|
|
.card-front { |
|
background: linear-gradient(135deg, #4a90e2, #357abd); |
|
} |
|
|
|
.card-back { |
|
background: white; |
|
transform: rotateY(180deg); |
|
color: #333; |
|
} |
|
|
|
.matched { |
|
animation: match 0.5s ease-in-out; |
|
} |
|
|
|
@keyframes match { |
|
0%, 100% { transform: scale(1) rotateY(180deg); } |
|
50% { transform: scale(1.1) rotateY(180deg); } |
|
} |
|
|
|
button { |
|
padding: 0.5rem 1rem; |
|
border: none; |
|
border-radius: 5px; |
|
background: #4a90e2; |
|
color: white; |
|
cursor: pointer; |
|
transition: background 0.3s; |
|
} |
|
|
|
button:hover { |
|
background: #357abd; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="game-container"> |
|
<div class="controls"> |
|
<div class="stats"> |
|
<span>Score: <span id="score">0</span></span> |
|
<span style="margin-left: 1rem">Time: <span id="timer">0:00</span></span> |
|
</div> |
|
<select id="difficulty"> |
|
<option value="easy">Easy (4x4)</option> |
|
<option value="medium">Medium (6x6)</option> |
|
<option value="hard">Hard (8x8)</option> |
|
</select> |
|
<button id="newGame">New Game</button> |
|
</div> |
|
<div class="grid" id="grid"></div> |
|
</div> |
|
|
|
<script> |
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
|
|
|
const createSound = (frequency, type = 'sine') => { |
|
const oscillator = audioContext.createOscillator(); |
|
const gainNode = audioContext.createGain(); |
|
oscillator.type = type; |
|
oscillator.frequency.value = frequency; |
|
oscillator.connect(gainNode); |
|
gainNode.connect(audioContext.destination); |
|
return { oscillator, gainNode }; |
|
}; |
|
|
|
const playSound = (frequency, duration = 0.1) => { |
|
const sound = createSound(frequency); |
|
sound.gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); |
|
sound.oscillator.start(); |
|
sound.oscillator.stop(audioContext.currentTime + duration); |
|
}; |
|
|
|
class MemoryGame { |
|
constructor() { |
|
this.grid = document.getElementById('grid'); |
|
this.scoreElement = document.getElementById('score'); |
|
this.timerElement = document.getElementById('timer'); |
|
this.difficultySelect = document.getElementById('difficulty'); |
|
this.newGameBtn = document.getElementById('newGame'); |
|
|
|
this.cards = []; |
|
this.flippedCards = []; |
|
this.matchedPairs = 0; |
|
this.score = 0; |
|
this.timer = 0; |
|
this.timerInterval = null; |
|
|
|
this.setupEventListeners(); |
|
this.startNewGame(); |
|
} |
|
|
|
setupEventListeners() { |
|
this.newGameBtn.addEventListener('click', () => this.startNewGame()); |
|
this.difficultySelect.addEventListener('change', () => this.startNewGame()); |
|
} |
|
|
|
startNewGame() { |
|
this.grid.innerHTML = ''; |
|
this.cards = []; |
|
this.flippedCards = []; |
|
this.matchedPairs = 0; |
|
this.score = 0; |
|
this.scoreElement.textContent = '0'; |
|
clearInterval(this.timerInterval); |
|
this.timer = 0; |
|
this.updateTimer(); |
|
|
|
const difficulty = this.difficultySelect.value; |
|
const size = difficulty === 'easy' ? 4 : difficulty === 'medium' ? 6 : 8; |
|
|
|
this.grid.style.gridTemplateColumns = `repeat(${size}, 100px)`; |
|
|
|
const pairs = (size * size) / 2; |
|
const symbols = this.generateSymbols(pairs); |
|
this.createCards(symbols); |
|
|
|
this.startTimer(); |
|
} |
|
|
|
generateSymbols(pairs) { |
|
const emojis = ['๐จ', '๐ฎ', '๐ฒ', '๐ญ', '๐ช', '๐จ', '๐ฏ', '๐ฑ', '๐ณ', '๐ผ', |
|
'๐ต', '๐น', '๐บ', '๐ธ', '๐ป', '๐ฌ', '๐ค', '๐ง', '๐', '๐', |
|
'๐', '๐', '๐ฝ', '๐ฅ', '๐บ', '๐ท', '๐ธ', '๐น', '๐ผ', '๐', |
|
'๐', '๐', '๐', '๐', '๐ซ', '๐ ', '๐ก', '๐ข']; |
|
|
|
const selectedEmojis = [...emojis].sort(() => Math.random() - 0.5).slice(0, pairs); |
|
return [...selectedEmojis, ...selectedEmojis].sort(() => Math.random() - 0.5); |
|
} |
|
|
|
createCards(symbols) { |
|
symbols.forEach((symbol, index) => { |
|
const card = document.createElement('div'); |
|
card.className = 'card'; |
|
card.innerHTML = ` |
|
<div class="card-front">?</div> |
|
<div class="card-back">${symbol}</div> |
|
`; |
|
card.addEventListener('click', () => this.flipCard(card, symbol)); |
|
this.grid.appendChild(card); |
|
this.cards.push(card); |
|
}); |
|
} |
|
|
|
flipCard(card, symbol) { |
|
if (this.flippedCards.length === 2 || card.classList.contains('flipped') || |
|
card.classList.contains('matched')) return; |
|
|
|
card.classList.add('flipped'); |
|
playSound(440); |
|
|
|
this.flippedCards.push({ card, symbol }); |
|
|
|
if (this.flippedCards.length === 2) { |
|
this.checkMatch(); |
|
} |
|
} |
|
|
|
checkMatch() { |
|
const [card1, card2] = this.flippedCards; |
|
|
|
if (card1.symbol === card2.symbol) { |
|
setTimeout(() => { |
|
card1.card.classList.add('matched'); |
|
card2.card.classList.add('matched'); |
|
playSound(880, 0.2); |
|
this.matchedPairs++; |
|
this.score += 10; |
|
this.scoreElement.textContent = this.score; |
|
|
|
if (this.matchedPairs === this.cards.length / 2) { |
|
this.endGame(); |
|
} |
|
}, 500); |
|
} else { |
|
setTimeout(() => { |
|
card1.card.classList.remove('flipped'); |
|
card2.card.classList.remove('flipped'); |
|
playSound(220, 0.2); |
|
this.score = Math.max(0, this.score - 5); |
|
this.scoreElement.textContent = this.score; |
|
}, 1000); |
|
} |
|
|
|
this.flippedCards = []; |
|
} |
|
|
|
startTimer() { |
|
this.timerInterval = setInterval(() => { |
|
this.timer++; |
|
this.updateTimer(); |
|
}, 1000); |
|
} |
|
|
|
updateTimer() { |
|
const minutes = Math.floor(this.timer / 60); |
|
const seconds = this.timer % 60; |
|
this.timerElement.textContent = |
|
`${minutes}:${seconds.toString().padStart(2, '0')}`; |
|
} |
|
|
|
endGame() { |
|
clearInterval(this.timerInterval); |
|
setTimeout(() => { |
|
alert(`Congratulations! You won!\nScore: ${this.score}\nTime: ${this.timerElement.textContent}`); |
|
}, 500); |
|
} |
|
} |
|
|
|
new MemoryGame(); |
|
</script> |
|
</body> |
|
</html> |