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}
` +
`得分 ${this.score} · 步数 ${this.stats.moves} · ` +
`最大方块 ${this.stats.maxTile} · 用时 ${this.stats.gameTime}秒`;
}
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;
});