用c语言怎么做出一个大字母游戏,用C语言写一个2048小游戏(用VS2013实现)

本文详细介绍了2048游戏的开发过程,包括游戏逻辑、关键函数moveUp的实现原理及其优化方法。

游戏简介

执行2048程序,进入游戏后屏幕打印如下图所示:

72e75313ab35afde9da20cbea58e6ecd.png

玩家通过u、d、l、r、q来控制游戏:

u ————>> 上

d ————>> 下

l ————>> 左

r ————>> 右

q ————>> 退出

最高分数用来记录游戏中的最大数,当最高分数达到2048的时候,屏幕显示“You Win!”,这个时候玩家可以通过“q”来退出游戏。如下图所示,我将玩家赢的条件设置为32.

f082e64876c3486a49783bcabf2d0d12.png

当界面全部被数字填满且不可移动时如果还没有出现2048,则代表玩家输了,这个时候屏幕显示“Game Over!”,玩家同样可以通过“q”来退出游戏,如下图所示:

26c5f4e6b59b306e1c63e2ddd6b34b91.png

代码分析

1.建立3个文件,分别是game.c、game.h、main.c

game.c用来放一些自定义的函数;game.h用于放函数声明等内容;main.c用于放主函数,组织整个游戏框架。

在程序中,我们用一个NxN的二维数组gameData来存放游戏数据,0代表位置为空。我们用一个一维数组seedList来存放要随机生成的游戏数据,其中包含8个2和2个4,代表2的概率为80%,4的概率为20%。

2、在game.h中中声明如下一些函数:

/*

* 函数名:initGame

* 功能:对游戏界面数据进行初始化,并产生两个随机数

* 参数:需要初始化的游戏数据

* 返回值:无

*/

void initGame(int data[N][N]);

/*

* 函数名:showGame

* 功能:显示游戏界面

* 参数:需要显示的游戏数据

* 返回值:无

*/

void showGame(int data[N][N]);

/*

* 函数名:getRand

* 功能:在游戏数据中产生一个随机数,2的概率为80%,4的概率为20%

* 参数:需要产生随机数的游戏数据

* 返回值:无

*/

void getRand(int data[N][N]);

/*

* 函数名:checkGameOver

* 功能:检查游戏是否结束

* 参数:需要检查的游戏数据

* 返回值:游戏结束返回1,没有结束返回0

*/

int checkGameOver(int data[N][N]);

/*

* 函数名:exitGame

* 功能:退出游戏

* 参数:无

* 返回值:无

*/

void exitGame(void);

/*

* 函数名:maxScore

* 功能:获取游戏数据中的最大数

* 参数:需要查找的游戏数据

* 返回值:游戏数据中的最大值

*/

int maxScore(int data[N][N]);

/*

* 函数名:moveUp

* 功能:把游戏数据上移

* 参数:需要上移的游戏数据

* 返回值:无

*/

void moveUp(int data[N][N]);

/*

* 函数名:moveDown

* 功能:把游戏数据下移

* 参数:需要下移的游戏数据

* 返回值:无

*/

void moveDown(int data[N][N]);

/*

* 函数名:moveLeft

* 功能:把游戏数据左移

* 参数:需要左移的游戏数据

* 返回值:无

*/

void moveLeft(int data[N][N]);

/*

* 函数名:moveRight

* 功能:把游戏数据右移

* 参数:需要右移的游戏数据

* 返回值:无

*/

void moveRight(int data[N][N]);

/*

* 函数名:getInput

* 功能:获取用户的输入

* 参数:无

* 返回值:返回用户输入的按键

*/

int getInput(void);

/*

* 函数名:checkGameWin

* 功能:判断玩家是否赢得比赛

* 参数:游戏数据中的最大值

* 玩家赢则返回1,否则返回0

*/

int checkGameWin(int maxScore);

3、对moveUp()函数的分析

因为其他几个函数的逻辑都非常简单,看我的源代码就可以很容易理解,所以不必详细分析。2048小游戏主要的逻辑难点在上、下、左、右四个移动函数上,而这四个移动函数其实是一个思想,因此我们只需分析moveUp()函数。

moveUp()函数的代码如下:

/*

* 函数名:moveUp

* 功能:把游戏数据上移

* 参数:需要上移的游戏数据

* 返回值:无

*/

void moveUp(int data[N][N])

{

int x = 0, y = 0;

int idx;

int isChange = 0; //可移动标记位

//先累加

for (y = 0; y < N; ++y)

{

for (x = 0; x < N; ++x)

{

if (data[x][y] == 0)

{

continue;

}

//判断是否可加,能加则加

for (idx = x + 1; idx <= N - 1; ++idx)

{

if (data[idx][y] == 0)

{

continue;

}

else if (data[idx][y] != data[x][y])

{

break;

}

else

{

data[x][y] += data[idx][y];//能加则加

data[idx][y] = 0;//加完之后将其置0以便于不影响下次判断,置0后下次就不会判断改位了

isChange = 1;

break;

}

}

}

}

//累加后移动

for (y = 0; y < N; ++y)

{

for (x = 1; x < N; ++x)

{

if (data[x][y] == 0)

{

continue;

}

idx = x - 1;//从x-1位置开始查

while (data[idx][y] == 0 && idx >= 0)//一直到非零或者超出范围结束

{

--idx;

}

if (data[idx + 1][y] == 0)

{

data[idx + 1][y] = data[x][y];

data[x][y] = 0;

isChange = 1;

}

}

}

//成功移动之后需要随机产生一个数

if(isChange == 1)

{

getRand(data);

}

}

