HOME> 世界杯推荐> #如何写一个 2048 小游戏?

#如何写一个 2048 小游戏?

2026-02-21 07:32:24

# 如何写一个 2048 小游戏? # 一、2048 是什么 2048 是一款滑块类游戏,由意大利程序员 Gabriele Cirulli 编写并在 GitHub 上开源。

游戏在一个 4 x 4 的网格上通过上下左右键来进行移动,方块在移动时会被边缘和已经有的方块所阻碍,但如果两个相同的方块在移动时碰撞,它们就会合并成一个新的方块,合并后的方块的值是原来的两倍。

# 二、为什么写 2048 小游戏 其实 2048 在我大二的那个暑假我就用原生的 JavaScript 写完了,这次是同事浏览我的 GitHub 库。

对话如下:

「你还写过 2048 ?」

「是啊」

「可以玩吗?」

「不能。。。」

为了能够让互联网的用户都能玩上,于是我花了几天的业余时间,将原生的 JavaScript 移植成 Vue3 版本,并且加上了排行榜。

大学时候想加排行榜,当时不会 NodeJs 也不会 Java,不想写 PHP,于是排行榜没做成,现在算是弥补某种遗憾了。

# 三、开发思路与步骤 游戏设计的核心是数值,其实 2048 游戏就是一个 4x4 二维数组上的数值变化。

# 3.1 布局 布局采用了绝对定位,每个方块会有各自的一个位置。

v-for="(cell, columnIndex) in row"

:key="rowIndex + columnIndex"

class="grid-cell"

:id="'grid-cell-' + rowIndex + '-' + columnIndex">

class="number-cell"

:id="'number-cell-' + rowIndex + '-' + columnIndex"

v-show="shouldShowCell(rowIndex, columnIndex)"

v-text="cell">

123456789101112131415161718192021# 3.2 UI 设计 方块的值不同,背景颜色和字体颜色要改变,如果超过了 1000 要更改字体大小。

# 3.3 游戏逻辑 # 3.3.1 开始游戏 添加键盘事件 document.addEventListener('keyup', processKeyUp);

1以便之后通过上下左右来移动滑块。

初始化网格 初始化网格位置,方块的值全部置为 0,模板上通过 v-show让方块值为 0 的不显示。

在网格上生成两个随机位置,值是 2 或者 4。 条件:

网格上还存在空间,没有空间就无法生成。 随机位置不能是已经有值的位置,我们是生成,不是更新。 对随机位置生成的方块添加背景颜色和字体颜色。 /**

* 判断棋盘中还有空间吗

*

* @returns true->还有空间, false->没有

*/

function isChessBoardExistSpace() {

for (let i = 0; i < 4; i++) {

for (let j = 0; j < 4; j++) {

// 还有空间

if (chessBoard[i][j] === 0) {

return false;

}

}

}

return true;

}

/**

* 生成数字

*/

function showNumberWithAnimation(i, j, randNumber) {

let numberCell = document.getElementById('number-cell-' + i + '-' + j);

if (numberCell) {

numberCell.style.top = getPosTop(i, j) + 'px';

numberCell.style.left = getPosLeft(i, j) + 'px';

numberCell.style.width = '100px';

numberCell.style.height = '100px';

// 获取随机数值的背景颜色和字体颜色

numberCell.style.backgroundColor = getNumberBackgroundColor(randNumber);

numberCell.style.color = getNumberColor(randNumber);

numberCell.textContent = randNumber;

}

}

/**

* 随机生成数字

*

* @returns

*/

function generateOneNumber() {

// 棋盘中还有空间就生成数字

if (isChessBoardExistSpace()) {

// 没有空间返回 false

return false;

}

// 随机生成 0-4 的位置不包括 4

let randX = parseInt(Math.floor(Math.random() * 4));

let randY = parseInt(Math.floor(Math.random() * 4));

// 判断位置是否可用

while (true) {

if (chessBoard[randX][randY] === 0) {

// 位置可用跳出死循环,不可用继续找

break;

}

randX = parseInt(Math.floor(Math.random() * 4));

randY = parseInt(Math.floor(Math.random() * 4));

}

// 随机生成 2 或 4,它们的概率相同

var randNumber = Math.random() > 0.5 ? 2 : 4;

// 在随机位置显示随机数字 2 或 4

chessBoard[randX][randY] = randNumber;

showNumberWithAnimation(randX, randY, randNumber);

}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869# 3.3.2 移动方块 点击上下左右按键 /**

* 点击上下左右按键

*/

function processKeyUp(e) {

var keyCode = e.keyCode;

// 点击上键

if (keyCode === 38) {

if (moveUp()) {

afterMove();

}

return;

}

// 点击左键

if (keyCode === 37) {

if (moveLeft()) {

afterMove();

}

return;

}

if (keyCode === 39) {

if (moveRight()) {

afterMove();

}

return;

}

if (keyCode === 40) {

if (moveDown()) {

afterMove();

}

return;

}

}

1234567891011121314151617181920212223242526272829303132333435判断是否可以向上移动 比如点击了上键,所有的方块都要向上移动,这时要判断可不可以向上移动,其他方向也是类似。

点击上键,第一行不变,因此从第二行开始,对数值不为0 的方块都去判断可不可以移动到它的上一个方块,有两种可能性:

