<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>2048 Game</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
body {
margin: 0;
background: #faf8ef;
font-family: 'Roboto', sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
padding: 20px;
user-select: none;
box-sizing: border-box;
}
.game-container {
margin-top: 40px;
background: #bbada0;
border-radius: 10px;
padding: 20px;
width: max-content;
box-shadow: 0 8px 15px rgba(0,0,0,0.2);
box-sizing: border-box;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.title {
font-size: 48px;
font-weight: 700;
color: #776e65;
user-select: none;
}
.scores-container {
display: flex;
gap: 10px;
}
.score-box {
background: #eee4da;
border-radius: 5px;
padding: 10px 15px;
text-align: center;
min-width: 70px;
box-shadow: inset 0 -3px 0 rgba(0,0,0,0.2);
}
.score-label {
font-size: 12px;
color: #776e65;
font-weight: 700;
margin-bottom: 5px;
}
.score-value {
font-size: 20px;
font-weight: 700;
color: #776e65;
}
.grid {
background: #cdc1b4;
border-radius: 10px;
padding: 15px;
width: 355px;
height: 355px;
overflow: hidden;
display: grid;
grid-template-columns: repeat(4, 70px);
grid-template-rows: repeat(4, 70px);
gap: 15px;
box-sizing: border-box;
}
.grid-cell {
background: #eee4da;
border-radius: 5px;
}
.tile {
/* Remove absolute positioning to align with grid cells */
width: 70px;
height: 70px;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
font-weight: 700;
font-size: 28px;
color: #776e65;
user-select: none;
transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease;
box-shadow: 0 2px 5px rgba(0,0,0,0.15);
}
.tile-2 { background: #eee4da; color: #776e65; }
.tile-4 { background: #ede0c8; color: #776e65; }
.tile-8 { background: #f2b179; color: #f9f6f2; }
.tile-16 { background: #f59563; color: #f9f6f2; }
.tile-32 { background: #f67c5f; color: #f9f6f2; }
.tile-64 { background: #f65e3b; color: #f9f6f2; }
.tile-128 { background: #edcf72; color: #f9f6f2; font-size: 22px; }
.tile-256 { background: #edcc61; color: #f9f6f2; font-size: 22px; }
.tile-512 { background: #edc850; color: #f9f6f2; font-size: 22px; }
.tile-1024 { background: #edc53f; color: #f9f6f2; font-size: 18px; }
.tile-2048 { background: #edc22e; color: #f9f6f2; font-size: 18px; }
.game-message {
position: absolute;
top: 50%;
left: 50%;
width: 300px;
height: 100px;
margin-left: -150px;
margin-top: -50px;
background: rgba(238, 228, 218, 0.73);
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
font-weight: 700;
color: #776e65;
z-index: 100;
user-select: none;
display: none;
}
.button-container {
margin-top: 15px;
display: flex;
justify-content: center;
}
button {
background: #8f7a66;
border: none;
border-radius: 5px;
color: #f9f6f2;
font-weight: 700;
font-size: 16px;
padding: 10px 20px;
cursor: pointer;
box-shadow: 0 3px 0 #6f5e4e;
transition: background-color 0.3s ease;
}
button:hover {
background: #9f8b7a;
}
@media (max-width: 400px) {
.game-container {
width: 90vw;
padding: 10px;
}
.grid {
grid-template-columns: repeat(4, 20vw);
grid-template-rows: repeat(4, 20vw);
gap: 4vw;
padding: 10px;
}
.grid-cell, .tile {
width: 20vw;
height: 20vw;
font-size: 6vw;
}
}
</style>
</head>
<body>
<div class="game-container" role="main" aria-label="2048 game">
<div class="header">
<div class="title">2048</div>
<div class="scores-container">
<div class="score-box">
<div class="score-label">SCORE</div>
<div id="score" class="score-value">0</div>
</div>
<div class="score-box">
<div class="score-label">BEST</div>
<div id="best-score" class="score-value">0</div>
</div>
</div>
</div>
<div class="grid" id="grid" aria-label="Game grid" role="grid">
<!-- Grid cells -->
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
<div class="grid-cell" role="gridcell"></div>
</div>
<div class="game-message" id="game-message" role="alert" aria-live="assertive"></div>
<div class="button-container">
<button id="restart-button" aria-label="Restart Game">New Game</button>
</div>
</div>
<script>
(() => {
const gridSize = 4;
const tileSize = 70;
const tileGap = 15;
const animationDuration = 200;
let grid = [];
let tiles = [];
let score = 0;
let bestScore = 0;
let gameOver = false;
let gameWon = false;
const gridElement = document.getElementById('grid');
const scoreElement = document.getElementById('score');
const bestScoreElement = document.getElementById('best-score');
const gameMessage = document.getElementById('game-message');
const restartButton = document.getElementById('restart-button');
// 初始化游戏状态和界面
function init() {
score = 0;
gameOver = false;
gameWon = false;
loadBestScore();
updateScore(0);
grid = createEmptyGrid();
tiles = [];
clearTiles();
addRandomTile();
addRandomTile();
updateUI();
gameMessage.style.display = 'none';
window.addEventListener('keydown', handleKeyDown);
}
// 创建一个空的4x4网格,所有格子初始化为0
function createEmptyGrid() {
let arr = [];
for (let i = 0; i < gridSize; i++) {
arr[i] = [];
for (let j = 0; j < gridSize; j++) {
arr[i][j] = 0;
}
}
return arr;
}
// 清除所有已有的数字方块元素
function clearTiles() {
const existingTiles = document.querySelectorAll('.tile');
existingTiles.forEach(t => t.remove());
}
// 在随机空白格子中添加一个新的数字方块(2或4)
function addRandomTile() {
let emptyCells = [];
for (let r = 0; r < gridSize; r++) {
for (let c = 0; c < gridSize; c++) {
if (grid[r][c] === 0) emptyCells.push({r, c});
}
}
if (emptyCells.length === 0) return false;
const {r, c} = emptyCells[Math.floor(Math.random() * emptyCells.length)];
grid[r][c] = Math.random() < 0.9 ? 2 : 4;
return true;
}
// 更新当前分数,并在需要时更新最高分
function updateScore(points) {
score += points;
scoreElement.textContent = score;
if (score > bestScore) {
bestScore = score;
saveBestScore();
}
bestScoreElement.textContent = bestScore;
}
// 将最高分保存到 localStorage
function saveBestScore() {
localStorage.setItem('2048-best-score', bestScore);
}
// 从 localStorage 加载最高分
function loadBestScore() {
const saved = localStorage.getItem('2048-best-score');
bestScore = saved ? parseInt(saved, 10) : 0;
bestScoreElement.textContent = bestScore;
}
// 根据当前网格状态更新界面显示
function updateUI() {
clearTiles();
for (let r = 0; r < gridSize; r++) {
for (let c = 0; c < gridSize; c++) {
if (grid[r][c] !== 0) {
createTile(r, c, grid[r][c]);
}
}
}
}
// 创建并定位一个数字方块元素
function createTile(row, col, value) {
const tile = document.createElement('div');
tile.classList.add('tile', `tile-${value}`);
// Use CSS grid positioning instead of absolute positioning
tile.style.gridRowStart = row + 1;
tile.style.gridColumnStart = col + 1;
tile.textContent = value;
gridElement.appendChild(tile);
}
// 处理键盘按键事件,实现上下左右移动
function handleKeyDown(e) {
if (gameOver || gameWon) return; // 游戏结束或胜利时忽略输入
let moved = false;
switch (e.key) {
case 'ArrowUp':
e.preventDefault();
moved = moveUp();
break;
case 'ArrowDown':
e.preventDefault();
moved = moveDown();
break;
case 'ArrowLeft':
e.preventDefault();
moved = moveLeft();
break;
case 'ArrowRight':
e.preventDefault();
moved = moveRight();
break;
default:
return;
}
if (moved) {
addRandomTile();
updateUI();
if (checkWin()) {
gameWon = true;
showGameMessage('You Win!');
} else if (checkGameOver()) {
gameOver = true;
showGameMessage('Game Over!');
}
}
}
// 向上移动数字方块并合并相同数字
function moveUp() {
let moved = false;
for (let c = 0; c < gridSize; c++) {
let col = [];
for (let r = 0; r < gridSize; r++) col.push(grid[r][c]);
let {newLine, points, changed} = slideAndCombine(col);
if (changed) moved = true;
for (let r = 0; r < gridSize; r++) grid[r][c] = newLine[r];
if (points > 0) updateScore(points);
}
return moved;
}
// 向下移动数字方块并合并相同数字
function moveDown() {
let moved = false;
for (let c = 0; c < gridSize; c++) {
let col = [];
for (let r = gridSize - 1; r >= 0; r--) col.push(grid[r][c]);
let {newLine, points, changed} = slideAndCombine(col);
if (changed) moved = true;
for (let r = gridSize - 1, i = 0; r >= 0; r--, i++) grid[r][c] = newLine[i];
if (points > 0) updateScore(points);
}
return moved;
}
// 向左移动数字方块并合并相同数字
function moveLeft() {
let moved = false;
for (let r = 0; r < gridSize; r++) {
let row = grid[r].slice();
let {newLine, points, changed} = slideAndCombine(row);
if (changed) moved = true;
grid[r] = newLine;
if (points > 0) updateScore(points);
}
return moved;
}
// 向右移动数字方块并合并相同数字
function moveRight() {
let moved = false;
for (let r = 0; r < gridSize; r++) {
let row = grid[r].slice().reverse();
let {newLine, points, changed} = slideAndCombine(row);
if (changed) moved = true;
grid[r] = newLine.reverse();
if (points > 0) updateScore(points);
}
return moved;
}
// 滑动并合并一行或一列数字方块,返回新的行/列,得分和是否发生变化
function slideAndCombine(line) {
let arr = line.filter(v => v !== 0);
let points = 0;
let changed = false;
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] === arr[i + 1]) {
arr[i] *= 2;
points += arr[i];
arr.splice(i + 1, 1);
arr.push(0);
changed = true;
}
}
while (arr.length < gridSize) arr.push(0);
if (!changed) {
for (let i = 0; i < gridSize; i++) {
if (arr[i] !== line[i]) {
changed = true;
break;
}
}
}
return {newLine: arr, points, changed};
}
// 检查游戏是否结束(无空格且无可合并方块)
function checkGameOver() {
for (let r = 0; r < gridSize; r++) {
for (let c = 0; c < gridSize; c++) {
if (grid[r][c] === 0) return false;
if (c < gridSize - 1 && grid[r][c] === grid[r][c + 1]) return false;
if (r < gridSize - 1 && grid[r][c] === grid[r + 1][c]) return false;
}
}
return true;
}
// 检查是否达到2048,游戏胜利
function checkWin() {
for (let r = 0; r < gridSize; r++) {
for (let c = 0; c < gridSize; c++) {
if (grid[r][c] === 2048) return true;
}
}
return false;
}
// 显示游戏结束或胜利信息
function showGameMessage(message) {
gameMessage.textContent = message;
gameMessage.style.display = 'flex';
}
restartButton.addEventListener('click', () => {
init();
});
init();
})();
</script>
</body>
</html>