第一次接触2048是在大学计算机实验室,看着同桌屏幕上不断跳动的数字方块,我完全被这个看似简单却充满魔力的游戏吸引了。作为一款风靡全球的益智游戏,2048不仅考验玩家的策略思维,其背后的算法逻辑更是编程学习的绝佳案例。本文将带你从游戏规则出发,逐步拆解2048的核心算法,并用C语言完整实现其"滑动合并"机制。
2048的游戏规则可以用三个关键词概括:移动、合并、新增。游戏在一个4×4的方格中进行,每个格子要么为空(用0表示),要么包含一个2的幂次方的数字(如2、4、8等)。玩家通过上下左右四个方向指令控制所有数字方块的移动:
关键特性:
注意:2048的合并机制与俄罗斯方块等游戏不同,它要求严格按移动方向顺序处理,这是算法实现的关键点。
实现2048游戏逻辑的核心在于处理两个基本操作:数字合并和空白填充。我们可以将其分解为三个步骤:
无论用户输入哪个方向的指令,我们都可以通过矩阵旋转将其转化为"向左移动"的处理:
c复制// 矩阵顺时针旋转90度
void rotateClockwise(int matrix[4][4]) {
int temp[4][4];
for(int i=0; i<4; i++) {
for(int j=0; j<4; j++) {
temp[j][3-i] = matrix[i][j];
}
}
memcpy(matrix, temp, sizeof(temp));
}
通过旋转,我们可以将不同方向的移动统一转化为向左移动处理,大大简化代码复杂度。
单行向左合并的核心逻辑可分为两个阶段:
c复制void mergeRow(int row[4]) {
// 第一阶段:合并相同数字
for(int i=0; i<3; i++) {
if(row[i] == 0) continue;
for(int j=i+1; j<4; j++) {
if(row[j] == 0) continue;
if(row[i] == row[j]) {
row[i] *= 2;
row[j] = 0;
break;
}
break;
}
}
// 第二阶段:压缩非零数字
int writePos = 0;
for(int readPos=0; readPos<4; readPos++) {
if(row[readPos] != 0) {
row[writePos++] = row[readPos];
}
}
while(writePos < 4) row[writePos++] = 0;
}
结合旋转和行合并,我们可以处理所有方向的移动:
| 方向 | 预处理旋转 | 行处理方向 | 后处理旋转 |
|---|---|---|---|
| 左 | 无 | 从左到右 | 无 |
| 右 | 旋转180度 | 从左到右 | 旋转180度 |
| 上 | 旋转270度 | 从左到右 | 旋转90度 |
| 下 | 旋转90度 | 从左到右 | 旋转270度 |
这种统一处理方式避免了为每个方向编写独立代码,提高了代码的可维护性。
在实际实现中,我们需要考虑一些边界条件和特殊规则:
不是所有用户输入都会改变游戏状态,我们需要检测移动是否有效:
c复制int isMoveValid(int before[4][4], int after[4][4]) {
for(int i=0; i<4; i++) {
for(int j=0; j<4; j++) {
if(before[i][j] != after[i][j]) {
return 1; // 有变化,移动有效
}
}
}
return 0; // 无变化,移动无效
}
游戏结束有两种情况:
c复制int isGameOver(int board[4][4]) {
// 检查是否有空格
for(int i=0; i<4; i++) {
for(int j=0; j<4; j++) {
if(board[i][j] == 0) return 0;
if(board[i][j] == 2048) return 2; // 胜利
}
}
// 检查是否有相邻相同数字
for(int i=0; i<4; i++) {
for(int j=0; j<4; j++) {
if(i<3 && board[i][j]==board[i+1][j]) return 0;
if(j<3 && board[i][j]==board[i][j+1]) return 0;
}
}
return 1; // 游戏结束
}
结合上述分析,我们可以给出XTU-OJ 1239题的完整解法。题目要求实现2048游戏的单步移动处理,这与我们讨论的核心逻辑完全一致。
我们采用模块化设计,将不同方向的处理统一转化为向左移动:
c复制#include <stdio.h>
#include <string.h>
void rotateClockwise(int matrix[4][4]) {
int temp[4][4];
for(int i=0; i<4; i++) {
for(int j=0; j<4; j++) {
temp[j][3-i] = matrix[i][j];
}
}
memcpy(matrix, temp, sizeof(temp));
}
void processLeft(int board[4][4]) {
for(int i=0; i<4; i++) {
// 合并相同数字
for(int j=0; j<3; j++) {
if(board[i][j] == 0) continue;
for(int k=j+1; k<4; k++) {
if(board[i][k] == 0) continue;
if(board[i][j] == board[i][k]) {
board[i][j] *= 2;
board[i][k] = 0;
}
break;
}
}
// 压缩非零数字
int writePos = 0;
for(int readPos=0; readPos<4; readPos++) {
if(board[i][readPos] != 0) {
board[i][writePos++] = board[i][readPos];
}
}
while(writePos < 4) board[i][writePos++] = 0;
}
}
void processMove(int board[4][4], char* direction) {
if(strcmp(direction, "LEFT") == 0) {
processLeft(board);
} else if(strcmp(direction, "RIGHT") == 0) {
rotateClockwise(board);
rotateClockwise(board);
processLeft(board);
rotateClockwise(board);
rotateClockwise(board);
} else if(strcmp(direction, "UP") == 0) {
rotateClockwise(board);
rotateClockwise(board);
rotateClockwise(board);
processLeft(board);
rotateClockwise(board);
} else if(strcmp(direction, "DOWN") == 0) {
rotateClockwise(board);
processLeft(board);
rotateClockwise(board);
rotateClockwise(board);
rotateClockwise(board);
}
}
主程序负责处理输入输出,调用上述函数完成题目要求:
c复制int main() {
int T;
scanf("%d", &T);
while(T--) {
int board[4][4];
char direction[10];
// 读取输入
for(int i=0; i<4; i++) {
for(int j=0; j<4; j++) {
scanf("%d", &board[i][j]);
}
}
scanf("%s", direction);
// 处理移动
processMove(board, direction);
// 输出结果
for(int i=0; i<4; i++) {
for(int j=0; j<3; j++) {
printf("%d ", board[i][j]);
}
printf("%d\n", board[i][3]);
}
printf("\n");
}
return 0;
}
对于算法竞赛,我们可以进一步优化:
c复制// 优化后的行处理函数
void processRowOptimized(int row[4]) {
// 合并和压缩一步完成
int writePos = 0;
for(int readPos=0; readPos<4; readPos++) {
if(row[readPos] == 0) continue;
if(writePos>0 && row[readPos]==row[writePos-1]) {
row[writePos-1] *= 2;
row[readPos] = 0;
} else {
if(writePos != readPos) {
row[writePos] = row[readPos];
row[readPos] = 0;
}
writePos++;
}
}
}
在实际开发中,2048游戏还可以加入动画效果、分数计算、撤销功能等扩展特性。但核心算法始终围绕着"滑动合并"这一基本机制展开。理解了这个核心,无论是解决OJ题目还是开发完整游戏,都将游刃有余。