feat: 添加贪吃蛇和记忆配对纸牌游戏
- 贪吃蛇游戏: Canvas渲染、方向控制、碰撞检测、计分系统 - 记忆配对纸牌: 3D翻转动画、配对消除、计时器、胜利判定
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
class MemoryGame {
|
||||
constructor() {
|
||||
this.symbols = ['🍎', '🍊', '🍋', '🍇', '🍓', '🍒', '🥝', '🍑'];
|
||||
this.cards = [];
|
||||
this.flippedCards = [];
|
||||
this.matchedPairs = 0;
|
||||
this.moves = 0;
|
||||
this.timer = null;
|
||||
this.seconds = 0;
|
||||
this.isLocked = false;
|
||||
this.gameStarted = false;
|
||||
|
||||
this.boardEl = document.getElementById('game-board');
|
||||
this.movesEl = document.getElementById('moves');
|
||||
this.timerEl = document.getElementById('timer');
|
||||
this.pairsEl = document.getElementById('pairs');
|
||||
this.winModal = document.getElementById('win-modal');
|
||||
this.finalMovesEl = document.getElementById('final-moves');
|
||||
this.finalTimeEl = document.getElementById('final-time');
|
||||
|
||||
document.getElementById('restart-btn').addEventListener('click', () => this.init());
|
||||
document.getElementById('play-again-btn').addEventListener('click', () => {
|
||||
this.winModal.style.display = 'none';
|
||||
this.init();
|
||||
});
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// 重置状态
|
||||
this.cards = [];
|
||||
this.flippedCards = [];
|
||||
this.matchedPairs = 0;
|
||||
this.moves = 0;
|
||||
this.seconds = 0;
|
||||
this.isLocked = false;
|
||||
this.gameStarted = false;
|
||||
|
||||
// 清除计时器
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
// 更新UI
|
||||
this.movesEl.textContent = '0';
|
||||
this.timerEl.textContent = '0:00';
|
||||
this.pairsEl.textContent = '0/8';
|
||||
|
||||
// 生成卡牌
|
||||
this.generateCards();
|
||||
this.renderBoard();
|
||||
}
|
||||
|
||||
generateCards() {
|
||||
// 创建配对卡牌
|
||||
const cardPairs = [...this.symbols, ...this.symbols];
|
||||
// 洗牌 (Fisher-Yates shuffle)
|
||||
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]];
|
||||
}
|
||||
this.cards = cardPairs.map((symbol, index) => ({
|
||||
id: index,
|
||||
symbol: symbol,
|
||||
isFlipped: false,
|
||||
isMatched: false
|
||||
}));
|
||||
}
|
||||
|
||||
renderBoard() {
|
||||
this.boardEl.innerHTML = '';
|
||||
this.cards.forEach(card => {
|
||||
const cardEl = document.createElement('div');
|
||||
cardEl.className = 'card';
|
||||
cardEl.dataset.id = card.id;
|
||||
cardEl.innerHTML = `
|
||||
<div class="card-face card-back"></div>
|
||||
<div class="card-face card-front">${card.symbol}</div>
|
||||
`;
|
||||
cardEl.addEventListener('click', () => this.flipCard(card.id));
|
||||
this.boardEl.appendChild(cardEl);
|
||||
});
|
||||
}
|
||||
|
||||
flipCard(id) {
|
||||
const card = this.cards[id];
|
||||
const cardEl = this.boardEl.querySelector(`[data-id="${id}"]`);
|
||||
|
||||
// 防止重复点击或锁定状态
|
||||
if (this.isLocked || card.isFlipped || card.isMatched) return;
|
||||
|
||||
// 开始计时
|
||||
if (!this.gameStarted) {
|
||||
this.gameStarted = true;
|
||||
this.startTimer();
|
||||
}
|
||||
|
||||
// 翻牌
|
||||
card.isFlipped = true;
|
||||
cardEl.classList.add('flipped');
|
||||
this.flippedCards.push(card);
|
||||
|
||||
// 检查是否翻了两张
|
||||
if (this.flippedCards.length === 2) {
|
||||
this.moves++;
|
||||
this.movesEl.textContent = this.moves;
|
||||
this.checkMatch();
|
||||
}
|
||||
}
|
||||
|
||||
checkMatch() {
|
||||
const [card1, card2] = this.flippedCards;
|
||||
|
||||
if (card1.symbol === card2.symbol) {
|
||||
// 配对成功
|
||||
this.matchSuccess(card1, card2);
|
||||
} else {
|
||||
// 配对失败
|
||||
this.matchFail(card1, card2);
|
||||
}
|
||||
}
|
||||
|
||||
matchSuccess(card1, card2) {
|
||||
card1.isMatched = true;
|
||||
card2.isMatched = true;
|
||||
this.matchedPairs++;
|
||||
this.pairsEl.textContent = `${this.matchedPairs}/8`;
|
||||
|
||||
const cardEl1 = this.boardEl.querySelector(`[data-id="${card1.id}"]`);
|
||||
const cardEl2 = this.boardEl.querySelector(`[data-id="${card2.id}"]`);
|
||||
|
||||
// 延迟消除动画
|
||||
setTimeout(() => {
|
||||
cardEl1.classList.add('matched');
|
||||
cardEl2.classList.add('matched');
|
||||
}, 500);
|
||||
|
||||
this.flippedCards = [];
|
||||
|
||||
// 检查胜利
|
||||
if (this.matchedPairs === 8) {
|
||||
this.win();
|
||||
}
|
||||
}
|
||||
|
||||
matchFail(card1, card2) {
|
||||
this.isLocked = true;
|
||||
|
||||
// 延迟翻回
|
||||
setTimeout(() => {
|
||||
card1.isFlipped = false;
|
||||
card2.isFlipped = false;
|
||||
|
||||
const cardEl1 = this.boardEl.querySelector(`[data-id="${card1.id}"]`);
|
||||
const cardEl2 = this.boardEl.querySelector(`[data-id="${card2.id}"]`);
|
||||
cardEl1.classList.remove('flipped');
|
||||
cardEl2.classList.remove('flipped');
|
||||
|
||||
this.flippedCards = [];
|
||||
this.isLocked = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
startTimer() {
|
||||
this.timer = setInterval(() => {
|
||||
this.seconds++;
|
||||
const mins = Math.floor(this.seconds / 60);
|
||||
const secs = this.seconds % 60;
|
||||
this.timerEl.textContent = `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
win() {
|
||||
clearInterval(this.timer);
|
||||
|
||||
const mins = Math.floor(this.seconds / 60);
|
||||
const secs = this.seconds % 60;
|
||||
const timeStr = `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
|
||||
this.finalMovesEl.textContent = this.moves;
|
||||
this.finalTimeEl.textContent = timeStr;
|
||||
|
||||
setTimeout(() => {
|
||||
this.winModal.style.display = 'flex';
|
||||
}, 800);
|
||||
}
|
||||
}
|
||||
|
||||
// 启动游戏
|
||||
new MemoryGame();
|
||||
Reference in New Issue
Block a user