一、引言
C语言作为编程界的经典语言,具有高效、灵活等特点,非常适合进行小游戏开发。本文将带领大家从零开始开发一款经典的2048数字拼图游戏,通过这个项目,你将掌握C语言的基本语法、函数使用、数组操作以及简单的游戏逻辑实现。
二、游戏设计思路
2048游戏的基本规则是玩家通过方向键移动数字方块,相同数字的方块相撞时会合并成为它们的和,每次移动后会在空白位置随机生成一个新的数字2或4,当界面中出现2048这个数字时游戏胜利,当界面被填满且无法进行有效移动时游戏失败。我们需要设计以下几个核心模块:
- 游戏界面初始化
- 数字方块的移动与合并
- 随机数字生成
- 游戏状态检测
- 游戏状态更新与显示
三、核心源码实现
下面是2048数字拼图游戏的完整源码实现:
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <windows.h>
// 定义游戏区域大小
#define SIZE 4
// 定义方向键值
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
#define ESC 27
// 定义游戏状态
enum Status {
RUNNING,
WON,
LOST,
EXIT
};
// 游戏的结构体
typedef struct {
int board[SIZE][SIZE]; // 游戏面板
int score; // 分数
enum Status status; // 游戏状态
} Game;
// 函数声明
void initGame(Game *game);
void drawGame(Game *game);
void moveBoard(Game *game, int direction);
int canMove(Game *game, int direction);
void generateNewNumber(Game *game);
int checkWin(Game *game);
int checkLose(Game *game);
void setCursorPosition(int x, int y);
void hideCursor();
// 初始化游戏
void initGame(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->status = RUNNING;
// 随机生成两个初始数字
generateNewNumber(game);
generateNewNumber(game);
// 隐藏光标
hideCursor();
}
// 绘制游戏界面
void drawGame(Game *game) {
system("cls"); // 清屏
// 显示游戏标题
printf("\n\n\t\t2048 Puzzle Game\n\n");
// 显示分数
printf("\t\tScore: %d\n\n", game->score);
// 绘制游戏面板边框
printf("\t\t");
for (int i = 0; i < SIZE; i++) {
printf("--------");
}
printf("-\n");
// 绘制游戏面板
for (int i = 0; i < SIZE; i++) {
printf("\t\t");
for (int j = 0; j < SIZE; j++) {
if (game->board[i][j] == 0) {
printf("| ");
} else {
printf("|%6d", game->board[i][j]);
}
}
printf("|\n");
// 绘制分隔线
printf("\t\t");
for (int j = 0; j < SIZE; j++) {
printf("--------");
}
printf("-\n");
}
// 显示游戏提示
printf("\n\t\tUse arrow keys to move. Press ESC to exit.\n");
// 显示游戏状态
if (game->status == WON) {
printf("\n\t\tCongratulations! You won!\n");
} else if (game->status == LOST) {
printf("\n\t\tGame Over! You lost.\n");
}
}
// 移动游戏面板
void moveBoard(Game *game, int direction) {
int moved = 0;
int merged[SIZE][SIZE] = {0}; // 记录每个位置是否已经合并过
// 根据方向移动
switch (direction) {
case UP:
for (int j = 0; j < SIZE; j++) {
for (int i = 1; i < SIZE; i++) {
if (game->board[i][j] != 0) {
int newPos = i;
// 找到可以移动到的位置
while (newPos > 0 && game->board[newPos-1][j] == 0) {
newPos--;
}
// 检查是否可以合并
if (newPos > 0 && game->board[newPos-1][j] == game->board[i][j] && !merged[newPos-1][j]) {
game->board[newPos-1][j] *= 2;
game->score += game->board[newPos-1][j];
game->board[i][j] = 0;
merged[newPos-1][j] = 1;
moved = 1;
}
// 移动到新位置
else if (newPos != i) {
game->board[newPos][j] = game->board[i][j];
game->board[i][j] = 0;
moved = 1;
}
}
}
}
break;
case DOWN:
for (int j = 0; j < SIZE; j++) {
for (int i = SIZE-2; i >= 0; i--) {
if (game->board[i][j] != 0) {
int newPos = i;
// 找到可以移动到的位置
while (newPos < SIZE-1 && game->board[newPos+1][j] == 0) {
newPos++;
}
// 检查是否可以合并
if (newPos < SIZE-1 && game->board[newPos+1][j] == game->board[i][j] && !merged[newPos+1][j]) {
game->board[newPos+1][j] *= 2;
game->score += game->board[newPos+1][j];
game->board[i][j] = 0;
merged[newPos+1][j] = 1;
moved = 1;
}
// 移动到新位置
else if (newPos != i) {
game->board[newPos][j] = game->board[i][j];
game->board[i][j] = 0;
moved = 1;
}
}
}
}
break;
case LEFT:
for (int i = 0; i < SIZE; i++) {
for (int j = 1; j < SIZE; j++) {
if (game->board[i][j] != 0) {
int newPos = j;
// 找到可以移动到的位置
while (newPos > 0 && game->board[i][newPos-1] == 0) {
newPos--;
}
// 检查是否可以合并
if (newPos > 0 && game->board[i][newPos-1] == game->board[i][j] && !merged[i][newPos-1]) {
game->board[i][newPos-1] *= 2;
game->score += game->board[i][newPos-1];
game->board[i][j] = 0;
merged[i][newPos-1] = 1;
moved = 1;
}
// 移动到新位置
else if (newPos != j) {
game->board[i][newPos] = game->board[i][j];
game->board[i][j] = 0;
moved = 1;
}
}
}
}
break;
case RIGHT:
for (int i = 0; i < SIZE; i++) {
for (int j = SIZE-2; j >= 0; j--) {
if (game->board[i][j] != 0) {
int newPos = j;
// 找到可以移动到的位置
while (newPos < SIZE-1 && game->board[i][newPos+1] == 0) {
newPos++;
}
// 检查是否可以合并
if (newPos < SIZE-1 && game->board[i][newPos+1] == game->board[i][j] && !merged[i][newPos+1]) {
game->board[i][newPos+1] *= 2;
game->score += game->board[i][newPos+1];
game->board[i][j] = 0;
merged[i][newPos+1] = 1;
moved = 1;
}
// 移动到新位置
else if (newPos != j) {
game->board[i][newPos] = game->board[i][j];
game->board[i][j] = 0;
moved = 1;
}
}
}
}
break;
}
// 如果有移动,生成新数字并检查游戏状态
if (moved) {
generateNewNumber(game);
if (checkWin(game)) {
game->status = WON;
} else if (checkLose(game)) {
game->status = LOST;
}
}
}
// 检查是否可以向某个方向移动
int canMove(Game *game, int direction) {
// 创建游戏面板副本
int copy[SIZE][SIZE];
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
copy[i][j] = game->board[i][j];
}
}
// 模拟移动
moveBoard(game, direction);
// 检查是否有变化
int changed = 0;
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
if (copy[i][j] != game->board[i][j]) {
changed = 1;
}
// 恢复原游戏面板
game->board[i][j] = copy[i][j];
}
}
return changed;
}
// 随机生成一个新数字(2或4)
void generateNewNumber(Game *game) {
// 找出所有空白位置
int emptyCells[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) {
emptyCells[count][0] = i;
emptyCells[count][1] = j;
count++;
}
}
}
if (count > 0) {
// 随机选择一个空白位置
srand(time(NULL));
int index = rand() % count;
int x = emptyCells[index][0];
int y = emptyCells[index][1];
// 90%概率生成2,10%概率生成4
int value = (rand() % 10 < 9) ? 2 : 4;
game->board[x][y] = value;
}
}
// 检查是否获胜
int checkWin(Game *game) {
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
if (game->board[i][j] >= 2048) {
return 1;
}
}
}
return 0;
}
// 检查是否失败
int checkLose(Game *game) {
// 检查是否还有空白位置
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
if (game->board[i][j] == 0) {
return 0;
}
}
}
// 检查是否还能向任何方向移动
if (canMove(game, UP) || canMove(game, DOWN) || canMove(game, LEFT) || canMove(game, RIGHT)) {
return 0;
}
return 1;
}
// 设置光标位置
void setCursorPosition(int x, int y) {
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
// 隐藏光标
void hideCursor() {
HANDLE hOut;
CONSOLE_CURSOR_INFO ConCurInf;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
ConCurInf.dwSize = 10;
ConCurInf.bVisible = FALSE;
SetConsoleCursorInfo(hOut, &ConCurInf);
}
int main() {
Game game;
initGame(&game);
// 游戏主循环
while (game.status == RUNNING) {
drawGame(&game);
// 获取用户输入
int key = _getch();
// 处理方向键
switch (key) {
case UP:
case DOWN:
case LEFT:
case RIGHT:
moveBoard(&game, key);
break;
case ESC:
game.status = EXIT;
break;
}
}
// 游戏结束后显示最终状态
drawGame(&game);
// 等待用户按键退出
_getch();
return 0;
}
四、源码解析
1. 数据结构设计
我们使用一个结构体来组织游戏数据:
- `Game` 结构体:存储游戏面板、分数和游戏状态
这种设计使代码结构清晰,便于维护和扩展。
2. 核心功能模块
初始化模块
`initGame` 函数负责游戏的初始化工作,包括设置游戏面板、初始化分数、游戏状态等,并生成两个初始数字。
绘制模块
`drawGame` 函数负责绘制整个游戏界面,包括边框、数字方块、分数信息和游戏状态。使用 `system("cls")` 清屏实现动态效果。
移动与合并模块
`moveBoard` 函数实现数字方块的移动和合并逻辑,根据不同方向键处理不同的移动逻辑,并处理合并情况。
随机数字生成模块
`generateNewNumber` 函数随机生成数字2或4,并将其放置在空白位置上。
游戏状态检测模块
`checkWin` 和 `checkLose` 函数分别检查游戏是否获胜或失败。
3. 游戏控制
在 `main` 函数的游戏主循环中,使用 `_getch()` 函数检测用户按键输入,实现对游戏的控制。
五、游戏优化与扩展建议
1. **增加撤销功能**:允许玩家撤销上一步操作。
2. **添加颜色显示**:根据数字大小显示不同颜色,提升视觉体验。
3. **保存最高分**:使用文件操作保存历史最高分。
4. **添加动画效果**:在数字移动和合并时添加动画效果。
六、总结
通过这个2048数字拼图游戏项目,我们学习了C语言的基本语法、函数使用、结构体和数组操作等知识。游戏开发不仅能够帮助我们巩固编程基础,还能培养逻辑思维和问题解决能力。希望本文能为你打开游戏开发的大门,激发你更多的编程创意!
如果你对代码有任何疑问或建议,欢迎在评论区留言讨论!