242 lines
8.9 KiB
JavaScript
Executable File
242 lines
8.9 KiB
JavaScript
Executable File
class Game2048 {
|
|
constructor() {
|
|
this.size = 4;
|
|
this.grid = [];
|
|
this.score = 0;
|
|
this.gameWon = false;
|
|
this.gameOver = false;
|
|
this.moved = false;
|
|
this.stats = { moves: 0, startTime: null, gameTime: 0, maxTile: 2, mergeCount: 0 };
|
|
|
|
this.initializeGrid();
|
|
this.updateDisplay();
|
|
this.addRandomTile();
|
|
this.addRandomTile();
|
|
this.updateDisplay();
|
|
this.bindEvents();
|
|
this.startTimer();
|
|
}
|
|
|
|
initializeGrid() {
|
|
this.grid = [];
|
|
for (let i = 0; i < this.size; i++) {
|
|
this.grid[i] = [];
|
|
for (let j = 0; j < this.size; j++) {
|
|
this.grid[i][j] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
addRandomTile() {
|
|
const emptyCells = [];
|
|
for (let i = 0; i < this.size; i++) {
|
|
for (let j = 0; j < this.size; j++) {
|
|
if (this.grid[i][j] === 0) emptyCells.push({ x: i, y: j });
|
|
}
|
|
}
|
|
if (emptyCells.length > 0) {
|
|
const cell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
|
|
const value = Math.random() < 0.9 ? 2 : 4;
|
|
this.grid[cell.x][cell.y] = value;
|
|
this.createTileElement(cell.x, cell.y, value, true);
|
|
}
|
|
}
|
|
|
|
createTileElement(x, y, value, isNew = false) {
|
|
const container = document.getElementById('tile-container');
|
|
const tile = document.createElement('div');
|
|
tile.className = `tile tile-${value}`;
|
|
if (isNew) tile.classList.add('tile-new');
|
|
tile.textContent = value;
|
|
tile.style.left = `${y * 25}%`;
|
|
tile.style.top = `${x * 25}%`;
|
|
tile.dataset.x = x;
|
|
tile.dataset.y = y;
|
|
tile.dataset.value = value;
|
|
container.appendChild(tile);
|
|
setTimeout(() => tile.classList.remove('tile-new'), 200);
|
|
}
|
|
|
|
updateDisplay() {
|
|
const container = document.getElementById('tile-container');
|
|
container.innerHTML = '';
|
|
for (let i = 0; i < this.size; i++) {
|
|
for (let j = 0; j < this.size; j++) {
|
|
if (this.grid[i][j] !== 0) this.createTileElement(i, j, this.grid[i][j]);
|
|
}
|
|
}
|
|
document.getElementById('score').textContent = this.score;
|
|
}
|
|
|
|
move(direction) {
|
|
if (this.gameOver) return;
|
|
this.moved = false;
|
|
|
|
switch (direction) {
|
|
case 'up': this.moveUp(); break;
|
|
case 'down': this.moveDown(); break;
|
|
case 'left': this.moveLeft(); break;
|
|
case 'right': this.moveRight(); break;
|
|
}
|
|
|
|
if (this.moved) {
|
|
this.stats.moves++;
|
|
this.addRandomTile();
|
|
this.updateDisplay();
|
|
|
|
if (this.isGameWon() && !this.gameWon) {
|
|
this.gameWon = true;
|
|
this.showEndScreen('你赢了!🎉');
|
|
} else if (this.isGameOver()) {
|
|
this.gameOver = true;
|
|
this.showEndScreen('游戏结束!');
|
|
}
|
|
}
|
|
}
|
|
|
|
moveLeft() {
|
|
for (let i = 0; i < this.size; i++) {
|
|
const row = this.grid[i].filter(v => v !== 0);
|
|
const merged = [];
|
|
for (let j = 0; j < row.length - 1; j++) {
|
|
if (row[j] === row[j + 1] && !merged[j] && !merged[j + 1]) {
|
|
row[j] *= 2; this.score += row[j]; this.stats.mergeCount++;
|
|
this.stats.maxTile = Math.max(this.stats.maxTile, row[j]);
|
|
row[j + 1] = 0; merged[j] = true;
|
|
}
|
|
}
|
|
const newRow = row.filter(v => v !== 0);
|
|
while (newRow.length < this.size) newRow.push(0);
|
|
for (let j = 0; j < this.size; j++) {
|
|
if (this.grid[i][j] !== newRow[j]) this.moved = true;
|
|
this.grid[i][j] = newRow[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
moveRight() {
|
|
for (let i = 0; i < this.size; i++) {
|
|
const row = this.grid[i].filter(v => v !== 0);
|
|
const merged = [];
|
|
for (let j = row.length - 1; j > 0; j--) {
|
|
if (row[j] === row[j - 1] && !merged[j] && !merged[j - 1]) {
|
|
row[j] *= 2; this.score += row[j]; this.stats.mergeCount++;
|
|
this.stats.maxTile = Math.max(this.stats.maxTile, row[j]);
|
|
row[j - 1] = 0; merged[j] = true;
|
|
}
|
|
}
|
|
const newRow = row.filter(v => v !== 0);
|
|
while (newRow.length < this.size) newRow.unshift(0);
|
|
for (let j = 0; j < this.size; j++) {
|
|
if (this.grid[i][j] !== newRow[j]) this.moved = true;
|
|
this.grid[i][j] = newRow[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
moveUp() {
|
|
for (let j = 0; j < this.size; j++) {
|
|
const col = [];
|
|
for (let i = 0; i < this.size; i++) { if (this.grid[i][j] !== 0) col.push(this.grid[i][j]); }
|
|
const merged = [];
|
|
for (let i = 0; i < col.length - 1; i++) {
|
|
if (col[i] === col[i + 1] && !merged[i] && !merged[i + 1]) {
|
|
col[i] *= 2; this.score += col[i]; this.stats.mergeCount++;
|
|
this.stats.maxTile = Math.max(this.stats.maxTile, col[i]);
|
|
col[i + 1] = 0; merged[i] = true;
|
|
}
|
|
}
|
|
const newCol = col.filter(v => v !== 0);
|
|
while (newCol.length < this.size) newCol.push(0);
|
|
for (let i = 0; i < this.size; i++) {
|
|
if (this.grid[i][j] !== newCol[i]) this.moved = true;
|
|
this.grid[i][j] = newCol[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
moveDown() {
|
|
for (let j = 0; j < this.size; j++) {
|
|
const col = [];
|
|
for (let i = 0; i < this.size; i++) { if (this.grid[i][j] !== 0) col.push(this.grid[i][j]); }
|
|
const merged = [];
|
|
for (let i = col.length - 1; i > 0; i--) {
|
|
if (col[i] === col[i - 1] && !merged[i] && !merged[i - 1]) {
|
|
col[i] *= 2; this.score += col[i]; this.stats.mergeCount++;
|
|
this.stats.maxTile = Math.max(this.stats.maxTile, col[i]);
|
|
col[i - 1] = 0; merged[i] = true;
|
|
}
|
|
}
|
|
const newCol = col.filter(v => v !== 0);
|
|
while (newCol.length < this.size) newCol.unshift(0);
|
|
for (let i = 0; i < this.size; i++) {
|
|
if (this.grid[i][j] !== newCol[i]) this.moved = true;
|
|
this.grid[i][j] = newCol[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
isGameWon() {
|
|
for (let i = 0; i < this.size; i++)
|
|
for (let j = 0; j < this.size; j++)
|
|
if (this.grid[i][j] === 2048) return true;
|
|
return false;
|
|
}
|
|
|
|
isGameOver() {
|
|
for (let i = 0; i < this.size; i++)
|
|
for (let j = 0; j < this.size; j++) {
|
|
if (this.grid[i][j] === 0) return false;
|
|
const c = this.grid[i][j];
|
|
if ((i > 0 && this.grid[i-1][j] === c) || (i < this.size-1 && this.grid[i+1][j] === c) ||
|
|
(j > 0 && this.grid[i][j-1] === c) || (j < this.size-1 && this.grid[i][j+1] === c))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
showEndScreen(text) {
|
|
const message = document.getElementById('game-message');
|
|
message.className = 'game-message ' + (this.gameWon ? 'game-won' : 'game-over');
|
|
message.style.display = 'flex';
|
|
message.querySelector('p').innerHTML =
|
|
`${text}<br><span style="font-size:16px;opacity:0.8;">` +
|
|
`得分 ${this.score} · 步数 ${this.stats.moves} · ` +
|
|
`最大方块 ${this.stats.maxTile} · 用时 ${this.stats.gameTime}秒</span>`;
|
|
}
|
|
|
|
restart() {
|
|
this.score = 0;
|
|
this.gameWon = false;
|
|
this.gameOver = false;
|
|
this.moved = false;
|
|
this.stats = { moves: 0, startTime: null, gameTime: 0, maxTile: 2, mergeCount: 0 };
|
|
this.initializeGrid();
|
|
this.addRandomTile();
|
|
this.addRandomTile();
|
|
this.updateDisplay();
|
|
document.getElementById('game-message').style.display = 'none';
|
|
this.startTimer();
|
|
}
|
|
|
|
startTimer() {
|
|
this.stats.startTime = Date.now();
|
|
if (this.timerInterval) clearInterval(this.timerInterval);
|
|
this.timerInterval = setInterval(() => {
|
|
if (!this.gameOver && this.stats.startTime) {
|
|
this.stats.gameTime = Math.floor((Date.now() - this.stats.startTime) / 1000);
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
bindEvents() {
|
|
document.getElementById('retry-btn').addEventListener('click', () => this.restart());
|
|
}
|
|
}
|
|
|
|
let game;
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
game = new Game2048();
|
|
window.game2048 = game;
|
|
});
|