游戏概述
实现一个基本的2048游戏涉及综合运用HTML、CSS和JavaScript这三种关键技术。
HTML用于构建游戏的结构框架,包括游戏容器、网格布局以及可能的用户交互元素(如按钮或得分显示)。
CSS则负责美化游戏界面,通过样式表定义网格的样式、瓷砖的外观、动画效果以及整体布局的视觉呈现。
JavaScript则是游戏逻辑的核心,它处理用户的输入(如键盘事件监听),控制游戏状态的更新(如瓷砖的移动和合并),以及动态地更新DOM以反映游戏状态的变化。
通过这三者的紧密协作,可以创建出一个既美观又富有挑战性的2048游戏。
使用工具
本篇文章有用到GPT代码更正,国内可稳定使用,感兴趣的大佬可以试试。
传送门:ChatGPT-4o
实现步骤
HTML结构
代码部分:
<div id="gameContainer">
<div id="score">Score: 0</div>
<div id="gridContainer"></div>
<div id="gameOverContainer">
<div id="gameOver">Game Over!</div>
<button id="restartButton">Restart</button>
</div>
</div>
解释:
- gameContainer: 包含整个游戏的容器。
- score: 显示当前分数。
- gridContainer: 游戏的主要区域,包含瓷砖。
- gameOverContainer: 游戏结束时显示的消息和重启按钮。
CSS样式
代码部分:
body {
text-align: center;
margin-top: 50px;
font-family: Arial, sans-serif;
background-color: #f0f0f0;
}
#gridContainer {
width: 360px;
height: 360px;
border: 1px solid #8b8b8b;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 5px;
background-color: #dcdcdc;
padding: 10px;
}
.tile {
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
font-weight: bold;
border-radius: 6px;
width: 80px;
height: 80px;
color: #fff;
}
解释:
- body: 设置页面的基本样式,包括字体、背景颜色和对齐方式。
- gridContainer: 使用 CSS Grid 布局创建了一个 4x4 的网格,定义了网格的宽度、高度以及瓷砖之间的间距。
- .tile: 定义了瓷砖的样式,包括大小、颜色和字体。
JavaScript逻辑
初始化
function init() {
score = 0;
gameOverFlag = false;
updateScore();
gameOverContainer.style.display = "none";
resetGrid();
tiles = Array(width * width).fill(null);
addTile();
addTile();
drawTiles();
}
解释:
- 初始化游戏状态:重置分数和游戏结束标志。
- resetGrid(): 清空现有的网格。
- addTile(): 添加两个初始瓷砖。
- drawTiles(): 绘制当前的瓷砖状态。
添加瓷砖
function addTile() {
const emptyTiles = tiles.map((tile, index) => tile === null ? index : null).filter(index => index !== null);
if (emptyTiles.length > 0) {
const randomIndex = emptyTiles[Math.floor(Math.random() * emptyTiles.length)];
tiles[randomIndex] = Math.random() < 0.9 ? 2 : 4;
}
}
解释:
- 查找空位置:创建一个包含所有空位置索引的数组。
- 随机添加:在空位置随机添加一个值为 2 或 4 的瓷砖。
绘制瓷砖
function drawTiles() {
resetGrid();
tiles.forEach(tile => {
const tileElement = document.createElement("div");
tileElement.classList.add("tile");
if (tile !== null) {
tileElement.textContent = tile;
tileElement.classList.add(`tile-${tile}`);
}
gridContainer.appendChild(tileElement);
});
}
解释:
- 清空网格:每次重新绘制时清空网格。
- 创建瓷砖元素:为每个瓷砖创建一个新的 div 元素并设置其样式和内容。
移动逻辑
function move(direction) {
if (gameOverFlag) return;
let hasChanged = false;
const moveTile = (currentIndex, newIndex) => {
if (tiles[newIndex] === null) {
tiles[newIndex] = tiles[currentIndex];
tiles[currentIndex] = null;
hasChanged = true;
} else if (tiles[newIndex] === tiles[currentIndex]) {
tiles[newIndex] *= 2;
tiles[currentIndex] = null;
score += tiles[newIndex];
hasChanged = true;
}
};
// Define moveUp, moveDown, moveLeft, moveRight
switch (direction) {
case "up":
moveUp();
break;
case "down":
moveDown();
break;
case "left":
moveLeft();
break;
case "right":
moveRight();
break;
}
if (hasChanged) {
addTile();
drawTiles();
updateScore();
checkGameOver();
}
}
解释:
- 检查变动:检测移动是否改变了网格。
- 移动函数:定义不同方向的移动逻辑,合并相同的瓷砖。
检查游戏结束
代码部分:
function checkGameOver() {
if (!tiles.includes(null) && !checkMove()) {
gameOverFlag = true;
gameOverContainer.style.display = "block";
restartButton.addEventListener("click", restart, { once: true });
}
}
解释:
- 游戏结束条件:当没有空格且不能移动时,游戏结束。
- 显示游戏结束信息:并提供重启按钮。
重启游戏
function restart() {
init();
}
重新初始化游戏:调用 init() 函数,重置游戏状态。
事件监听
document.addEventListener("keydown", event => {
switch (event.key) {
case "ArrowUp":
case "w":
move("up");
break;
case "ArrowDown":
case "s":
move("down");
break;
case "ArrowLeft":
case "a":
move("left");
break;
case "ArrowRight":
case "d":
move("right");
break;
}
});
- 键盘事件:监听方向键和 WASD 键来移动瓷砖。
运行界面:
以下是完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2048 Game</title>
<style>
body {
text-align: center;
margin-top: 50px;
font-family: Arial, sans-serif;
background-color: #f0f0f0;
}
h1 {
font-size: 50px;
color: #333;
}
#gameContainer {
margin-top: 20px;
display: inline-block;
}
#score {
font-size: 24px;
margin-bottom: 10px;
color: #333;
}
#gridContainer {
width: 360px;
height: 360px;
border: 1px solid #8b8b8b;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 5px;
background-color: #dcdcdc;
padding: 10px;
}
.tile {
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
font-weight: bold;
border-radius: 6px;
width: 80px;
height: 80px;
color: #fff;
}
.tile-2 { background-color: #f78c6b; }
.tile-4 { background-color: #f7b26b; }
.tile-8 { background-color: #f7d06b; }
.tile-16 { background-color: #f4f76b; }
.tile-32 { background-color: #a3f76b; }
.tile-64 { background-color: #6bf7c4; }
.tile-128 { background-color: #6baff7; }
.tile-256 { background-color: #a26bf7; }
.tile-512 { background-color: #f76bf7; }
.tile-1024 { background-color: #f76b9f; }
.tile-2048 { background-color: #f76b6b; }
#gameOverContainer {
display: none;
margin-top: 20px;
}
#gameOver {
font-size: 36px;
color: #333;
margin-bottom: 10px;
}
#restartButton {
font-size: 20px;
padding: 8px 20px;
border-radius: 6px;
background-color: #444;
color: #fff;
border: none;
cursor: pointer;
transition: background-color 0.3s;
}
#restartButton:hover {
background-color: #666;
}
</style>
</head>
<body>
<h1>2048</h1>
<div id="gameContainer">
<div id="score">Score: 0</div>
<div id="gridContainer"></div>
<div id="gameOverContainer">
<div id="gameOver">Game Over!</div>
<button id="restartButton">Restart</button>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const gridContainer = document.getElementById("gridContainer");
const scoreDisplay = document.getElementById("score");
const gameOverContainer = document.getElementById("gameOverContainer");
const restartButton = document.getElementById("restartButton");
const width = 4;
let tiles = [];
let score = 0;
let gameOverFlag = false;
function init() {
score = 0;
gameOverFlag = false;
updateScore();
gameOverContainer.style.display = "none";
resetGrid();
tiles = Array(width * width).fill(null);
addTile();
addTile();
drawTiles();
}
function resetGrid() {
gridContainer.innerHTML = "";
}
function addTile() {
const emptyTiles = tiles.map((tile, index) => tile === null ? index : null).filter(index => index !== null);
if (emptyTiles.length > 0) {
const randomIndex = emptyTiles[Math.floor(Math.random() * emptyTiles.length)];
tiles[randomIndex] = Math.random() < 0.9 ? 2 : 4;
}
}
function drawTiles() {
resetGrid();
tiles.forEach(tile => {
const tileElement = document.createElement("div");
tileElement.classList.add("tile");
if (tile !== null) {
tileElement.textContent = tile;
tileElement.classList.add(`tile-${tile}`);
}
gridContainer.appendChild(tileElement);
});
}
function updateScore() {
scoreDisplay.textContent = `Score: ${score}`;
}
function move(direction) {
if (gameOverFlag) return;
let hasChanged = false;
const moveTile = (currentIndex, newIndex) => {
if (tiles[newIndex] === null) {
tiles[newIndex] = tiles[currentIndex];
tiles[currentIndex] = null;
hasChanged = true;
} else if (tiles[newIndex] === tiles[currentIndex]) {
tiles[newIndex] *= 2;
tiles[currentIndex] = null;
score += tiles[newIndex];
hasChanged = true;
}
};
const moveUp = () => {
for (let col = 0; col < width; col++) {
for (let row = 1; row < width; row++) {
let currentIndex = col + row * width;
while (currentIndex >= width && tiles[currentIndex] !== null) {
moveTile(currentIndex, currentIndex - width);
currentIndex -= width;
}
}
}
};
const moveDown = () => {
for (let col = 0; col < width; col++) {
for (let row = width - 2; row >= 0; row--) {
let currentIndex = col + row * width;
while (currentIndex + width < width * width && tiles[currentIndex] !== null) {
moveTile(currentIndex, currentIndex + width);
currentIndex += width;
}
}
}
};
const moveLeft = () => {
for (let row = 0; row < width; row++) {
for (let col = 1; col < width; col++) {
let currentIndex = row * width + col;
while (currentIndex % width !== 0 && tiles[currentIndex] !== null) {
moveTile(currentIndex, currentIndex - 1);
currentIndex--;
}
}
}
};
const moveRight = () => {
for (let row = 0; row < width; row++) {
for (let col = width - 2; col >= 0; col--) {
let currentIndex = row * width + col;
while ((currentIndex + 1) % width !== 0 && tiles[currentIndex] !== null) {
moveTile(currentIndex, currentIndex + 1);
currentIndex++;
}
}
}
};
switch (direction) {
case "up":
moveUp();
break;
case "down":
moveDown();
break;
case "left":
moveLeft();
break;
case "right":
moveRight();
break;
}
if (hasChanged) {
addTile();
drawTiles();
updateScore();
checkGameOver();
}
}
function checkGameOver() {
if (!tiles.includes(null) && !checkMove()) {
gameOverFlag = true;
gameOverContainer.style.display = "block";
restartButton.addEventListener("click", restart, { once: true });
}
}
function checkMove() {
for (let i = 0; i < tiles.length; i++) {
if (tiles[i] === null) return true;
if (i % width !== width - 1 && tiles[i] === tiles[i + 1]) return true;
if (i < tiles.length - width && tiles[i] === tiles[i + width]) return true;
}
return false;
}
function restart() {
init();
}
document.addEventListener("keydown", event => {
switch (event.key) {
case "ArrowUp":
case "w":
move("up");
break;
case "ArrowDown":
case "s":
move("down");
break;
case "ArrowLeft":
case "a":
move("left");
break;
case "ArrowRight":
case "d":
move("right");
break;
}
});
init();
});
</script>
</body>
</html>
通过这篇文章,你可以了解如何从头开始构建一个简单的 2048 游戏,包括 HTML、CSS 和 JavaScript 的使用。
希望你能从中学习到有用的知识!
感谢阅读!!!