moveUp()函数主要由三部分来实现:先累加、累加后移动、成功移动之后需要随机产生一个数。

(1)先累加

for (y = 0; y < N; ++y)

{

for (x = 0; x < N; ++x)

{

if (data[x][y] == 0)//如果行元素是零则跳过该元素

{

continue;

}

//判断是否可加,能加则加

for (idx = x + 1; idx <= N - 1; ++idx)

{

if (data[idx][y] == 0)

{

continue;

}

else if (data[idx][y] != data[x][y])

{

break;

}

else

{

data[x][y] += data[idx][y];//能加则加

data[idx][y] = 0;//加完之后将其置0以便于不影响下次判断,置0后下次就不会判断改位了

isChange = 1;

break;

}

}

}

}

在这一部分中,我们先固定列不变,对行进行累加。比如说y=0的时候相当于固定累加列为第一列,这个时候再固定行通过行遍历来进行累加。如果行元素是零则跳过该元素,如果行元素非零则需要判断是否可加。比如说第一个行元素非零(x=0),判断是否可加的时候需要从该元素的下一个元素(x=1)开始判断,如果下面的元素为零则跳过,继续往下判断,直到遇到非零元素或者超出范围停止。如果超出范围则说明下面的元素都是零,不可加。如果遇到非零元素,判断和原来的元素(x=0)是否相等,如果不相等表示不可加,如果相等(比如说x=2的位置),则将(x=0)的位置赋值为累加和,另一个位置(x=2)赋值为零,以便于不影响下次判断,置0后下次就不会判断改位了。将(x=1)这个位置累加完之后,将(x=1, x=2, x=3 … x=N-2)的位置用上面的方法依次累加。

在第一列累加完成后,将(y=1, y=2, y=3… y=N-2)依次用上面的方法进行累加。

累加示意图:

6224077050eb5758a24b12e509e89ab6.png

图中(1)是上移之前的数据,在y=0时(固定第一列,对第一列进行累加),通过对x=0遍历累加得到(2),对x=1和x=2遍历累加得到(3)和(4)。在y=1时(固定第二列,对第二列进行累加),通过对x=0遍历累加得到(5),通过对x=1和x=2遍历累加得到(6)和(7)。同样的方法,经过多次迭代累加得到(8)。

(2)累加后移动

//累加后移动

for (y = 0; y < N; ++y)

{

for (x = 1; x < N; ++x)

{

if (data[x][y] == 0)

{

continue;

}

idx = x - 1;//从x-1位置开始查

while (data[idx][y] == 0 && idx >= 0)//一直到非零或者超出范围结束

{

--idx;

}

if (data[idx + 1][y] == 0)

{

data[idx + 1][y] = data[x][y];

data[x][y] = 0;

isChange = 1;

}

}

}

这部分和上以部分相似。先固定列,对行进行遍历。从第二行(x=1)开始,如果数据为零则跳过(不需要移动),如果不为零则往上找,一直到找到非零数据或者超出范围为止(程序中用idx来记录停止的位置)。最后对停止位置的下一个位置(idx+1)判断,如果非零则不需要移动,如果为零则将(idx+1)位置赋值为打算移动的位置的值,同时将打算移动的位置赋零。

移动示意图:

46af2b092322b6a02a910ab4abd3fd60.png

(3)成功移动之后需要随机产生一个数

//成功移动之后需要随机产生一个数

if(isChange == 1)

{

getRand(data);

}

在第一步和第二步中,我们用isChange变量来标记是否移动了。当玩家的操作使得累加成功执行或移动成功执行时,就说明操作使得移动成功了,这时isChange=1,程序要随机产生一个数。否则代表没有移动,isChange=0,程序不会随机产生一个数。

对代码的一些优化

如下面代码所示,在getInput()函数中,一开始采用的是ch = getchar()来获取玩家输入的字符,这样做首先是用户体验不太好,每次都需要用户输入回车才会做出反应;除此之外,当玩家赢了或者输了的时候输入“q”进行退出的时候,还需要考虑回车被当做玩家输入的字符被读取的情况(需要在showGame()函数中添加fflush(stdio)来将缓冲区清空一下)。

/*

* 函数名:getInput

* 功能:获取用户的输入

* 参数:无

* 返回值:返回用户输入的按键

*/

int getInput(void)

{

char ch;

int key;

ch = getchar();

switch (ch)

{

case 'u': key = UP; break;

case 'd': key = DOWN; break;

case 'l': key = LEFT; break;

case 'r': key = RIGHT; break;

case 'q': key = EXIT; break;

default: key = OTHER; break;

}

return key;

}

后来我做了如下改进,将ch = getchar()改为了ch = _getch(),就这样的一个简单改进,玩家就可以不用考虑输入回车的问题,同时回车被误读入的问题也解决了。

在windows下的编译器,如果支持头文件conio.h,那么就可以使用_getch()函数,来实现不按回车就输入的效果。

源代码链接:https://github.com/xiao-hao-hao/2048

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值