上面那个方块格子的值为 0,表明为空,因此可以移动 上面那个方块格子的值与我当前的值相同,表示合并,因此可以移动。 /**

* 判断是否能向上移动

* 1. 上面那个格子的值为 0,表示是空的,因此可以移动过去

* 2. 上面那个格子的值与当前值相同,表示可以合并,因此也可以移动过去

*

* @return 可以返回 true, 不能返回 false

*/

function canMoveUp() {

for (let i = 1; i < 4; i++) {

for (let j = 0; j < 4; j++) {

if (chessBoard[i][j] !== 0) {

if (

chessBoard[i - 1][j] === 0 ||

chessBoard[i - 1][j] === chessBoard[i][j]

) {

// 可以向上移动

return true;

}

}

}

}

return false;

}

1234567891011121314151617181920212223对方块移动进行处理 先说垂直移动时的障碍物判断,比如说从最下方上移到最上方,只要中间有一个障碍物,就不可以移动过去。

/**

* 判断垂直障碍物是否存在

* 在同一列的 startRow 和 endRow 中间只要有一个值为 0,说明垂直方向存在障碍物

*

* @return 存在 false, 不存在 true

*/

function noBlockVer(col, startRow, endRow) {

for (let i = startRow + 1; i < endRow; i++) {

// 存在障碍物

if (chessBoard[i][col] !== 0) {

return false;

}

}

return true;

}

123456789101112131415方块移动的核心代码 这里就是整个 2048 最核心的代码了,如果上侧为空,并且中间不存在障碍物,那么直接移动过去。如果上侧的值与当前方块的值相同并且中间不存在障碍物,那么移动过去进行合并,更新分数。

这边都没什么问题,问题在于细节上的 bug。

一种情况:

往上移动时,会出现一种 bug 情况:

实际上,我们想要的:

早上很早起来打算修复这个 bug,发现按了葫芦起了瓢,改动了,其他列会受影响。

晚上回来的时候,改了一会没改成,有点生气,我连一个二维数组的算法都搞不定的话,干脆改行算了。

我认真思索了一下,变成 8,是因为从 [2 2 4 0] 变成 [4 0 4 0],然后底下的那个 4 又往上移动合并成了 8,因此要定义一个一维数组

canMoveTopRowIndex表示每一列上的方块所可以移动到最上方的行索引,刚开始是 0,表明可以移动到第一行。

一旦上方有发生合并,就不能是 0 了,这一列上的最上方的变为 1,这时候 [4 0 4 0] 就只能变成 [4 4 0 0 ],也就是我们想要的效果了。

/**

* 往上移动

*/

function moveUp() {

if (!canMoveUp()) {

// 如果不能移动

return false;

}

// 每一列上的方块可以移动到最顶端的那个行索引

let canMoveTopRowIndex = [0, 0, 0, 0];

for (let rowIndex = 1; rowIndex < 4; rowIndex++) {

for (let columnIndex = 0; columnIndex < 4; columnIndex++) {

if (chessBoard[rowIndex][columnIndex] !== 0) {

for (let k = canMoveTopRowIndex[columnIndex]; k < rowIndex; k++) {

if (

chessBoard[k][columnIndex] === 0 &&

noBlockVer(columnIndex, k, rowIndex)

) {

// 上侧为空,不存在障碍物

showMoveAnimation(rowIndex, columnIndex, k, columnIndex);

// 移动过去

chessBoard[k][columnIndex] = chessBoard[rowIndex][columnIndex];

// 之前的消失

chessBoard[rowIndex][columnIndex] = 0;

continue;

} else if (

chessBoard[k][columnIndex] === chessBoard[rowIndex][columnIndex] &&

noBlockVer(columnIndex, k, rowIndex)

) {

showMoveAnimation(rowIndex, columnIndex, k, columnIndex);

chessBoard[k][columnIndex] = 2 * chessBoard[rowIndex][columnIndex];

chessBoard[rowIndex][columnIndex] = 0;

score.value = score.value + chessBoard[k][columnIndex];

canMoveTopRowIndex[columnIndex] = k + 1;

continue;

}

}

}

}

}

return true;

}

1234567891011121314151617181920212223242526272829303132333435363738394041424344# 3.3.3 游戏结束的判断 游戏结束要同时满足以下两个条件

方格上没有空白空间了 方格上的所有点都不能移动了,上下左右四个方向都不能移动了。 空白空间的判断

/**

* 判断棋盘中还有空间吗

*/

function noSpace() {

for (let i = 0; i < 4; i++) {

for (let j = 0; j < 4; j++) {

// 还有空间

if (chessBoard[i][j] === 0) {

return false;

}

}

}

return true;

}

1234567891011121314判断是否可以移动

/**

* 判断是否可以移动

*

* @returns false 可以移动, true 无法移动

*/

function noMove() {

// 只要有一个方向可以移动,就能移动

if (canMoveDown() || canMoveLeft() || canMoveRight() || canMoveUp()) {

return false;

}

// 无法移动

return true;

}

1234567891011121314# 四、游戏结果

排行榜

我只玩到了第十名,他们实在太卷了,最后感谢 SAKURA和其他同学的大力捧场,谢谢大家的支持。

GitHub 仓库地址:https://github.com/stevenling/vue-2048 (opens new window)

游戏体验地址:http://2048.yunhu.wiki/ (opens new window)

《双截龙》系列究竟讲了啥剧情,为什么受虐的总是女友玛丽安?

唯品会运费,唯品会快递收费标准