以下是使用C语言实现的2048小游戏代码:
#include
#include
#include
#include // 用于Windows平台的_getch()
#define SIZE 4
#define TARGET 2048
// 游戏板结构体
typedef struct
{
int board[SIZE][SIZE];
int score;
int game_over;
int win;
} Game;
// 方向枚举
enum Direction
{
UP,
DOWN,
LEFT,
RIGHT
};
void display(const Game *game);
int check_game_over(const Game *game);
void add_new_tile(Game *game);
void move(Game *game, enum Direction dir);
// 初始化游戏
void init_game(Game *game)
{
// 清空棋盘
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < size j game->board[i][j] = 0;
}
}
game->score = 0;
game->game_over = 0;
game->win = 0;
// 生成两个初始数字
add_new_tile(game);
add_new_tile(game);
}
// 添加新数字(90%概率生成2,10%生成4)
void add_new_tile(Game *game)
{
int empty_cells[SIZE * SIZE][2];
int count = 0;
// 查找所有空格子
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < size j if game->board[i][j] == 0)
{
empty_cells[count][0] = i;
empty_cells[count][1] = j;
count++;
}
}
}
if (count > 0)
{
// 随机选择一个空格子
int index = rand() % count;
int value = (rand() % 10 < 9 2 : 4 game->board[empty_cells[index][0]][empty_cells[index][1]] = value;
}
}
// 打印游戏界面
void display(const Game *game)
{
system("cls"); // 清屏(Windows)
printf("Score: %d\n", game->score);
printf("-------------------------\n");
for (int i = 0; i < SIZE; i++)
{
printf("|");
for (int j = 0; j < size j if game->board[i][j] == 0)
{
printf(" |");
}
else
{
printf("%4d|", game->board[i][j]);
}
}
printf("\n-------------------------\n");
}
}
// 检查游戏是否结束
int check_game_over(const Game *game)
{
// 检查是否有空格子
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < size j if game->board[i][j] == 0)
return 0;
}
}
// 检查是否有相邻相同数字
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < size j int current='game-'>board[i][j];
// 检查右侧和下侧
if (j < size - 1 current='= game-'>board[i][j + 1])
return 0;
if (i < size - 1 current='= game-'>board[i + 1][j])
return 0;
}
}
return 1;
}
// 移动并合并数字(核心逻辑)
void move(Game *game, enum Direction dir)
{
int moved = 0;
int temp_board[SIZE][SIZE];
// 复制当前棋盘状态
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < size j temp_boardij='game-'>board[i][j];
}
}
// 根据方向处理移动
switch (dir)
{
case LEFT:
for (int i = 0; i < SIZE; i++)
{
int pos = 0;
int last_merged = -1;
for (int j = 0; j < size j if temp_boardij if pos> 0 && temp_board[i][pos - 1] == temp_board[i][j] && last_merged != pos - 1)
{
// 合并
temp_board[i][pos - 1] *= 2;
game->score += temp_board[i][pos - 1];
temp_board[i][j] = 0;
last_merged = pos - 1;
moved = 1;
}
else
{
if (pos != j)
moved = 1;
temp_board[i][pos] = temp_board[i][j];
pos++;
}
}
}
// 填充右侧空白
while (pos < SIZE)
{
if (temp_board[i][pos] != 0)
moved = 1;
temp_board[i][pos++] = 0;
}
}
break;
case DOWN:
// 按列处理,从下往上扫描
for (int j = 0; j < size j int pos='SIZE' - 1 int last_merged='SIZE;' for int i='SIZE' - 1 i>= 0; i--)
{ // 从下往上扫描
if (temp_board[i][j] != 0)
{ // 发现非空单元格
// 检查是否可以合并
if (pos < size - 1 temp_boardpos 1j='= temp_board[i][j]' last_merged 1 temp_boardpos 1j game->score += temp_board[pos + 1][j]; // 更新分数
temp_board[i][j] = 0; // 清空当前位置
last_merged = pos + 1; // 记录合并位置
moved = 1; // 标记发生移动
}
else
{
// 无法合并则移动元素
if (pos != i)
{ // 位置发生变化
temp_board[pos][j] = temp_board[i][j];
temp_board[i][j] = 0; // 清空原位置
moved = 1;
}
pos--; // 更新填充位置
}
}
}
// 填充上方空白(非必需但更安全)
while (pos >= 0)
{
if (temp_board[pos][j] != 0)
moved = 1;
temp_board[pos--][j] = 0;
}
}
break;
case RIGHT:
// 按行处理,从右往左扫描
for (int i = 0; i < size i int pos='SIZE' - 1 int last_merged='SIZE;' for int j='SIZE' - 1 j>= 0; j--)
{ // 从右往左扫描
if (temp_board[i][j] != 0)
{ // 发现非空单元格
// 检查是否可以合并
if (pos < size - 1 temp_boardipos 1='= temp_board[i][j]' last_merged 1 temp_boardipos 1 game->score += temp_board[i][pos + 1]; // 更新分数
temp_board[i][j] = 0; // 清空当前位置
last_merged = pos + 1; // 记录合并位置
moved = 1; // 标记发生移动
}
else
{
// 无法合并则移动元素
if (pos != j)
{ // 位置发生变化
temp_board[i][pos] = temp_board[i][j];
temp_board[i][j] = 0; // 清空原位置
moved = 1;
}
pos--; // 更新填充位置
}
}
}
// 填充左侧空白(非必需但更安全)
while (pos >= 0)
{
if (temp_board[i][pos] != 0)
moved = 1;
temp_board[i][pos--] = 0;
}
}
break;
case UP:
// 按列处理,从上往下扫描
for (int j = 0; j < SIZE; j++)
{ // 遍历每一列
int pos = 0; // 从最顶行开始填充
int last_merged = -1; // 初始化最后合并位置(无效位置)
for (int i = 0; i < size i if temp_boardij if pos> 0 && // 确保不是第一个元素
temp_board[pos - 1][j] == temp_board[i][j] && // 值相同
last_merged != pos - 1)
{ // 防止重复合并
// 执行合并
temp_board[pos - 1][j] *= 2;
game->score += temp_board[pos - 1][j]; // 更新分数
temp_board[i][j] = 0; // 清空当前位置
last_merged = pos - 1; // 记录合并位置
moved = 1; // 标记发生移动
}
else
{
// 无法合并则移动元素
if (pos != i)
{ // 位置发生变化
temp_board[pos][j] = temp_board[i][j];
temp_board[i][j] = 0; // 清空原位置
moved = 1;
}
pos++; // 更新填充位置
}
}
}
// 填充下方空白(非必需但更安全)
while (pos < SIZE)
{
if (temp_board[pos][j] != 0)
moved = 1;
temp_board[pos++][j] = 0;
}
}
break;
// 其他方向处理类似(代码略)
// ...
}
// 更新棋盘状态
if (moved)
{
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < size j game->board[i][j] = temp_board[i][j];
}
}
add_new_tile(game);
game->game_over = check_game_over(game);
}
}
// 获取用户输入
enum Direction get_input()
{
int c = _getch();
switch (c)
{
case 72:
return UP; // 方向键上
case 80:
return DOWN; // 方向键下
case 75:
return LEFT; // 方向键左
case 77:
return RIGHT; // 方向键右
case 'q':
exit(0); // 退出游戏
default:
return get_input(); // 无效输入重新获取
}
}
int main()
{
srand(time(NULL)); // 初始化随机种子
Game game;
init_game(&game);
while (!game.game_over && !game.win)
{
display(&game);
enum Direction dir = get_input();
move(&game, dir);
// 检查是否达成2048
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < SIZE; j++)
{
if (game.board[i][j] == TARGET)
{
game.win = 1;
break;
}
}
}
}
display(&game);
if (game.win)
{
printf("Congratulations! You've reached %d!\n", TARGET);
}
else
{
printf("Game Over! Final score: %d\n", game.score);
}
return 0;
}
代码结构解析
- 数据结构设计
- Game结构体:包含游戏板(4x4二维数组)、当前分数、游戏结束标志和胜利标志
- 方向枚举:使用枚举类型定义四个移动方向
- 核心函数说明
- init_game():初始化游戏状态,生成初始数字
- add_new_tile():在随机空白位置生成新数字
- display():控制台界面显示
- check_game_over():检查游戏结束条件
- move():处理移动和合并的核心逻辑
- get_input():获取键盘输入
- 关键算法实现--移动合并算法(以左移为例):
- 按行处理,从左到右扫描
- 将非零数字向左紧凑排列
- 检查相邻相同数字进行合并
- 合并后再次紧凑排列
- 记录移动状态用于判断是否需要生成新数字
- 用户输入处理
- 使用_getch()获取方向键输入
- 处理Windows平台方向键的特殊编码(72/80/75/77)
- 游戏流程控制
- 主循环:显示界面 → 获取输入 → 处理移动 → 检查状态
- 结束条件:达成2048或无法继续移动
游戏特色功能
- 智能合并机制
if (pos > 0 && temp_board[i][pos-1] == temp_board[i][j] && last_merged != pos-1)
- 使用last_merged标记防止同一回合重复合并
- 例如:[2, 2, 2, 2] → [4, 4, 0, 0]而非[8, 0, 0, 0]
- 动态分数计算
game->score += temp_board[i][pos-1];
- 每次合并后累加合并后的值到总分
- 概率生成新数字
int value = (rand() % 10 < 9) ? 2 : 4;
- 90%概率生成2,10%概率生成4
代码详解:move()函数中的DOWN分支:
核心逻辑详解(以列j=0为例)
假设当前列初始状态为:
[2] <- i=0
[ ]
[2] <- i=2
[4] <- i=3 (最下方)
- 初始化参数:
- pos = 3(最底行索引)
- last_merged = 4(无效位置)
- 第一次循环 i=3:
- 发现数字4,直接放入pos=3位置
- pos-- → 2
- 结果:[ ][ ][ ][4]
- 第二次循环 i=2:
- 发现数字2
- 检查下方位置pos+1=3的4 ≠ 2 → 无法合并
- 移动到pos=2位置
- pos-- → 1
- 结果:[ ][ ][2][4]
4.第三次循环 i=1:
- 空单元格 → 跳过
5.第四次循环 i=0:
- 发现数字2
- 检查下方位置pos+1=1的单元格为空
- 移动到pos=1位置
- pos-- → 0
- 最终结果:[ ][2][2][4]
6.填充空白:
- 此时pos=0,将0-pos位置置0(本例无变化)
复杂情况处理示例
案例1:连续合并
输入列:[2][2][2][2]
处理流程:
- i=3发现2 → 放置pos=3
- i=2发现2 → 与pos+1=3合并 → [0][0][0][4]
- i=1发现2 → 移动到pos=2 → [0][0][2][4]
- i=0发现2 → 与pos+1=2合并 → [0][0][0][4+2=6]
最终结果:[0][0][0][8]
案例2:阻挡合并
输入列:[2][4][4][2]
处理流程:
- i=3发现2 → 放置pos=3
- i=2发现4 → 无法合并,移动到pos=2
- i=1发现4 → 与pos+1=2合并 → [0][0][8][2]
- i=0发现2 → 移动到pos=1
最终结果:[0][2][8][2]
关键设计亮点
- 逆向扫描机制:
for (int i = SIZE-1; i >= 0; i--) // 从下往上扫描
- 符合向下移动时"沉底"的物理直觉
- 确保合并操作的正确顺序
2.防重复合并保护:
last_merged != pos+1
- 防止同一位置多次合并
- 例如序列[4][4][8][8]应合并为[0][0][8][16]而不是[0][0][0][32]
3.双重移动检测:
if (pos != i) moved = 1; // 位置变化检测 while (pos >= 0) { ... } // 显式填充检测
- 确保所有移动情况都被记录
- 处理类似[2][ ][ ][ ] → [ ][ ][ ][2]的情况
方向处理统一模式
其他方向的实现可通过调整以下参数实现:
- 遍历顺序:
- 上下方向:列循环在外层
- 左右方向:行循环在外层
- 扫描方向:
- 下/右:逆向扫描(i/j从大到小)
- 上/左:正向扫描(i/j从小到大)
- 位置指针:
- 下/右:从最大索引开始递减
- 上/左:从0开始递增
这种模块化设计使得各方向处理代码保持高度一致性,只需调整循环顺序和方向参数即可实现完整移动逻辑。
扩展建议
- 增加存档功能
void save_game(const Game* game) {
FILE* fp = fopen("save.dat", "wb");
fwrite(game, sizeof(Game), 1, fp);
fclose(fp);
}
2. 实现动画效果
// 使用Windows API实现简单动画
#include
void animate_move(int from_x, int from_y, int to_x, int to_y) {
// 实现移动动画
}
- 添加音效
#include
void play_sound(int type) {
switch(type) {
case 1: PlaySound("merge.wav", NULL, SND_ASYNC); break;
case 2: PlaySound("win.wav", NULL, SND_ASYNC); break;
}
}
该代码完整展现了2048游戏的核心机制,通过模块化设计方便扩展,使用控制台界面确保跨平台兼容性,适合作为学习C语言项目开发的典型案例。