超萌来袭!用 HTML、CSS 和 JavaScript 打造可爱 2048 游戏
一、引言
在如今琳琅满目的游戏世界里,简单又好玩的小游戏总能吸引众多玩家的目光。2048 这款经典数字合并游戏,以其简洁的规则和富有挑战性的玩法,一直备受欢迎。今天,咱们就用 HTML、CSS 和 JavaScript 来打造一个独具特色的可爱版 2048 游戏,用可爱的动物图片替代传统数字,让游戏更加有趣!
二、游戏功能概述
1. 核心玩法
游戏规则依旧遵循经典 2048 的玩法,玩家使用键盘方向键(上、下、左、右)控制卡片移动,相同的卡片会合并成一个数值更大的卡片,目标是合并出数值为 2048 的卡片。
2. 独特设计
- 可爱动物图片:用一系列可爱的动物图片替代了传统的数字,为游戏增添了不少萌趣。
- 得分与最高分记录:游戏中实时显示当前得分,并且会记录玩家的历史最高分,激励玩家不断挑战自我。
- 重置功能:玩家可以随时点击“重置”按钮,将游戏重置到初始状态,重新开始挑战。
- 动物介绍按钮:鼠标悬浮在“动物介绍”按钮上时,会显示每个动物图片所代表的数字,而且数字采用不同的流动渐变色显示,与对应的图片整齐对齐,增加了游戏的趣味性和可探索性。
- 胜利提示:当玩家成功合并出 2048 时,会弹出胜利提示框,给玩家满满的成就感。
三、代码实现详解
1. HTML 部分
HTML 负责构建游戏的基本结构,包括游戏标题、得分区域、游戏棋盘、游戏规则说明和胜利提示框等。以下是关键代码片段:
<div id="header">
<h1>可爱 2048 游戏</h1>
<div id="score-board">
<div id="score">得分: 0</div>
<div id="high-score">最高分: 0</div>
<button id="reset-button" onclick="resetGame()">重置</button>
<div id="animal-intro">
动物介绍
<div id="animal-intro-content">
<p><span class="gradient-number">数字 2</span>: <img src="图片链接" alt="2" width="30"> </p>
<!-- 其他数字和图片对应关系 -->
</div>
</div>
</div>
</div>
<div id="game-board"></div>
<div id="game-rules">
<p>游戏规则:使用键盘方向键(上、下、左、右)控制卡片移动,相同的卡片会合并成一个数值更大的卡片,目标是合并出数值为 2048 的卡片。</p>
</div>
<div id="win-modal">
<div id="win-modal-content">
<p>恭喜你已经合并了一只宇宙无敌最可爱的猫咪</p>
<button onclick="closeWinModal()">确认</button>
</div>
</div>
2. CSS 部分
CSS 用于美化游戏界面,包括设置背景颜色、字体样式、按钮样式、棋盘布局和动物介绍的悬浮效果等。特别地,为动物介绍中的数字设置了流动渐变色效果:
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.gradient-number {
background-size: 200% 200%;
background-clip: text;
-webkit-background-clip: text;
color: transparent;
animation: gradient 3s ease infinite;
}
#animal-intro-content p:nth-child(1) .gradient-number {
background-image: linear-gradient(45deg, #ff00cc, #333399);
}
/* 其他数字的渐变色设置 */
3. JavaScript 部分
JavaScript 实现了游戏的核心逻辑,包括棋盘的初始化、随机卡片的生成、卡片的移动和合并、得分计算、胜利判断等。以下是部分关键函数:
// 初始化棋盘
function createBoard() {
gameBoard.innerHTML = '';
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
const cell = document.createElement('div');
cell.classList.add('cell');
if (board[i][j]!== 0) {
const tile = document.createElement('div');
tile.classList.add('tile');
tile.style.backgroundImage = `url(${
images[board[i][j]]})`;
cell.appendChild(tile);
}
gameBoard.appendChild(cell);
}
}
scoreElement.textContent = `得分: ${
score}`;
}
// 随机添加卡片
function addRandomTile() {
const emptyCells = [];
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (board[i][j] === 0) {
emptyCells.push({
i, j });
}
}
}
if (emptyCells.length > 0) {
const randomIndex = Math.floor(Math.random() * emptyCells.length);
const {
i, j } = emptyCells[randomIndex];
board[i][j] = Math.random() < 0.9? 2 : 4;
}
}
// 合并卡片
function mergeTiles(row) {
let newRow = row.filter(tile => tile!== 0);
for (let i = 0; i < newRow.length - 1; i++) {
if (newRow[i] === newRow[i + 1]) {
newRow[i] *= 2;
score += newRow[i];
if (newRow[i] === 2048) {
showWinModal();
}
newRow[i + 1] = 0;
}
}
newRow = newRow.filter(tile => tile!== 0);
while (newRow.length < 4) {
newRow.push(0);
}
return newRow;
}
四、运行与体验
将上述 HTML、CSS 和 JavaScript 代码整合到一个 HTML 文件中,用浏览器打开该文件,就可以开始游戏啦!在游戏过程中,你能感受到可爱动物图片带来的愉悦,还能不断挑战自己的最高分。
五、总结
通过这个项目,我们不仅重温了经典的 2048 游戏,还学习了如何运用 HTML、CSS 和 JavaScript 来实现一个完整的网页游戏。从页面布局到样式设计,再到核心逻辑的实现,每一个环节都充满了乐趣和挑战。希望大家能喜欢这个可爱版的 2048 游戏,也可以根据自己的喜好对代码进行修改和扩展,创造出属于自己的独特游戏!
赶紧动手试试吧,说不定你就是下一个游戏小达人!
下面是完整代码,保存为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>
body {
background-color: #faf8ef;
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
}
#header {
display: flex;
justify-content: space-between;
align-items: center;
width: 90%;
max-width: 500px;
color: #776e65;
}
h1 {
margin: 0;
}
#score-board {
display: flex;
gap: 10px;
}
#score,
#high-score {
background-color: #bbada0;
color: white;
padding: 5px 10px;
border-radius: 5px;
}
#reset-button {
background-color: #8f7a66;
color: white;
padding: 5px 10px;
border: none;
border-radius: 5px;
cursor: pointer;
}
#animal-intro {
background-color: #8f7a66;
color: white;
padding: 5px 10px;
border: none;
border-radius: 5px;
cursor: pointer;
position: relative;
}
#animal-intro-content {
display: none;
position: absolute;
background-color: #f9f9f9;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
padding: 12px 16px;
z-index: 1;
top: 100%;
left: 0;
}
#animal-intro:hover #animal-intro-content {
display: block;
}
#animal-intro-content p {
display: flex;
align-items: center;
gap: 10px;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.gradient-number {
background-size: 200% 200%;
background-clip: text;
-webkit-background-clip: text;
color: transparent;
animation: gradient 3s ease infinite;
}
#animal-intro-content p:nth-child(1) .gradient-number {
background-image: linear-gradient(45deg, #ff00cc, #333399);
}
#animal-intro-content p:nth-child(2) .gradient-number {
background-image: linear-gradient(45deg, #ff6600, #ffcc00);
}
#animal-intro-content p:nth-child(3) .gradient-number {
background-image: linear-gradient(45deg, #00ff00, #0066ff);
}
#animal-intro-content p:nth-child(4) .gradient-number {
background-image: linear-gradient(45deg, #ff0000, #ff9900);
}
#animal-intro-content p:nth-child(5) .gradient-number {
background-image: linear-gradient(45deg, #9900ff, #33cc33);
}
#animal-intro-content p:nth-child(6) .gradient-number {
background-image: linear-gradient(45deg, #0099ff, #ff00ff);
}
#animal-intro-content p:nth-child(7) .gradient-number {
background-image: linear-gradient(45deg, #ff3399, #00ccff);
}
#animal-intro-content p:nth-child(8) .gradient-number {
background-image: linear-gradient(45deg, #66ff33, #ff66cc);
}
#animal-intro-content p:nth-child(9) .gradient-number {
background-image: linear-gradient(45deg, #00ffcc, #ff9933);
}
#animal-intro-content p:nth-child(10) .gradient-number {
background-image: linear-gradient(45deg, #ff0066, #33ff99);
}
#animal-intro-content p:nth-child(11) .gradient-number {
background-image: linear-gradient(45deg, #99ff33, #ff0099);
}
#game-board {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, 1fr);
gap: 10px;
background-color: #bbada0;
padding: 10px;
border-radius: 5px;
width: 90%;
max-width: 500px;
margin: 20px 0;
}
.cell {
background-color: rgba(238, 228, 218, 0.35);
border-radius: 5px;
aspect-ratio: 1/1;
}
.tile {
width: 100%;
height: 100%;
background-size: cover;
border-radius: 5px;
}
#game-rules {
text-align: left;
color: #776e65;
width: 90%;
max-width: 500px;
font-size: 14px;
}
#win-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
align-items: center;
justify-content: center;
}
#win-modal-content {
background-color: white;
padding: 20px;
border-radius: 5px;
text-align: center;
}
</style>
</head>
<body>
<div id="header">
<h1>可爱 2048 游戏</h1>
<div id="score-board">
<div id="score">得分: 0</div>
<div id="high-score">最高分: 0</div>
<button id="reset-button" onclick="resetGame()">重置</button>
<div id="animal-intro">
动物介绍
<div id="animal-intro-content">
<p><span class="gradient-number">数字 2</span>: <img src="https://p9-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/df099f209335f8b25e1d4ab9ce2867ff_1741677321668230149.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213321&x-signature=reNM%2F907NNX7Rc%2BE4FflYbWaRKo%3D" alt="2" width="30"> </p>
<p><span class="gradient-number">数字 4</span>: <img src="https://p9-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/322b9826909d14458d22e0931fd06aac_1741677330491084517.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213330&x-signature=a1ptVuhTOwjrpRZSdnL%2FUKYV9MY%3D" alt="4" width="30"> </p>
<p><span class="gradient-number">数字 8</span>: <img src="https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/1a563d103b7496a5b9eb365ff9ea58bd_1741677409407029394.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213409&x-signature=L0drmZ0N1Z2%2BjUD9g9gcP0Nn7TM%3D" alt="8" width="30"> </p>
<p><span class="gradient-number">数字 16</span>: <img src="https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/f51aebc9e6d5eee19fc026e3f1070185_1741677412756659826.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213412&x-signature=%2F%2FMLyi7m%2Bu%2Fo%2F%2FTQeYqmUWeDtLQ%3D" alt="16" width="30"> </p>
<p><span class="gradient-number">数字 32</span>: <img src="https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/8693822e52091fd6896366ae4e922bc2_1741677429462075923.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213429&x-signature=tLOJO0F5DKTLStoKnja6l2fr%2FOg%3D" alt="32" width="30"> </p>
<p><span class="gradient-number">数字 64</span>: <img src="https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/5fbb02d79c0f16f69ec86ea7a83d7e36_1741677431738410576.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213431&x-signature=Ry8L%2FkXszOCFFHkMeIdKm6Ktg%2FQ%3D" alt="64" width="30"> </p>
<p><span class="gradient-number">数字 128</span>: <img src="https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/f2c91103fc53286b5d481390c12d665f_1741677444595987213.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213444&x-signature=X5AHPsnNLXS25hlANqlvTtpSQmc%3D" alt="128" width="30"> </p>
<p><span class="gradient-number">数字 256</span>: <img src="https://p9-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/9f4e790544e519f8c110d2c73e7b03bf_1741677446066399364.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213446&x-signature=DmV9R3QrONwWz%2F4fUnoceUjiU8E%3D" alt="256" width="30"> </p>
<p><span class="gradient-number">数字 512</span>: <img src="https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/8f72db8a79741930894920bc8faced83_1741677458934448994.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213458&x-signature=YxMhmlrT%2FY7brkyal9u5oaifd3g%3D" alt="512" width="30"> </p>
<p><span class="gradient-number">数字 1024</span>: <img src="https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/f565fa57b977fa953208f7a2b4edd839_1741677459756646895.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213459&x-signature=r0bBAR7T7NVF3dyu1aGI%2FlWfR4w%3D" alt="1024" width="30"> </p>
<p><span class="gradient-number">数字 2048</span>: <img src="https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/dbb80e7d3948b8af02664915eeee38d2_1741677502512862779.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213502&x-signature=Bv%2FEdGFqr%2Bq47a8g1D4KiU9XYqQ%3D" alt="2048" width="30"> </p>
</div>
</div>
</div>
</div>
<div id="game-board"></div>
<div id="game-rules">
<p>游戏规则:使用键盘方向键(上、下、左、右)控制卡片移动,相同的卡片会合并成一个数值更大的卡片,目标是合并出数值为 2048 的卡片。</p>
</div>
<div id="win-modal">
<div id="win-modal-content">
<p>恭喜你已经合并了一只宇宙无敌最可爱的猫咪</p>
<button onclick="closeWinModal()">确认</button>
</div>
</div>
<script>
const gameBoard = document.getElementById('game-board');
const winModal = document.getElementById('win-modal');
const scoreElement = document.getElementById('score');
const highScoreElement = document.getElementById('high-score');
const images = {
2: 'https://p9-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/df099f209335f8b25e1d4ab9ce2867ff_1741677321668230149.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213321&x-signature=reNM%2F907NNX7Rc%2BE4FflYbWaRKo%3D',
4: 'https://p9-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/322b9826909d14458d22e0931fd06aac_1741677330491084517.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213330&x-signature=a1ptVuhTOwjrpRZSdnL%2FUKYV9MY%3D',
8: 'https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/1a563d103b7496a5b9eb365ff9ea58bd_1741677409407029394.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213409&x-signature=L0drmZ0N1Z2%2BjUD9g9gcP0Nn7TM%3D',
16: 'https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/f51aebc9e6d5eee19fc026e3f1070185_1741677412756659826.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213412&x-signature=%2F%2FMLyi7m%2Bu%2Fo%2F%2FTQeYqmUWeDtLQ%3D',
32: 'https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/8693822e52091fd6896366ae4e922bc2_1741677429462075923.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213429&x-signature=tLOJO0F5DKTLStoKnja6l2fr%2FOg%3D',
64: 'https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/5fbb02d79c0f16f69ec86ea7a83d7e36_1741677431738410576.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213431&x-signature=Ry8L%2FkXszOCFFHkMeIdKm6Ktg%2FQ%3D',
128: 'https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/f2c91103fc53286b5d481390c12d665f_1741677444595987213.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213444&x-signature=X5AHPsnNLXS25hlANqlvTtpSQmc%3D',
256: 'https://p9-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/9f4e790544e519f8c110d2c73e7b03bf_1741677446066399364.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213446&x-signature=DmV9R3QrONwWz%2F4fUnoceUjiU8E%3D',
512: 'https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/8f72db8a79741930894920bc8faced83_1741677458934448994.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213458&x-signature=YxMhmlrT%2FY7brkyal9u5oaifd3g%3D',
1024: 'https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/f565fa57b977fa953208f7a2b4edd839_1741677459756646895.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213459&x-signature=r0bBAR7T7NVF3dyu1aGI%2FlWfR4w%3D',
2048: 'https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_generation/dbb80e7d3948b8af02664915eeee38d2_1741677502512862779.png~tplv-a9rns2rl98-image.png?rk3s=25bff839&x-expires=1773213502&x-signature=Bv%2FEdGFqr%2Bq47a8g1D4KiU9XYqQ%3D'
};
let board = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
];
let score = 0;
let highScore = localStorage.getItem('highScore')? parseInt(localStorage.getItem('highScore')) : 0;
highScoreElement.textContent = `最高分: ${highScore}`;
function createBoard() {
gameBoard.innerHTML = '';
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
const cell = document.createElement('div');
cell.classList.add('cell');
if (board[i][j]!== 0) {
const tile = document.createElement('div');
tile.classList.add('tile');
tile.style.backgroundImage = `url(${images[board[i][j]]})`;
cell.appendChild(tile);
}
gameBoard.appendChild(cell);
}
}
scoreElement.textContent = `得分: ${score}`;
}
function addRandomTile() {
const emptyCells = [];
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (board[i][j] === 0) {
emptyCells.push({ i, j });
}
}
}
if (emptyCells.length > 0) {
const randomIndex = Math.floor(Math.random() * emptyCells.length);
const { i, j } = emptyCells[randomIndex];
board[i][j] = Math.random() < 0.9? 2 : 4;
}
}
function mergeTiles(row) {
let newRow = row.filter(tile => tile!== 0);
for (let i = 0; i < newRow.length - 1; i++) {
if (newRow[i] === newRow[i + 1]) {
newRow[i] *= 2;
score += newRow[i];
if (newRow[i] === 2048) {
showWinModal();
}
newRow[i + 1] = 0;
}
}
newRow = newRow.filter(tile => tile!== 0);
while (newRow.length < 4) {
newRow.push(0);
}
return newRow;
}
function moveLeft() {
let moved = false;
for (let i = 0; i < 4; i++) {
const oldRow = [...board[i]];
board[i] = mergeTiles(board[i]);
if (oldRow.join(',')!== board[i].join(',')) {
moved = true;
}
}
if (moved) {
addRandomTile();
}
updateScore();
createBoard();
}
function moveRight() {
let moved = false;
for (let i = 0; i < 4; i++) {
const oldRow = [...board[i]];
const reversedRow = board[i].slice().reverse();
const newReversedRow = mergeTiles(reversedRow);
board[i] = newReversedRow.reverse();
if (oldRow.join(',')!== board[i].join(',')) {
moved = true;
}
}
if (moved) {
addRandomTile();
}
updateScore();
createBoard();
}
function moveUp() {
let moved = false;
for (let j = 0; j < 4; j++) {
const column = [];
for (let i = 0; i < 4; i++) {
column.push(board[i][j]);
}
const oldColumn = [...column];
const newColumn = mergeTiles(column);
for (let i = 0; i < 4; i++) {
board[i][j] = newColumn[i];
}
if (oldColumn.join(',')!== newColumn.join(',')) {
moved = true;
}
}
if (moved) {
addRandomTile();
}
updateScore();
createBoard();
}
function moveDown() {
let moved = false;
for (let j = 0; j < 4; j++) {
const column = [];
for (let i = 0; i < 4; i++) {
column.push(board[i][j]);
}
const oldColumn = [...column];
const reversedColumn = column.slice().reverse();
const newReversedColumn = mergeTiles(reversedColumn);
const newColumn = newReversedColumn.reverse();
for (let i = 0; i < 4; i++) {
board[i][j] = newColumn[i];
}
if (oldColumn.join(',')!== newColumn.join(',')) {
moved = true;
}
}
if (moved) {
addRandomTile();
}
updateScore();
createBoard();
}
function handleKeyPress(event) {
switch (event.key) {
case 'ArrowLeft':
moveLeft();
break;
case 'ArrowRight':
moveRight();
break;
case 'ArrowUp':
moveUp();
break;
case 'ArrowDown':
moveDown();
break;
}
}
function showWinModal() {
winModal.style.display = 'flex';
}
function closeWinModal() {
winModal.style.display = 'none';
}
function updateScore() {
if (score > highScore) {
highScore = score;
highScoreElement.textContent = `最高分: ${highScore}`;
localStorage.setItem('highScore', highScore);
}
scoreElement.textContent = `得分: ${score}`;
}
function resetGame() {
board = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
];
score = 0;
addRandomTile();
addRandomTile();
createBoard();
updateScore();
closeWinModal();
}
window.addEventListener('keydown', handleKeyPress);
addRandomTile();
addRandomTile();
createBoard();
</script>
</body>
</html>