引言
在C语言学习阶段,通过实战项目巩固知识点是最高效的方式。本文分享一个基于C语言的2048游戏,实现了四方向滑动、数字合并、胜负判定等核心功能,涵盖结构体设计、二维数组操作、随机数生成、控制台绘图等关键技术点,代码精简不到200行,非常适合作为入门级项目练手。
一、项目介绍
- 开发背景
2048是一款风靡全球的数字益智游戏,玩家通过滑动方块使相同数字合并,最终目标是合成2048。本文使用纯C语言在控制台环境下实现该游戏,无需任何第三方库,帮助初学者理解游戏逻辑的设计思路。
- 技术栈
编程语言:C语言(C99标准)
核心库:stdio.h(输入输出)、stdlib.h(随机数与系统调用)、stdbool.h(布尔类型)、conio.h(控制台按键输入)、time.h(时间种子)
运行环境:Windows控制台
- 核心功能
| 功能编号 | 功能名称 | 功能描述 |
|---|---|---|
| 1 | 游戏初始化 | 清空棋盘,随机生成两个初始数字 |
| 2 | 四方向滑动 | 支持↑↓←→四个方向移动方块 |
| 3 | 数字合并 | 相邻相同数字自动合并(2+2=4) |
| 4 | 随机生成 | 每次移动后随机生成2或4 |
| 5 | 胜负判定 | 达到2048胜利,无法移动则失败 |
| 6 | 退出游戏 | 按Q键安全退出 |
二、核心技术要点
- 结构体设计
使用结构体封装游戏状态,将棋盘数据和游戏状态绑定在一起,便于函数间传递数据:
typedef struct { int board[4][4]; // 4x4游戏棋盘,0表示空格 bool isWin; // 是否胜利(达到2048) bool isOver; // 是否游戏结束(无法移动) } GameState;- 移动算法设计
以左移为核心,其他方向通过变换复用左移逻辑:
- 右移 = 反转行 + 左移 + 反转行
- 上移 = 矩阵转置 + 左移 + 矩阵转置
- 下移 = 矩阵转置 + 右移 + 矩阵转置
- 控制台绘图
使用Unicode制表符绘制棋盘边框:
- ┌ ┬ ┐ 表示上边框
- ├ ┼ ┤ 表示中间分隔线
- └ ┴ ┘ 表示下边框
- │ 表示竖线分隔
- 按键处理
使用conio.h库的_getch()函数捕获方向键:
- 72 = 上箭头
- 80 = 下箭头
- 75 = 左箭头
- 77 = 右箭头
- 随机数生成
使用srand(time(NULL))初始化随机种子,保证每次游戏随机性不同:
- 90%概率生成数字2
- 10%概率生成数字4
三、核心功能解析
- 游戏初始化(initGame函数)
核心逻辑:
- 清空棋盘所有格子为0
- 初始化游戏状态标志
- 设置随机数种子
- 生成两个初始数字
代码部分:
void initGame(GameState* game) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = 0; game->isWin = false; game->isOver = false; srand((unsigned int)time(NULL)); generateNewNumber(game); generateNewNumber(game); }- 棋盘打印(printBoard函数)
核心逻辑:
- 清屏后打印标题和操作提示
- 使用Unicode字符绘制4x4网格
- 根据数字位数自动调整对齐
- 显示游戏胜利或失败状态
代码部分:
void printBoard(GameState* game) { system("cls"); printf("================= 2048 =================\n"); printf("操作: ↑ ↓ ← → 退出:Q\n\n"); printf("┌─────────┬─────────┬─────────┬─────────┐\n"); for (int i = 0; i < 4; i++) { printf("│"); for (int j = 0; j < 4; j++) { if (game->board[i][j] == 0) printf(" │"); else if (game->board[i][j] < 10) printf(" %d │", game->board[i][j]); else if (game->board[i][j] < 100) printf(" %d │", game->board[i][j]); else if (game->board[i][j] < 1000) printf(" %d │", game->board[i][j]); else printf(" %d │", game->board[i][j]); } printf("\n"); if (i < 3) printf("├─────────┼─────────┼─────────┼─────────┤\n"); else printf("└─────────┴─────────┴─────────┴─────────┘\n"); } if (game->isWin) printf("\n恭喜!你赢了!\n"); else if (game->isOver) printf("\n游戏结束!\n"); }- 左移操作(moveLeft函数)
核心逻辑:
- 遍历每一行,将非零数字靠左存储
- 合并相邻相同数字(每个数字每轮只能合并一次)
- 合并后再次靠左,更新棋盘并返回是否发生移动
代码部分:
bool moveLeft(GameState* game) { bool moved = false; int temp[4]; for (int i = 0; i < 4; i++) { int idx = 0; for (int j = 0; j < 4; j++) if (game->board[i][j] != 0) temp[idx++] = game->board[i][j]; while (idx < 4) temp[idx++] = 0; for (int j = 0; j < 3; j++) if (temp[j] != 0 && temp[j] == temp[j+1]) { temp[j] *= 2; temp[j+1] = 0; moved = true; } idx = 0; int newRow[4] = {0}; for (int j = 0; j < 4; j++) if (temp[j] != 0) newRow[idx++] = temp[j]; for (int j = 0; j < 4; j++) if (game->board[i][j] != newRow[j]) { moved = true; game->board[i][j] = newRow[j]; } } return moved; }- 其他方向移动(复用左移)
核心逻辑:
- 右移:先反转每行,再左移,再反转恢复
- 上移:先转置矩阵,再左移,再转置恢复
- 下移:先转置矩阵,再右移,再转置恢复
代码部分:
bool moveRight(GameState* game) { bool moved = false; for (int i = 0; i < 4; i++) for (int j = 0; j < 2; j++) { int t = game->board[i][j]; game->board[i][j] = game->board[i][3-j]; game->board[i][3-j] = t; } moved = moveLeft(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 2; j++) { int t = game->board[i][j]; game->board[i][j] = game->board[i][3-j]; game->board[i][3-j] = t; } return moved; } bool moveUp(GameState* game) { bool moved = false; int temp[4][4]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; moved = moveLeft(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; return moved; } bool moveDown(GameState* game) { bool moved = false; int temp[4][4]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; moved = moveRight(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; return moved; }- 随机生成数字(generateNewNumber函数)
核心逻辑:
- 检查棋盘是否已满
- 随机选择一个空位置
- 90%概率生成2,10%概率生成4
代码部分:
void generateNewNumber(GameState* game) { if (isBoardFull(game)) return; int x, y; do { x = rand() % 4; y = rand() % 4; } while (game->board[x][y] != 0); game->board[x][y] = (rand() % 10 == 0) ? 4 : 2; }- 胜负判定(checkGameState函数)
核心逻辑:
- 遍历棋盘检查是否有2048,有则胜利
- 调用canMove检查是否还能继续移动,不能则失败
代码部分:
void checkGameState(GameState* game) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (game->board[i][j] == 2048) { game->isWin = true; return; } if (!canMove(game)) game->isOver = true; } bool canMove(GameState* game) { if (!isBoardFull(game)) return true; for (int i = 0; i < 4; i++) for (int j = 0; j < 3; j++) if (game->board[i][j] == game->board[i][j+1]) return true; for (int j = 0; j < 4; j++) for (int i = 0; i < 3; i++) if (game->board[i][j] == game->board[i+1][j]) return true; return false; }- 主函数(main)
核心逻辑:
- 初始化游戏
- 循环:打印棋盘 → 获取按键 → 执行移动 → 生成新数字 → 检查状态
- 游戏结束时等待用户按键退出
代码部分:
int main() { GameState game; initGame(&game); while (true) { printBoard(&game); if (game.isWin || game.isOver) break; char input = _getch(); bool moved = false; switch (input) { case 72: moved = moveUp(&game); break; case 80: moved = moveDown(&game); break; case 75: moved = moveLeft(&game); break; case 77: moved = moveRight(&game); break; case 'q': case 'Q': return 0; default: continue; } if (moved) { generateNewNumber(&game); checkGameState(&game); } } printf("\n按任意键退出\n"); _getch(); return 0; }四、运行效果演示
示例 1:游戏初始界面
================= 2048 ================= 操作: ↑ ↓ ← → 退出:Q ┌─────────┬─────────┬─────────┬─────────┐ │ │ 2 │ │ │ ├─────────┼─────────┼─────────┼─────────┤ │ │ │ │ 4 │ ├─────────┼─────────┼─────────┼─────────┤ │ │ │ │ │ ├─────────┼─────────┼─────────┼─────────┤ │ │ │ │ │ └─────────┴─────────┴─────────┴─────────┘示例 2:游戏中界面
================= 2048 ================= 操作: ↑ ↓ ← → 退出:Q ┌─────────┬─────────┬─────────┬─────────┐ │ │ 2 │ │ 4 │ ├─────────┼─────────┼─────────┼─────────┤ │ 8 │ 16 │ 4 │ │ ├─────────┼─────────┼─────────┼─────────┤ │ │ 2 │ 8 │ 2 │ ├─────────┼─────────┼─────────┼─────────┤ │ 4 │ │ 4 │ 8 │ └─────────┴─────────┴─────────┴─────────┘示例 3:游戏胜利
================= 2048 ================= 操作: ↑ ↓ ← → 退出:Q ┌─────────┬─────────┬─────────┬─────────┐ │ 2 │ 4 │ 8 │ 16 │ ├─────────┼─────────┼─────────┼─────────┤ │ 64 │ 32 │ 16 │ 8 │ ├─────────┼─────────┼─────────┼─────────┤ │ 256 │ 128 │ 64 │ 32 │ ├─────────┼─────────┼─────────┼─────────┤ │ 16 │ 1024 │ 512 │ 2048 │ └─────────┴─────────┴─────────┴─────────┘ 恭喜!你赢了!示例 4:游戏失败
================= 2048 ================= 操作: ↑ ↓ ← → 退出:Q ┌─────────┬─────────┬─────────┬─────────┐ │ 2 │ 4 │ 2 │ 4 │ ├─────────┼─────────┼─────────┼─────────┤ │ 4 │ 2 │ 4 │ 2 │ ├─────────┼─────────┼─────────┼─────────┤ │ 2 │ 4 │ 2 │ 4 │ ├─────────┼─────────┼─────────┼─────────┤ │ 4 │ 2 │ 4 │ 2 │ └─────────┴─────────┴─────────┴─────────┘ 游戏结束!五、项目总结与改进方向
- 项目收获
- 掌握C语言结构体的定义与使用
- 理解二维数组的操作技巧
- 学会矩阵转置、反转等算法应用
- 掌握随机数生成与种子初始化
- 学会使用控制台字符绘制简单图形
- 改进方向
- 添加分数统计功能
- 实现撤销功能
- 添加最高分记录
- 跨平台支持(Linux/macOS)
- 图形界面(SDL/OpenGL)
六、完整源码
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <conio.h> #include <time.h> typedef struct { int board[4][4]; bool isWin; bool isOver; } GameState; void initGame(GameState* game); void printBoard(GameState* game); void generateNewNumber(GameState* game); bool moveLeft(GameState* game); bool moveRight(GameState* game); bool moveUp(GameState* game); bool moveDown(GameState* game); void checkGameState(GameState* game); bool isBoardFull(GameState* game); bool canMove(GameState* game); void initGame(GameState* game) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = 0; game->isWin = false; game->isOver = false; srand((unsigned int)time(NULL)); generateNewNumber(game); generateNewNumber(game); } void printBoard(GameState* game) { system("cls"); printf("================= 2048 =================\n"); printf("操作: ↑ ↓ ← → 退出:Q\n\n"); printf("┌─────────┬─────────┬─────────┬─────────┐\n"); for (int i = 0; i < 4; i++) { printf("│"); for (int j = 0; j < 4; j++) { if (game->board[i][j] == 0) printf(" │"); else if (game->board[i][j] < 10) printf(" %d │", game->board[i][j]); else if (game->board[i][j] < 100) printf(" %d │", game->board[i][j]); else if (game->board[i][j] < 1000) printf(" %d │", game->board[i][j]); else printf(" %d │", game->board[i][j]); } printf("\n"); if (i < 3) printf("├─────────┼─────────┼─────────┼─────────┤\n"); else printf("└─────────┴─────────┴─────────┴─────────┘\n"); } if (game->isWin) printf("\n恭喜!你赢了!\n"); else if (game->isOver) printf("\n游戏结束!\n"); } void generateNewNumber(GameState* game) { if (isBoardFull(game)) return; int x, y; do { x = rand() % 4; y = rand() % 4; } while (game->board[x][y] != 0); game->board[x][y] = (rand() % 10 == 0) ? 4 : 2; } bool isBoardFull(GameState* game) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (game->board[i][j] == 0) return false; return true; } bool moveLeft(GameState* game) { bool moved = false; int temp[4]; for (int i = 0; i < 4; i++) { int idx = 0; for (int j = 0; j < 4; j++) if (game->board[i][j] != 0) temp[idx++] = game->board[i][j]; while (idx < 4) temp[idx++] = 0; for (int j = 0; j < 3; j++) if (temp[j] != 0 && temp[j] == temp[j+1]) { temp[j] *= 2; temp[j+1] = 0; moved = true; } idx = 0; int newRow[4] = {0}; for (int j = 0; j < 4; j++) if (temp[j] != 0) newRow[idx++] = temp[j]; for (int j = 0; j < 4; j++) if (game->board[i][j] != newRow[j]) { moved = true; game->board[i][j] = newRow[j]; } } return moved; } bool moveRight(GameState* game) { bool moved = false; for (int i = 0; i < 4; i++) for (int j = 0; j < 2; j++) { int t = game->board[i][j]; game->board[i][j] = game->board[i][3-j]; game->board[i][3-j] = t; } moved = moveLeft(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 2; j++) { int t = game->board[i][j]; game->board[i][j] = game->board[i][3-j]; game->board[i][3-j] = t; } return moved; } bool moveUp(GameState* game) { bool moved = false; int temp[4][4]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; moved = moveLeft(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; return moved; } bool moveDown(GameState* game) { bool moved = false; int temp[4][4]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; moved = moveRight(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; return moved; } bool canMove(GameState* game) { if (!isBoardFull(game)) return true; for (int i = 0; i < 4; i++) for (int j = 0; j < 3; j++) if (game->board[i][j] == game->board[i][j+1]) return true; for (int j = 0; j < 4; j++) for (int i = 0; i < 3; i++) if (game->board[i][j] == game->board[i+1][j]) return true; return false; } void checkGameState(GameState* game) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (game->board[i][j] == 2048) { game->isWin = true; return; } if (!canMove(game)) game->isOver = true; } int main() { GameState game; initGame(&game); while (true) { printBoard(&game); if (game.isWin || game.isOver) break; char input = _getch(); bool moved = false; switch (input) { case 72: moved = moveUp(&game); break; case 80: moved = moveDown(&game); break; case 75: moved = moveLeft(&game); break; case 77: moved = moveRight(&game); break; case 'q': case 'Q': printf("游戏退出\n"); return 0; default: continue; } if (moved) { generateNewNumber(&game); checkGameState(&game); } } printf("\n按任意键退出\n"); _getch(); return 0; }