2048小游戏实现
将创建一个完整的2048小游戏,包含游戏核心逻辑和美观的用户界面。
设计思路
- 4x4网格布局
- 响应式设计,适配不同设备
- 分数显示和最高分记录
- 键盘控制(方向键)和触摸滑动支持
- 游戏状态提示(胜利/失败)
完整代码实现
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2048小游戏</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Arial Rounded MT Bold', 'Arial', sans-serif;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
max-width: 500px;
width: 100%;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
h1 {
color: #776e65;
font-size: 60px;
font-weight: 700;
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
}
.scores-container {
display: flex;
gap: 10px;
}
.score-box {
background: #bbada0;
color: white;
padding: 10px 15px;
border-radius: 6px;
text-align: center;
min-width: 100px;
position: relative;
}
.score-box:after {
content: attr(data-label);
display: block;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
color: #eee4da;
}
.score {
font-size: 22px;
font-weight: bold;
}
.game-intro {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.game-explanation {
color: #776e65;
font-size: 16px;
line-height: 1.4;
max-width: 250px;
}
.restart-button {
background: #8f7a66;
color: #f9f6f2;
border: none;
border-radius: 6px;
padding: 12px 20px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
transition: all 0.2s;
}
.restart-button:hover {
background: #7c6b5a;
transform: scale(1.05);
}
.game-container {
background: #bbada0;
border-radius: 6px;
padding: 15px;
position: relative;
box-shadow: 0 10px 30px rgba(0,0,0,0.15);
}
.grid-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 10px;
background: rgba(238, 228, 218, 0.35);
border-radius: 6px;
padding: 10px;
position: relative;
touch-action: none;
}
.grid-cell {
background: rgba(238, 228, 218, 0.35);
border-radius: 4px;
height: 0;
padding-bottom: 100%;
}
.tile-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 10px;
}
.tile {
position: absolute;
width: calc(25% - 12.5px);
height: calc(25% - 12.5px);
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
font-size: 35px;
font-weight: bold;
transition: all 0.15s ease;
z-index: 10;
}
.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: 30px; }
.tile-256 { background: #edcc61; color: #f9f6f2; font-size: 30px; }
.tile-512 { background: #edc850; color: #f9f6f2; font-size: 30px; }
.tile-1024 { background: #edc53f; color: #f9f6f2; font-size: 25px; }
.tile-2048 { background: #edc22e; color: #f9f6f2; font-size: 25px; }
.tile-super { background: #3c3a32; color: #f9f6f2; font-size: 20px; }
.game-message {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(238, 228, 218, 0.73);
border-radius: 6px;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
}
.game-message.game-won,
.game-message.game-over {
display: flex;
}
.game-message p {
font-size: 50px;
font-weight: bold;
color: #776e65;
margin-bottom: 20px;
}
.instructions {
margin-top: 30px;
color: #776e65;
text-align: center;
line-height: 1.6;
font-size: 16px;
}
@media (max-width: 520px) {
.header {
flex-direction: column;
align-items: flex-start;
}
h1 {
font-size: 50px;
margin-bottom: 15px;
}
.game-intro {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.tile {
font-size: 24px;
}
.tile-128, .tile-256, .tile-512 {
font-size: 22px;
}
.tile-1024, .tile-2048 {
font-size: 18px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>2048</h1>
<div class="scores-container">
<div class="score-box" data-label="分数">
<div class="score" id="score">0</div>
</div>
<div class="score-box" data-label="最高分">
<div class="score" id="best-score">0</div>
</div>
</div>
</div>
<div class="game-intro">
<div class="game-explanation">
合并相同数字的方块,得到2048方块!
</div>
<button class="restart-button" id="restart-button">新游戏</button>
</div>
<div class="game-container">
<div class="grid-container">
<!-- 4x4网格 -->
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="tile-container" id="tile-container"></div>
<div class="game-message" id="game-message">
<p id="message-text"></p>
<button class="restart-button" id="keep-playing-button">继续游戏</button>
</div>
</div>
</div>
<div class="instructions">
<p><strong>游戏指南:</strong>使用键盘方向键(↑, ↓, ←, →)或触摸屏滑动移动方块。当两个相同数字的方块碰撞时,它们会合并成它们的和!</p>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 游戏常量
const GRID_SIZE = 4;
const CELL_SIZE = 20; // 百分比
const CELL_GAP = 2; // 百分比
// 游戏状态
let grid = [];
let score = 0;
let bestScore = localStorage.getItem('bestScore') || 0;
let gameOver = false;
let gameWon = false;
// DOM元素
const gridContainer = document.querySelector('.grid-container');
const tileContainer = document.getElementById('tile-container');
const scoreDisplay = document.getElementById('score');
const bestScoreDisplay = document.getElementById('best-score');
const restartButton = document.getElementById('restart-button');
const gameMessage = document.getElementById('game-message');
const messageText = document.getElementById('message-text');
const keepPlayingButton = document.getElementById('keep-playing-button');
// 初始化游戏
function initGame() {
// 重置游戏状态
grid = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(0));
tileContainer.innerHTML = '';
score = 0;
gameOver = false;
gameWon = false;
// 更新UI
updateScore();
gameMessage.className = 'game-message';
// 添加初始方块
addRandomTile();
addRandomTile();
// 渲染游戏板
render();
}
// 添加随机方块
function addRandomTile() {
const emptyCells = [];
// 查找所有空格子
for (let row = 0; row < GRID_SIZE; row++) {
for (let col = 0; col < GRID_SIZE; col++) {
if (grid[row][col] === 0) {
emptyCells.push({ row, col });
}
}
}
// 如果有空格子,随机选择一个并添加2或4
if (emptyCells.length > 0) {
const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
grid[randomCell.row][randomCell.col] = Math.random() < 0.9 ? 2 : 4;
}
}
// 渲染游戏板
function render() {
tileContainer.innerHTML = '';
for (let row = 0; row < GRID_SIZE; row++) {
for (let col = 0; col < GRID_SIZE; col++) {
const value = grid[row][col];
if (value !== 0) {
const tile = document.createElement('div');
tile.className = `tile tile-${value}`;
tile.textContent = value;
// 设置位置
tile.style.left = `${col * (CELL_SIZE + CELL_GAP) + CELL_GAP}%`;
tile.style.top = `${row * (CELL_SIZE + CELL_GAP) + CELL_GAP}%`;
tileContainer.appendChild(tile);
}
}
}
}
// 移动方块
function move(direction) {
if (gameOver) return;
let moved = false;
let newGrid = JSON.parse(JSON.stringify(grid));
// 根据方向处理移动
switch (direction) {
case 'up':
for (let col = 0; col < GRID_SIZE; col++) {
const column = [];
for (let row = 0; row < GRID_SIZE; row++) {
if (newGrid[row][col] !== 0) {
column.push(newGrid[row][col]);
}
}
// 合并相同数字
for (let i = 0; i < column.length - 1; i++) {
if (column[i] === column[i + 1]) {
column[i] *= 2;
score += column[i];
column.splice(i + 1, 1);
}
}
// 更新列
for (let row = 0; row < GRID_SIZE; row++) {
newGrid[row][col] = row < column.length ? column[row] : 0;
}
}
break;
case 'down':
for (let col = 0; col < GRID_SIZE; col++) {
const column = [];
for (let row = GRID_SIZE - 1; row >= 0; row--) {
if (newGrid[row][col] !== 0) {
column.push(newGrid[row][col]);
}
}
// 合并相同数字
for (let i = 0; i < column.length - 1; i++) {
if (column[i] === column[i + 1]) {
column[i] *= 2;
score += column[i];
column.splice(i + 1, 1);
}
}
// 更新列
for (let row = GRID_SIZE - 1; row >= 0; row--) {
newGrid[row][col] = (GRID_SIZE - 1 - row) < column.length ? column[GRID_SIZE - 1 - row] : 0;
}
}
break;
case 'left':
for (let row = 0; row < GRID_SIZE; row++) {
const rowData = [];
for (let col = 0; col < GRID_SIZE; col++) {
if (newGrid[row][col] !== 0) {
rowData.push(newGrid[row][col]);
}
}
// 合并相同数字
for (let i = 0; i < rowData.length - 1; i++) {
if (rowData[i] === rowData[i + 1]) {
rowData[i] *= 2;
score += rowData[i];
rowData.splice(i + 1, 1);
}
}
// 更新行
for (let col = 0; col < GRID_SIZE; col++) {
newGrid[row][col] = col < rowData.length ? rowData[col] : 0;
}
}
break;
case 'right':
for (let row = 0; row < GRID_SIZE; row++) {
const rowData = [];
for (let col = GRID_SIZE - 1; col >= 0; col--) {
if (newGrid[row][col] !== 0) {
rowData.push(newGrid[row][col]);
}
}
// 合并相同数字
for (let i = 0; i < rowData.length - 1; i++) {
if (rowData[i] === rowData[i + 1]) {
rowData[i] *= 2;
score += rowData[i];
rowData.splice(i + 1, 1);
}
}
// 更新行
for (let col = GRID_SIZE - 1; col >= 0; col--) {
newGrid[row][col] = (GRID_SIZE - 1 - col) < rowData.length ? rowData[GRID_SIZE - 1 - col] : 0;
}
}
break;
}
// 检查是否有移动
moved = JSON.stringify(grid) !== JSON.stringify(newGrid);
if (moved) {
grid = newGrid;
addRandomTile();
updateScore();
render();
checkGameStatus();
}
}
// 检查游戏状态
function checkGameStatus() {
// 检查是否达到2048
for (let row = 0; row < GRID_SIZE; row++) {
for (let col = 0; col < GRID_SIZE; col++) {
if (grid[row][col] === 2048 && !gameWon) {
gameWon = true;
messageText.textContent = '恭喜你赢了!';
gameMessage.className = 'game-message game-won';
}
}
}
// 检查是否有空格子
for (let row = 0; row < GRID_SIZE; row++) {
for (let col = 0; col < GRID_SIZE; col++) {
if (grid[row][col] === 0) {
return;
}
}
}
// 检查是否可以移动
for (let row = 0; row < GRID_SIZE; row++) {
for (let col = 0; col < GRID_SIZE; col++) {
const value = grid[row][col];
// 检查右侧
if (col < GRID_SIZE - 1 && grid[row][col + 1] === value) {
return;
}
// 检查下方
if (row < GRID_SIZE - 1 && grid[row + 1][col] === value) {
return;
}
}
}
// 如果没有空格子且无法移动,游戏结束
gameOver = true;
messageText.textContent = '游戏结束!';
gameMessage.className = 'game-message game-over';
}
// 更新分数
function updateScore() {
scoreDisplay.textContent = score;
// 更新最高分
if (score > bestScore) {
bestScore = score;
localStorage.setItem('bestScore', bestScore);
}
bestScoreDisplay.textContent = bestScore;
}
// 键盘控制
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'ArrowUp':
move('up');
break;
case 'ArrowDown':
move('down');
break;
case 'ArrowLeft':
move('left');
break;
case 'ArrowRight':
move('right');
break;
}
});
// 触摸控制
let touchStartX, touchStartY;
gridContainer.addEventListener('touchstart', (event) => {
touchStartX = event.touches[0].clientX;
touchStartY = event.touches[0].clientY;
event.preventDefault();
});
gridContainer.addEventListener('touchmove', (event) => {
event.preventDefault();
});
gridContainer.addEventListener('touchend', (event) => {
if (!touchStartX || !touchStartY) return;
const touchEndX = event.changedTouches[0].clientX;
const touchEndY = event.changedTouches[0].clientY;
const dx = touchEndX - touchStartX;
const dy = touchEndY - touchStartY;
// 确定主要移动方向
if (Math.abs(dx) > Math.abs(dy)) {
// 水平移动
if (dx > 0) {
move('right');
} else {
move('left');
}
} else {
// 垂直移动
if (dy > 0) {
move('down');
} else {
move('up');
}
}
// 重置触摸起点
touchStartX = null;
touchStartY = null;
});
// 按钮事件
restartButton.addEventListener('click', initGame);
keepPlayingButton.addEventListener('click', () => {
gameMessage.className = 'game-message';
});
// 初始化游戏
initGame();
bestScoreDisplay.textContent = bestScore;
});
</script>
</body>
</html>
功能说明
-
游戏核心功能:
- 4x4网格布局
- 方块移动和合并逻辑
- 随机生成新方块(90%概率生成2,10%概率生成4)
- 分数计算(每次合并累加得分)
- 最高分记录(使用localStorage保存)
-
游戏状态:
- 胜利条件:创建出2048方块
- 失败条件:棋盘填满且无法继续合并
- 游戏结束后可重新开始
-
控制方式:
- 键盘方向键(↑, ↓, ←, →)
- 触摸屏滑动操作(支持移动设备)
-
UI设计:
- 响应式布局,适配不同屏幕尺寸
- 不同数字的方块有不同的颜色
- 分数显示和最高分记录
- 游戏状态提示(胜利/失败)
使用说明
- 使用键盘方向键或触摸屏滑动来移动方块
- 当两个相同数字的方块碰撞时,它们会合并成它们的和
- 每次移动后,会在空白位置随机生成一个新的方块(2或4)
- 游戏目标:创建出一个2048方块
- 当无法再移动方块时,游戏结束
这个2048小游戏包含了所有核心功能,界面美观,操作流畅,适合在桌面和移动设备上使用。