《C语言程序设计基础》学习完毕,完成第一个项目:2048小游戏。
//思考:编程时,要先想整个框架还是先想每个函数的算法。
//思考:每一个函数要不要传参数(即数组)
//思考:每一个函数之间的联系是什么,谁调用谁…这里很容易搞得头晕,越想越觉得复杂,便不想完成项目了。
将游戏分为几个步骤,拆分来完成每一个函数。
一、定义一个4*4的二维数组arr[4][4],初始化为0;
二、打印界面函数:Show_game();将数组打印出来;
三、随机函数:Add_rand():在数组中随机选出一个元素赋值,并且赋值要随机2或者4。所以就要定义i,j两个数组下表变量,将随机值%4,(求4的余数)就会得到0、1、2、3,将随机值赋值给i、j。还需要随机的2和4,可以在定义一个随机数m,m%3,得到0、1、2,在m大于1的情况下将m赋值给数组元素arr[i][j]。
这里的arr[i][j] = (m>1) ?2:4;这句代码,一句解决,厉害厉害是我最开始没有想到的。都是c语言中最简单的语句,但自己在实际编程中用得少,下意识的只会用if语句去判断,导致代码可读性差。
srand((unsigned) time (NULL));//随机种子。Srand需要用到头文<stdlib.h>,
这句用到time()函数,所以需要用到头文件<time.h>
关于srand()函数:
3.1设置随机的起点
3.2Void srand(unsigned int seed);
3.3参数seed,随机生成的种子。
3.4用法:srand和rand()配合使用产生伪随机数序列。rand函数在产生随机数前,需要系统提供的生成伪随机数序列的种子,rand根据这个种子的值产生一系列随机数。如果系统提供的种子没有变化,每次调用rand函数生成的伪随机数序列都是一样的。srand(unsigned seed)通过参数seed改变系统提供的种子值,从而可以使得每次调用rand函数生成的伪随机数序列不同,从而实现真正意义上的“随机”。通常可以利用系统时间来改变系统的种子值,即srand(time(NULL)),可以为rand函数提供不同的种子值,进而产生不同的随机数序列
四、游戏初始化函数InitGame():游戏开始第一个随机值要定为2
五、*核心算法*上下左右移动函数:Move_****()
我们这里分析左移(其他移动方法几乎相同)
┏━┳━┳━┳━┓i
┣━╋━╋━╋━┫i
┣━╋━╋━╋━┫i
┣━╋━╋━╋━┫i
┗━┻━┻━┻━┛
K J J J
左移的结果:是将每行的元素移动到最左边。
将k列设置为当前列,遍历数组时:i++;j++
找到arr[i][j] == 0时,不管
找到arr[i][j]不为0时,分三个情况移动:
- arr[i][k] == arr[i][j]时
- arr[i][k] == 0时
- arr[i][k] != 0 && arr[i][k] != arr[i][j] 又分两种情况
a.j、k挨着
b.j、k不挨着
右移同理,将k列的当前放在最右边:
┏━┳━┳━┳━┓i
┣━╋━╋━╋━┫i
┣━╋━╋━╋━┫i
┣━╋━╋━╋━┫i
┗━┻━┻━┻━┛
J J J K
上移动:
┏━┳━┳━┳━┓k
┣━╋━╋━╋━┫j
┣━╋━╋━╋━┫j
┣━╋━╋━╋━┫j
┗━┻━┻━┻━┛
i i i i
向下移动:
┏━┳━┳━┳━┓j
┣━╋━╋━╋━┫j
┣━╋━╋━╋━┫j
┣━╋━╋━╋━┫k
┗━┻━┻━┻━┛
i i i i
每一种情况都相似,所以函数只是在遍历是将条件略微根据k、j、i修改。
六、判断游戏是否结束函数:GameOver(). 游戏结束有两个条件:1.没有一个元素为0 2.没有相邻的元素相等。
条件一:定义一个变量n,初始化为0;写了一个bool函数,遍历每一个元素,如果数组中有一个0元素,n++;判断n的值,若n == 0,返回1,说明条件该成立。(这个写法我一开始没有想到用一个计数器n)。
条件二:同样的方法,定义变量n,判断数组相邻位置是否一样n++。若n == 0则没有相邻位置元素一样,返回1。
最后GameOver函数中判断条件一和二,一&&二,则返回1,表示游戏结束。
(这里我写复杂了,用一个bool函数就可以实现,只需要遍历一次,找出n和m的值,if(n==0 &&m==0),则返回true,表示游戏结束)
七、主函数中,接受键盘字符getchar,用到switch语句写接收到的case
八、用到清屏系统命令system(“cls”),需要加头文件<windows.h>。
九、getchar()和getch()是有区别的。它们都是输入单个字符用的。getch()用户不需要按回车,是读取按键值用的,并且需要头文件conio.h。按键输入时,屏幕没有反应。getchar()需要回车,是在输入设备中得到数据用的。按键输入时,屏幕显示输入的字符.
十、上下左右箭头:72 80 75 77.就是数字,不是字符,写成【case ‘75’: 】就错了。这也导致我开始从键盘怎么也读不上箭头符号。
以下源代码:
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<windows.h>
#include<conio.h>
int arr[4][4] = {0};//游戏抽象为二维数组
int if_add;//是否生成随机数 1表示生成 0不生成
void ShowArr()//和下例的函数功能一样,但是下面例子加了边框,游戏观赏性高
{
for(int i = 0;i < 4;i++)
{
for(int j = 0;j < 4;j++)
{
printf("%5d",arr[i][j]);
}
printf("\n\n");
}
}
void ShowGame()//游戏界面(参考网上,加了边框,真香)
{
printf("\n\n *****2048*****\n");
printf("┏━━━┳━━━┳━━━┳━━━┓\n");
for(int i = 0;i<4;i++)
{
printf("┃");
for(int j = 0;j < 4;j++)
{
//二维数组元素为0
if(arr[i][j] == 0)
{
printf(" ┃");//4个空格
}
else
{
printf("%3d┃",arr[i][j]);
}
}
if(i<3)
{
printf("\n┣━━━╋━━━╋━━━╋━━━┫\n");
}
else
{
printf("\n┗━━━┻━━━┻━━━┻━━━┛\n");
printf("****WELCOME****\n\n\n");
}
}
}
void Add_rand()//在随机数组中放入一个随机的2或者4,2的概率要大。
{
srand((unsigned) time (NULL));//随机种子
int i;
int j;
int m = rand() % 3;//m可能的结果为0、1、2。
while(1)
{
i = rand() % 4;
j = rand() % 4;
if(arr[i][j] == 0)
{
arr[i][j] = (m>1) ? 4 :2;//m大于1为4,否则为2。这样2的概率为33.3%,4的概率为66.7%
break;//生成一个数,则退出
}
else
{
continue;//否则执行下一次循环,重新生成随机值。
}
}
}
void InitGame()//初始化:游戏开始有两个数字
{
if_add = 1;//需要生成一个随机数
int i = rand()%4;
int j = rand()%4;
arr[i][j] = 2;//第一个数字为2
}
void Move_left()//左移动
{
for(int i = 0;i < 4;i++)
{
for(int j = 1,k = 0;j < 4;j++)
{
//先找到k项后面不为0的项
if(arr[i][j] > 0)//分3个情况
{
//1.k项 == j项
if(arr[i][k] == arr[i][j])
{
arr[i][k++] *= 2;//相当于arr[i][k++] <<= 1这样效率更高
arr[i][j] = 0;
if_add = 1;
}
//2.k项 == 0
else if(arr[i][k] == 0)
{
arr[i][k] = arr[i][j];
arr[i][j] = 0;
if_add = 1;
}
//3.k项 != 0;j项 != k项
else//分2种情况
{
arr[i][++k] = arr[i][j];
if(j != k)
{
arr[i][j] = 0;
if_add = 1;
}
}
}
}
}
}
void Move_right()//右移
{
for(int i = 0;i < 4;i++)
{
for(int j = 2,k = 3;j >= 0;j--)
{
//先找到k项前面不为0的项
if(arr[i][j] > 0)//分3个情况
{
//1.k项 == j项
if(arr[i][k] == arr[i][j])
{
arr[i][k--] *= 2;//相当于arr[i][k--] <<= 1这样效率更高
arr[i][j] = 0;
if_add = 1;
}
//2.k项 == 0
else if(arr[i][k] == 0)
{
arr[i][k] = arr[i][j];
arr[i][j] = 0;
if_add = 1;
}
//3.k项 != 0;j项 != k项
else//分2种情况
{
arr[i][--k] = arr[i][j];
if(j != k)
{
arr[i][j] = 0;
if_add = 1;
}
}
}
}
}
}
void Move_up()//上移
{
for(int i = 0;i < 4;i++)//列
{
for(int j = 1,k = 0;j < 4;j++)//行。
{
//先找到k项后面不为0的项
if(arr[j][i] > 0)//分3个情况
{
//1.k项 == j项
if(arr[k][i] == arr[j][i])
{
arr[k++][i] *= 2;
arr[j][i] = 0;
if_add= 1;
}
//2.k项 == 0
else if(arr[k][i] == 0)
{
arr[k][i] = arr[j][i];
arr[j][i] = 0;
if_add= 1;
}
//3.k项 != 0;j项 != k项
else//分2种情况
{
arr[++k][i] = arr[j][i];
if(j != k)
{
arr[j][i] = 0;
if_add = 1;
}
}
}
}
}
}
void Move_down()//下移
{
for(int i = 0;i < 4;i++)//列
{
for(int j = 2,k = 3;j >= 0;j--)//行
{
//先找到k项后面不为0的项
if(arr[j][i] > 0)//分3个情况
{
//1.k项 == j项
if(arr[k][i] == arr[j][i])
{
arr[k--][i] *= 2;
arr[j][i] = 0;
if_add= 1;
}
//2.k项 == 0
else if(arr[k][i] == 0)
{
arr[k][i] = arr[j][i];
arr[j][i] = 0;
if_add= 1;
}
//3.k项 != 0;j项 != k项
else//分2种情况
{
arr[--k][i] = arr[j][i];
if(j != k)
{
arr[j][i] = 0;
if_add = 1;
}
}
}
}
}
}
bool TiaoJian_1()//游戏结束条件1:没有格子为0,游戏结束,return 1
{
int n = 0;
for(int i = 0;i < 4;i++)
{
for(int j = 0;j < 4;j++)
{
if(arr[i][j] == 0)
{
n++;//有格子为零,游戏继续
}
}
}
if( n == 0)//游戏结束
{
return true;
}
else//游戏继续
{
return false;
}
}
bool TiaoJian_2()//2.相邻(上下,左右)的值不相等,则游戏结束,return 1
{
int n = 0;
for(int i = 0;i < 4;i++)
{
for(int j = 0;j+1 < 4;j++)
{
if((arr[i][j] == arr[i][j+1]) || (arr[j][i] == arr[j+1][i]))
{
n++;
}
}
}
if(n == 0)//表示没有相等,结束
{
return 1;
}
else
{
return 0;
}
}
bool GameOver()//判断游戏:true表示结束。
{
if(TiaoJian_1() && TiaoJian_2())
{
return true;
}
else
{
return false;
}
}
int main()
{
InitGame();
Add_rand();
ShowGame();
ShowArr();
while(1)
{
int ch;
if_add= 0;
switch(ch = getch())//接收字符函数
{
case 'a':
case 'A':
case '4':
case 75:
Move_left();
break;
case 'd':
case'D':
case '6':
case 77:
Move_right();
break;
case'w':
case 'W':
case '8':
case 72:
Move_up();
break;
case 's':
case 'S':
case'2':
case 80:
Move_down();
break;
default:
break;
}
if(if_add == 1)
{
Add_rand();
system("cls");//清屏系统命令
ShowGame();
//ShowArr();
}
if(GameOver())
{
printf(" ***游戏结束***\n点击回车键退出程序");
getchar();
break;
}
}
}