“2048”游戏网页版html+css+js
“2048”游戏网页版html+css+js
别忘了请点个赞+收藏+关注支持一下博主喵!!!
2048 游戏是一个非常流行的数字拼图游戏,玩家通过移动方块使相同数字的方块合并,最终达到 2048 或更高分数。本教程将详细介绍如何使用 HTML、CSS 和 JavaScript 创建一个简单的 2048 游戏网页。。
项目结构如下:(懒得搭vue了)
2048-game/
├── index.html
├── styles.css
└── script.js
1. index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"> <!-- 设置文档的字符编码为 UTF-8 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 使页面在移动设备上适配屏幕宽度 -->
<title>2048 Game</title> <!-- 页面标题 -->
<link rel="stylesheet" href="styles.css"> <!-- 引入外部 CSS 文件 -->
</head>
<body>
<div class="game-container"> <!-- 游戏容器,用于包裹整个游戏界面 -->
<div class="score-container">0</div> <!-- 分数显示区域 -->
<div class="grid-container"> <!-- 网格容器,用于包裹 4x4 的游戏网格 -->
<div class="grid-row"> <!-- 每一行的网格 -->
<div class="grid-cell"></div> <!-- 每一个网格单元 -->
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
</div>
<button id="restart-button">Restart</button> <!-- 重新开始按钮 -->
</div>
<script src="script.js"></script> <!-- 引入外部 JavaScript 文件 -->
</body>
</html>
2. styles.css
body {
display: flex; /* 使用 Flexbox 布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh; /* 占满整个视窗高度 */
margin: 0; /* 移除默认外边距 */
background-color: #faf8ef; /* 背景颜色 */
font-family: Arial, sans-serif; /* 字体设置 */
}
.game-container {
width: 500px; /* 容器宽度 */
height: 500px; /* 容器高度 */
position: relative; /* 相对定位 */
background-color: #bbada0; /* 背景颜色 */
border-radius: 10px; /* 圆角 */
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3); /* 阴影效果 */
}
.score-container {
position: absolute; /* 绝对定位 */
top: 10px; /* 距离顶部 10px */
left: 10px; /* 距离左边 10px */
background-color: #eee4da; /* 背景颜色 */
padding: 10px; /* 内边距 */
border-radius: 5px; /* 圆角 */
font-size: 24px; /* 字体大小 */
font-weight: bold; /* 加粗字体 */
}
.grid-container {
width: 400px; /* 网格容器宽度 */
height: 400px; /* 网格容器高度 */
position: absolute; /* 绝对定位 */
top: 50%; /* 距离顶部 50% */
left: 50%; /* 距离左边 50% */
transform: translate(-50%, -50%); /* 居中对齐 */
display: grid; /* 使用 Grid 布局 */
grid-template-columns: repeat(4, 100px); /* 4 列,每列 100px */
grid-template-rows: repeat(4, 100px); /* 4 行,每行 100px */
gap: 10px; /* 网格间距 */
}
.grid-cell {
background-color: #cdc1b4; /* 背景颜色 */
border-radius: 3px; /* 圆角 */
display: flex; /* 使用 Flexbox 布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
font-size: 32px; /* 字体大小 */
font-weight: bold; /* 加粗字体 */
color: #776e65; /* 文字颜色 */
}
/* 不同数字的方块样式 */
.grid-cell.number-2 { background-color: #eee4da; color: #776e65; }
.grid-cell.number-4 { background-color: #ede0c8; color: #776e65; }
.grid-cell.number-8 { background-color: #f2b179; color: #f9f6f2; }
.grid-cell.number-16 { background-color: #f59563; color: #f9f6f2; }
.grid-cell.number-32 { background-color: #f67c5f; color: #f9f6f2; }
.grid-cell.number-64 { background-color: #f65e3b; color: #f9f6f2; }
.grid-cell.number-128 { background-color: #edcf72; color: #f9f6f2; }
.grid-cell.number-256 { background-color: #edcc61; color: #f9f6f2; }
.grid-cell.number-512 { background-color: #edc850; color: #f9f6f2; }
.grid-cell.number-1024 { background-color: #edc53f; color: #f9f6f2; }
.grid-cell.number-2048 { background-color: #edc22e; color: #f9f6f2; }
#restart-button {
position: absolute; /* 绝对定位 */
bottom: 10px; /* 距离底部 10px */
left: 50%; /* 距离左边 50% */
transform: translateX(-50%); /* 水平居中 */
padding: 10px 20px; /* 内边距 */
background-color: #8f7a66; /* 背景颜色 */
color: #f9f6f2; /* 文字颜色 */
border: none; /* 无边框 */
border-radius: 5px; /* 圆角 */
cursor: pointer; /* 鼠标指针样式 */
font-size: 18px; /* 字体大小 */
font-weight: bold; /* 加粗字体 */
}
#restart-button:hover {
background-color: #746655; /* 鼠标悬停时的背景颜色 */
}
3. 最难的script.js
代码分布详解:
1. 获取 DOM 元素
const grid = document.querySelector('.grid-container'); // 获取网格容器
const cells = document.querySelectorAll('.grid-cell'); // 获取所有网格单元
const scoreContainer = document.querySelector('.score-container'); // 获取分数显示区域
const restartButton = document.getElementById('restart-button'); // 获取重新开始按钮
这些代码通过 querySelector
和 getElementById
方法获取页面上的各个元素,以便后续操作。
2. 初始化变量
let score = 0; // 初始化分数
let hasWon = false; // 标记是否已经赢得游戏
const matrix = [
[0, 0, 0, 0], // 初始化 4x4 的矩阵
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
];
const winNumber = 2048; // 赢得游戏的目标数字
score
:当前得分。hasWon
:标记是否已经赢得游戏。matrix
:4x4 的二维数组,用于存储游戏中的数字。winNumber
:赢得游戏的目标数字,即 2048。
3. 获取随机空格子
function getRandomEmptyCell() {
const emptyCells = []; // 存储所有空格子的坐标
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (matrix[i][j] === 0) { // 如果格子为空
emptyCells.push({ row: i, col: j }); // 将坐标加入空格子列表
}
}
}
if (emptyCells.length > 0) { // 如果有空格子
const randomIndex = Math.floor(Math.random() * emptyCells.length); // 随机选择一个空格子
return emptyCells[randomIndex];
}
return null; // 没有空格子返回 null
}
这个函数遍历 matrix
,找到所有为空的格子,并将它们的坐标存储在 emptyCells
数组中。如果存在空格子,则随机选择一个返回;否则返回 null
。
算法步骤:
- 初始化一个空数组
emptyCells
,用于存储所有空格子的坐标。 - 使用两层嵌套循环遍历
matrix
,检查每个格子是否为空(即matrix[i][j] === 0
)。 - 如果格子为空,将该格子的坐标
{ row: i, col: j }
推入emptyCells
数组。 - 循环结束后,检查
emptyCells
是否有元素。 - 如果有空格子,随机选择一个空格子的索引
randomIndex
,并返回该空格子的坐标。 - 如果没有空格子,返回
null
。
4. 添加随机方块
function addRandomTile() {
const cell = getRandomEmptyCell();
if (cell) {
const value = Math.random() < 0.9 ? 2 : 4; // 90% 的概率生成 2,10% 的概率生成 4
matrix[cell.row][cell.col] = value; // 更新矩阵
updateGrid(); // 更新网格显示
}
}
这个函数调用 getRandomEmptyCell
获取一个随机的空格子,然后以 90% 的概率生成 2,10% 的概率生成 4,并更新 matrix
和网格显示。
算法步骤:
- 调用
getRandomEmptyCell
获取一个随机的空格子。 - 如果找到了空格子:
- 生成一个随机值
value
,90% 的概率生成 2,10% 的概率生成 4。 - 更新
matrix
中对应位置的值为value
。 - 调用
updateGrid
更新网格显示。
- 生成一个随机值
5. 更新网格显示
function updateGrid() {
cells.forEach((cell, index) => {
const row = Math.floor(index / 4); // 计算当前单元格所在的行
const col = index % 4; // 计算当前单元格所在的列
const value = matrix[row][col]; // 获取矩阵中的值
cell.textContent = value || ''; // 显示值,如果没有值则显示空字符串
cell.classList.remove('number-2', 'number-4', 'number-8', 'number-16', 'number-32', 'number-64', 'number-128', 'number-256', 'number-512', 'number-1024', 'number-2048'); // 移除所有样式类
if (value !== 0) { // 如果有值,添加对应的样式类
cell.classList.add(`number-${value}`);
}
});
}
这个函数遍历所有 cells
,根据 matrix
中的值更新每个单元格的文本内容和样式类。
算法步骤:
- 遍历所有
cells
,使用forEach
方法。 - 对于每个
cell
,计算其在matrix
中的行row
和列col
。 - 获取
matrix
中对应位置的值value
。 - 设置
cell
的文本内容为value
,如果没有值则显示为空字符串。 - 移除
cell
上的所有样式类。 - 如果
value
不为 0,添加对应的样式类number-${value}
。
6. 向上移动
function moveUp() {
for (let col = 0; col < 4; col++) { // 遍历每一列
let tempCol = []; // 临时存储非零值
for (let row = 0; row < 4; row++) {
if (matrix[row][col] !== 0) {
tempCol.push(matrix[row][col]); // 将非零值加入临时列表
}
}
tempCol = mergeTiles(tempCol); // 合并相同的值
for (let row = 0; row < 4; row++) {
matrix[row][col] = tempCol[row] || 0; // 更新矩阵
}
}
addRandomTile(); // 添加新的方块
checkGameOver(); // 检查游戏是否结束
}
这个函数处理向上移动的操作:
- 遍历每一列。
- 将每列中的非零值存储到
tempCol
中。 - 调用
mergeTiles
合并相同的值。 - 更新
matrix
。 - 添加新的方块。
- 检查游戏是否结束。
算法步骤:
- 遍历每一列
col
。 - 初始化一个空数组
tempCol
,用于存储当前列中的非零值。 - 使用内层循环遍历当前列的每一行
row
,将非零值推入tempCol
。 - 调用
mergeTiles
合并tempCol
中的相同值。 - 使用内层循环更新
matrix
中当前列的值,从tempCol
中取值,如果tempCol
中没有值则设为 0。 - 调用
addRandomTile
添加一个新的方块。 - 调用
checkGameOver
检查游戏是否结束。
7. 向下移动
function moveDown() {
for (let col = 0; col < 4; col++) { // 遍历每一列
let tempCol = []; // 临时存储非零值
for (let row = 3; row >= 0; row--) {
if (matrix[row][col] !== 0) {
tempCol.push(matrix[row][col]); // 将非零值加入临时列表
}
}
tempCol = mergeTiles(tempCol); // 合并相同的值
for (let row = 3; row >= 0; row--) {
matrix[row][col] = tempCol[3 - row] || 0; // 更新矩阵
}
}
addRandomTile(); // 添加新的方块
checkGameOver(); // 检查游戏是否结束
}
这个函数处理向下移动的操作,与 moveUp
类似,只是方向相反。
8. 向左移动
function moveLeft() {
for (let row = 0; row < 4; row++) { // 遍历每一行
let tempRow = []; // 临时存储非零值
for (let col = 0; col < 4; col++) {
if (matrix[row][col] !== 0) {
tempRow.push(matrix[row][col]); // 将非零值加入临时列表
}
}
tempRow = mergeTiles(tempRow); // 合并相同的值
for (let col = 0; col < 4; col++) {
matrix[row][col] = tempRow[col] || 0; // 更新矩阵
}
}
addRandomTile(); // 添加新的方块
checkGameOver(); // 检查游戏是否结束
}
这个函数处理向左移动的操作,与 moveUp
类似,只是方向不同。
- 遍历每一行
row
。 - 初始化一个空数组
tempRow
,用于存储当前行中的非零值。 - 使用内层循环遍历当前行的每一列
col
,将非零值推入tempRow
。 - 调用
mergeTiles
合并tempRow
中的相同值。 - 使用内层循环更新
matrix
中当前行的值,从tempRow
中取值,如果tempRow
中没有值则设为 0。 - 调用
addRandomTile
添加一个新的方块。 - 调用
checkGameOver
检查游戏是否结束。
9. 向右移动
function moveRight() {
for (let row = 0; row < 4; row++) { // 遍历每一行
let tempRow = []; // 临时存储非零值
for (let col = 3; col >= 0; col--) {
if (matrix[row][col] !== 0) {
tempRow.push(matrix[row][col]); // 将非零值加入临时列表
}
}
tempRow = mergeTiles(tempRow); // 合并相同的值
for (let col = 3; col >= 0; col--) {
matrix[row][col] = tempRow[3 - col] || 0; // 更新矩阵
}
}
addRandomTile(); // 添加新的方块
checkGameOver(); // 检查游戏是否结束
}
这个函数处理向右移动的操作,与 moveLeft
类似,只是方向相反。
10. 合并相同的值
function mergeTiles(tiles) {
for (let i = 0; i < tiles.length - 1; i++) {
if (tiles[i] === tiles[i + 1]) { // 如果相邻的两个值相同
tiles[i] *= 2; // 合并值
tiles.splice(i + 1, 1); // 删除合并后的值
score += tiles[i]; // 更新分数
scoreContainer.textContent = score; // 更新分数显示
if (tiles[i] === winNumber && !hasWon) { // 如果达到目标值且未赢过
alert('You Win!'); // 提示胜利
hasWon = true; // 标记已赢
}
}
}
while (tiles.length < 4) { // 确保列表长度为 4
tiles.push(0);
}
return tiles;
}
这个函数合并 tiles
列表中的相同值:
- 遍历
tiles
,如果相邻的两个值相同,则合并它们。 - 更新分数和分数显示。
- 如果达到目标值 2048 且未赢过,则提示胜利。
- 确保
tiles
列表长度为 4。
算法步骤:
- 遍历
tiles
数组,检查相邻的两个值是否相同。 - 如果相邻的两个值相同:
- 将第一个值乘以 2。
- 从数组中删除第二个值。
- 更新分数
score
并更新分数显示。 - 如果合并后的值等于
winNumber
且未赢过,提示胜利并标记已赢。
- 使用
while
循环确保tiles
数组的长度为 4,不足的部分用 0 填充。 - 返回合并后的
tiles
数组。
11. 检查游戏是否结束
function checkGameOver() {
let gameOver = true; // 默认游戏结束
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (matrix[i][j] === 0 || // 如果有空格子
(i < 3 && matrix[i][j] === matrix[i + 1][j]) || // 或者有相邻的相同值
(j < 3 && matrix[i][j] === matrix[i][j + 1])) {
gameOver = false; // 游戏未结束
break;
}
}
if (!gameOver) break;
}
if (gameOver) {
alert('Game Over!'); // 提示游戏结束
}
}
这个函数检查游戏是否结束:
- 遍历
matrix
,检查是否有空格子或相邻的相同值。 - 如果有空格子或相邻的相同值,则游戏未结束。
- 否则,提示游戏结束。
算法步骤:
- 初始化一个布尔变量
gameOver
为true
,默认游戏结束。 - 使用两层嵌套循环遍历
matrix
,检查每个格子是否为空或有相邻的相同值。 - 如果有空格子或有相邻的相同值,将
gameOver
设为false
并跳出循环。 - 如果
gameOver
仍为true
,提示游戏结束。
12. 重新开始游戏
function restartGame() {
matrix.forEach(row => row.fill(0)); // 重置矩阵
score = 0; // 重置分数
hasWon = false; // 重置胜利标记
scoreContainer.textContent = score; // 更新分数显示
addRandomTile(); // 添加初始方块
addRandomTile(); // 添加初始方块
updateGrid(); // 更新网格显示
}
这个函数重置游戏:
- 重置
matrix
。 - 重置分数和胜利标记。
- 更新分数显示。
- 添加两个初始方块。
- 更新网格显示。
算法步骤:
- 使用
forEach
方法遍历matrix
的每一行,将每一行的值全部设为 0。 - 将分数
score
设为 0。 - 将胜利标记
hasWon
设为false
。 - 更新分数显示。
- 调用
addRandomTile
两次,添加两个初始方块。 - 调用
updateGrid
更新网格显示。
13. 监听键盘事件
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'ArrowUp': // 上箭头
moveUp();
break;
case 'ArrowDown': // 下箭头
moveDown();
break;
case 'ArrowLeft': // 左箭头
moveLeft();
break;
case 'ArrowRight': // 右箭头
moveRight();
break;
}
});
这个事件监听器监听键盘事件,根据按键方向调用相应的移动函数
14. 监听重新开始按钮点击事件
restartButton.addEventListener('click', restartGame);
这个事件监听器监听重新开始按钮的点击事件,调用 restartGame
重置游戏。
15. 初始化游戏
restartGame();
最终代码:
const grid = document.querySelector('.grid-container'); // 获取网格容器
const cells = document.querySelectorAll('.grid-cell'); // 获取所有网格单元
const scoreContainer = document.querySelector('.score-container'); // 获取分数显示区域
const restartButton = document.getElementById('restart-button'); // 获取重新开始按钮
let score = 0; // 初始化分数
let hasWon = false; // 标记是否已经赢得游戏
const matrix = [
[0, 0, 0, 0], // 初始化 4x4 的矩阵
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
];
const winNumber = 2048; // 赢得游戏的目标数字
// 获取一个随机的空格子
function getRandomEmptyCell() {
const emptyCells = []; // 存储所有空格子的坐标
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (matrix[i][j] === 0) { // 如果格子为空
emptyCells.push({ row: i, col: j }); // 将坐标加入空格子列表
}
}
}
if (emptyCells.length > 0) { // 如果有空格子
const randomIndex = Math.floor(Math.random() * emptyCells.length); // 随机选择一个空格子
return emptyCells[randomIndex];
}
return null; // 没有空格子返回 null
}
// 在随机选择的空格子中添加一个新方块(2 或 4)
function addRandomTile() {
const cell = getRandomEmptyCell();
if (cell) {
const value = Math.random() < 0.9 ? 2 : 4; // 90% 的概率生成 2,10% 的概率生成 4
matrix[cell.row][cell.col] = value; // 更新矩阵
updateGrid(); // 更新网格显示
}
}
// 更新 HTML 网格中的方块显示
function updateGrid() {
cells.forEach((cell, index) => {
const row = Math.floor(index / 4); // 计算当前单元格所在的行
const col = index % 4; // 计算当前单元格所在的列
const value = matrix[row][col]; // 获取矩阵中的值
cell.textContent = value || ''; // 显示值,如果没有值则显示空字符串
cell.classList.remove('number-2', 'number-4', 'number-8', 'number-16', 'number-32', 'number-64', 'number-128', 'number-256', 'number-512', 'number-1024', 'number-2048'); // 移除所有样式类
if (value !== 0) { // 如果有值,添加对应的样式类
cell.classList.add(`number-${value}`);
}
});
}
// 向上移动
function moveUp() {
for (let col = 0; col < 4; col++) { // 遍历每一列
let tempCol = []; // 临时存储非零值
for (let row = 0; row < 4; row++) {
if (matrix[row][col] !== 0) {
tempCol.push(matrix[row][col]); // 将非零值加入临时列表
}
}
tempCol = mergeTiles(tempCol); // 合并相同的值
for (let row = 0; row < 4; row++) {
matrix[row][col] = tempCol[row] || 0; // 更新矩阵
}
}
addRandomTile(); // 添加新的方块
checkGameOver(); // 检查游戏是否结束
}
// 向下移动
function moveDown() {
for (let col = 0; col < 4; col++) { // 遍历每一列
let tempCol = []; // 临时存储非零值
for (let row = 3; row >= 0; row--) {
if (matrix[row][col] !== 0) {
tempCol.push(matrix[row][col]); // 将非零值加入临时列表
}
}
tempCol = mergeTiles(tempCol); // 合并相同的值
for (let row = 3; row >= 0; row--) {
matrix[row][col] = tempCol[3 - row] || 0; // 更新矩阵
}
}
addRandomTile(); // 添加新的方块
checkGameOver(); // 检查游戏是否结束
}
// 向左移动
function moveLeft() {
for (let row = 0; row < 4; row++) { // 遍历每一行
let tempRow = []; // 临时存储非零值
for (let col = 0; col < 4; col++) {
if (matrix[row][col] !== 0) {
tempRow.push(matrix[row][col]); // 将非零值加入临时列表
}
}
tempRow = mergeTiles(tempRow); // 合并相同的值
for (let col = 0; col < 4; col++) {
matrix[row][col] = tempRow[col] || 0; // 更新矩阵
}
}
addRandomTile(); // 添加新的方块
checkGameOver(); // 检查游戏是否结束
}
// 向右移动
function moveRight() {
for (let row = 0; row < 4; row++) { // 遍历每一行
let tempRow = []; // 临时存储非零值
for (let col = 3; col >= 0; col--) {
if (matrix[row][col] !== 0) {
tempRow.push(matrix[row][col]); // 将非零值加入临时列表
}
}
tempRow = mergeTiles(tempRow); // 合并相同的值
for (let col = 3; col >= 0; col--) {
matrix[row][col] = tempRow[3 - col] || 0; // 更新矩阵
}
}
addRandomTile(); // 添加新的方块
checkGameOver(); // 检查游戏是否结束
}
// 合并相同的值
function mergeTiles(tiles) {
for (let i = 0; i < tiles.length - 1; i++) {
if (tiles[i] === tiles[i + 1]) { // 如果相邻的两个值相同
tiles[i] *= 2; // 合并值
tiles.splice(i + 1, 1); // 删除合并后的值
score += tiles[i]; // 更新分数
scoreContainer.textContent = score; // 更新分数显示
if (tiles[i] === winNumber && !hasWon) { // 如果达到目标值且未赢过
alert('You Win!'); // 提示胜利
hasWon = true; // 标记已赢
}
}
}
while (tiles.length < 4) { // 确保列表长度为 4
tiles.push(0);
}
return tiles;
}
// 检查游戏是否结束
function checkGameOver() {
let gameOver = true; // 默认游戏结束
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (matrix[i][j] === 0 || // 如果有空格子
(i < 3 && matrix[i][j] === matrix[i + 1][j]) || // 或者有相邻的相同值
(j < 3 && matrix[i][j] === matrix[i][j + 1])) {
gameOver = false; // 游戏未结束
break;
}
}
if (!gameOver) break;
}
if (gameOver) {
alert('Game Over!'); // 提示游戏结束
}
}
// 重新开始游戏
function restartGame() {
matrix.forEach(row => row.fill(0)); // 重置矩阵
score = 0; // 重置分数
hasWon = false; // 重置胜利标记
scoreContainer.textContent = score; // 更新分数显示
addRandomTile(); // 添加初始方块
addRandomTile(); // 添加初始方块
updateGrid(); // 更新网格显示
}
// 监听键盘事件
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'ArrowUp': // 上箭头
moveUp();
break;
case 'ArrowDown': // 下箭头
moveDown();
break;
case 'ArrowLeft': // 左箭头
moveLeft();
break;
case 'ArrowRight': // 右箭头
moveRight();
break;
}
});
// 监听重新开始按钮点击事件
restartButton.addEventListener('click', restartGame);
// 初始化游戏
restartGame();
别忘了请点个赞+收藏+关注支持一下博主喵!!!
累了,水一下吧。。。。。。∗︎˚(* ˃̤൬˂̤ *)˚∗︎
gitee:2048-game: html+css+js设计的一个2048游戏(网页)