经过这几天的学习,有所感悟,简单的写一个小游戏来检验成果
在写三子棋这个简单的小游戏之前首先还得提一嘴数组,这也是即函数之后所学,数组这部分比较简单,但却很实用。
首先简单的介绍一下数组,数组跟高中所学的函数集合比较类似,他也是代表着相同元素的集合,但就是不同的元素表达的方式可能不太一样,大体的定义形式为:数组元素类型+数组命[元素个数]={元素内容}或者“字符串”例如定义整型数组:int arr[5]={1,2,3,4,5};或者:char arr[5]=“abcde”;元素个数可以为空,如果为空就相当于元素内容为多少,元素个数就是多少。当然这块也有一个隐藏的事情就是数组长度,并不是元素个数是多少,数组长度就是多少。因为每个数组的末尾都会隐藏一个“\0”,这个字符是一直都有的,意义在于停止,就好比你输入一组数组,计算机怎么能知道你是输入完了还是没输入完呢?前面定义的元素个数并不能说明,因为你完全可以少些,多些,但是你定义了元素个数就相当于跟计算机说,我在我定义的元素个数的位置加了“\0”,所以计算机只有读到”\0”时候就知道,“啊,到位置了,可以停了” 这样数组才算打印出你想要的个数:例如int arr[5]={1,2,3,4,5},计算机就会读到“1,2,3,4,5”,如果int arr[5]={1,2,3,4,5,6};计算机也会读到“1,2,3,4,5”,如果int arr[5]={1,2,3};那么计算机就会自动在空的位置填0,变成“1,2,3,0,0”;再说一个重中之重那就是,数组名既可以表示数组的地址,也可以表示数组首元素的地址,两个在数值上是一样的,但是含义不一样。说完这部分,大体提一下数组的分类,其实可以说是一维数组或者多维数组,顾名思义,定义一个[]的就是一维数组,定义两个[]的就是二维数组,依此类推,简单记一下小笔记二维数组部分例如int arr[行][列]={};行代表这有几行,列代表着每行有几个元素,这里行可以省略但是列不能。列要省略了就不能确定一行有多少个元素,也不能确定数组有多少行。
数组还有一点就是,他起始的位置的数组下标为0,从0开始,为什么从0开始,有兴趣者可以去搜一下,我在这里就不详细介绍了。
OK,接下来开始本次的重点了,三子棋的实现。
根据以往学到的内容,三子棋的实现我也分为三个部分,主函数,函数定义,函数实现。接下来请君看详解代码:
开始简单写一个游戏,说起游戏最开始能引入眼帘的就是菜单,这里将引用一个名为菜单的库函数menu很好使:
void menu()
{
printf("*******************\n");
printf("***1.开始游戏******\n");
printf("***0.退出游戏******\n");
printf("*******************\n");
}
做到最基本的选择功能,这样到时候一接收就好了,当然都这种选择了,肯定会用到switch语句了,这里很基本我也就不一一细说了之间看代码:
int main()
{
int a = 0;
menu();
printf("请选择: \n");
scanf_s("%d" ,& a);
switch (a)
{
case 1:
game();
break;
case 0:
printf("游戏结束");
break;
default:
printf("输入有误,请重新输入");
break;
}
return 0;
}
那么既然是游戏,我们就不能让他就玩一次,这样也不过隐,总不能想玩第二次的时候就得重新运行,很麻烦所以我们需要在外面套上一个循环,那么循环条件是什么呢?总不能我们人为规定你就能玩三次,玩四次这样,所以for循环肯定是不行了,那么就是while,do while循环了,根据所写的条件,我们这里选择了do while循环(如果有不懂,请看前面循环章节),那么完整的主函数代码就基本上出来了:
int main()
{
int a = 0;
do
{
menu();
printf("请选择: \n");
scanf_s("%d" ,& a);
switch (a)
{
case 1:
game();
break;
case 0:
printf("游戏结束");
break;
default:
printf("输入有误,请重新输入");
break;
}
} while (a);
return 0;
}
菜单环节完事,那么就开始我们今天的重头戏,游戏函数的编写,三子棋顾名思义就是一种下棋的游戏,下棋游戏的基本条件就是咱们得有一个棋盘,所以下面来写一写棋盘的代码:首先还是在主函数页面里写一下游戏的大框:
void game()
{
char ret = 0;
char board[Hang][Lie] = {0};
InitBoard(board, Hang, Lie);//初始化棋盘
DisplayBoard(board, Hang, Lie);//打印棋盘
}
这里我就为了让小白能读懂,我之间有拼音代替了(当然也是为了让自己一下就能知道是啥,毕竟英语一般),ok现在就开始写关于棋盘的函数了,当然嗷,写这些函数之前一定要在头文件声明一下例如这样,往后的头文件声明我就不依次展示了,最后在总结,当然,我在头文件里先定义了行和列的全局变量,因为毕竟是棋盘吗,也为了以后方便好改,所以没有定义具体数字,好了看代码:
#define Hang 3
#define Lie 3
#include<stdio.h>
void InitBoard(char board[Hang][Lie], int hang, int lie);
void DisplayBoard(char board[Hang][Lie], int hang, int lie);
接下来开始咱们的核心代码,三子棋棋盘的代码书写:定义棋盘和打印棋盘,其实这两部分的核心就是嵌套循环,嵌套for循环,跟之前写的乘法口诀表一样,三子棋嘛,肯定是3*3的方块,根据上述的二维数组,我们也轻松的就能知道那个是行,那个是列,一循环就出来了
void InitBoard(char board[Hang][Lie], int hang, int lie)
{
int i = 0;
int j = 0;
for (i = 0; i < hang; i++)
{
for (j = 0; j < lie; j++)
{
board[i][j] = ' ';
}
}
}
当然我肯定不能直接这样说,根据代码理解一下,我就不画图了,for循环是一层一层进行的,外面的第一层for循环会令i为0,然后带入第二层循环,这是i的值就固定住了,就开始j的不断变化,就会形成board[i][],也是在第i行j列中的列不断变化,直到把i行填满,依次类推,我们简单的棋盘就建好了。
那接下来就开始打印棋盘了,打印棋盘的原理跟这个相同也是两层循环如下:
void DisplayBoard(char board[Hang][Lie], int hang, int lie)
{
int i = 0;
for (i = 0; i < hang; i++)
{
int j = 0;
for (j = 0; j < lie; j++) //打印一行数据
{
printf(" %c ", board[i][j]);
}
printf("\n");
}
}
但是当你这样打出来你就会发现,一片空白,毕竟我们建的就是一片空白的初始数据,所以我们在打印的时候就要稍加润色一下,让他有分割线的出现,如图
根据图片我们就不难分析出来“|”这个标识肯定是从列循环中出来的,先就看一行,如果是打印出来那么这个顺序就是空白,竖线,空白,竖线,空白,空白就是我们的初始化数据,循环了三次,竖线在空白中间,可以理解为循环两次,但是在运行的时候,我们不用去在嵌套一个循环,可以把其看成一个整体,只不过在循环最后一次的时候,就不执行了就可以,所以根据前后顺序我们就能写出如下代码:
for (j = 0; j < lie; j++) //打印一行数据
{
printf(" %c ", board[i][j]);
if (j < lie - 1)
printf("|");
}
打完竖线就开始打印横线了,为了让其不是一条直线有距离美,我们也需要用到列,让他变成一个独立小空间,代码如下:
if (i < hang - 1) //打印分割行
{
for (j = 0; j < lie; j++)
{
printf("---");
}
printf("\n");
}
所以这个整体代码如下:
void DisplayBoard(char board[Hang][Lie], int hang, int lie)
{
int i = 0;
for (i = 0; i < hang; i++)
{
int j = 0;
for (j = 0; j < lie; j++)
{
printf(" %c ", board[i][j]);
if (j < lie - 1)
printf("|");
}
printf("\n");
if (i < hang - 1) //打印分割行
{
for (j = 0; j < lie; j++)
{
printf("---");
}
printf("\n");
}
}
}
这样,我们的棋盘初始化和打印棋盘就完事了,接下来就是行走规则了,我们这个肯定不能用鼠标点击控制走路,所以我们就接近键盘,用数组控制走路,毕竟我们定义的就是数组:当然我们也需要给玩家走的坐标和电脑走的坐标区分出来,我们暂定玩家走的标记为“*”,电脑走的为“#”,当然为了更加大众化,我们需要稍微手动改一下数组的下标,因为并不是所有人都知道数组是从0开始的,所以接下来就很简单,当然,为了能一直走下去,外面得套一个循环,代码如下:
void PlayerMove(char board[Hang][Lie], int hang, int lie)
{
int x = 0;
int y = 0;
printf("玩家走: \n");
while (1)
{
printf("请输入坐标:");
scanf_s("%d%d", &x, &y);
if (x >= 1 && x <= hang && y >= 1 && y <= lie)
{
if (board[x - 1][y - 1] = ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("该坐标被占用");
}
}
else
{
printf("该坐标非法,请重新输入\n");
}
}
}
到这我们就完成了人操作的步骤,接下来就开始进行电脑的操作步骤了,解决电脑的操作步骤的最大问题就是,该如何让电脑自己行走,也就是相当于如何让电脑产生相应的随机数,这里我们就不得不介绍两个库函数了,rand和srand,首先我们来介绍一下rand,rand是C语言提供的能生成随机函数的一个函数,但是这个函数有一个弊端,那就是起点一样生成的随机函数也是一样的,就好比你第一次运行他随机出个3,第二次他还是会随机生成3,所以我们就需要改变他的起点,这样就出现了我们的srand函数,srand原型:void srand(unsigned int seed),其中srand设置产生一系列伪随机数发生器的起始点,要想把发生器重新初始化,可用1作seed值。任何其它的值都把发生器匿成一个随机的起始点。通俗来说他就是rand函数的随机起点的一个框架,seed就是那个随意改动的随机值,改动seed值,我们的随机函数rand就会产生一个新的随机函数,但是毕竟是电脑,我们不可能让他运行一次我们就给他输入一次,这样也不自动化了,那什么是不断在变化的呢?答案就是时间,时间是一直不断的流失,我们不可能暂停时间,所以这里我们也就引用了time函数做完seed的值,time函数会不断获取电脑的提供的时间,达到无时无刻都在变化,从而引起rand的随机起点再不断变化,从而引起rand 的不断变化所以接下来代码如下:
void ComputerMove(char board[Hang][Lie], int hang, int lie)
{
int x = 0;
int y = 0;
printf("电脑走:\n");
while (1)
{
x = rand() % hang;
y = rand() % lie;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
在主函数里面插入:
srand((unsigned int)time(NULL));
当然运用这些函数的时候一定要引对应的头文件,rand和srand的头文件为#include<stdlib.h>,time的头文件为#include<time.h>,这些函数的运用和头文件的查找请详细看我第一章推荐的网站,都有结果能搜查到。
解决完棋手的操作,那么我们就得知道规则是啥了,得知道我们该如何赢,所以接下来就是书写这些规则,横三,竖三,斜三算赢,棋盘满了为平局:
int IsFull(char board[Hang][Lie], int hang, int lie)
{
int i = 0;
int j = 0;
for (i = 0; i < hang; i++)
{
for (j = 0; j < lie; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
char IsWin(char board[Hang][Lie], int hang, int lie) //行三列
{
int i = 0;
for (i = 0; i < hang; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
{
return board[i][1];
}
}
for (i = 0; i < lie; i++) // 竖三列
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
{
return board[1][i];
}
// 对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
{
return board[1][1];
}
//判断是否平局
if (1 == IsFull(board, hang, lie))
{
return 'Q'; //平局
}
return 'C'; // 继续
}
}
在这里,横三,竖三,斜三满足条件的,我们传回他们其中任意一个地方的符号,为什么这样呢,因为我们现在都是再写函数的功能实现,最后的决定权还都是在主函数里面,我们剩下的就需要在主函数里面相应的润色一下,就能完成最终代码:
void game()
{
char ret = 0;
char board[Hang][Lie] = {0};
InitBoard(board, Hang, Lie);//初始化棋盘
DisplayBoard(board, Hang, Lie);//打印棋盘
while (1)
{
PlayerMove(board, Hang, Lie); //玩家
DisplayBoard(board, Hang, Lie);
ret = IsWin(board, Hang, Lie); //判断输赢
if (ret != 'C')
{
break;
}
ComputerMove(board, Hang, Lie);//电脑
DisplayBoard(board, Hang, Lie);
ret = IsWin(board, Hang, Lie); //判断输赢
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
printf("玩家赢\n");
}
else if (ret == '#')
{
printf("电脑赢\n");
}
else
printf("平局\n");
}
这些就是我们所写的全部代码了,当然做的很简单,电脑走的也十分的水,但最起码可以自娱自乐一下,这些都是前几章收获所得,也是所学的结果的一种体现,写了这篇文章也算是勉励自己吧,各位大佬有能优化的可以尽情评论,感谢你的阅读,也感谢自己写的这些。
附带函数声明头文件:
#define Hang 3
#define Lie 3
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//声明
void InitBoard(char board[Hang][Lie], int hang, int lie);
void DisplayBoard(char board[Hang][Lie], int hang, int lie);
void PlayerMove(char board[Hang][Lie], int hang, int lie);
void ComputerMove(char board[Hang][Lie], int hang, int lie);
char IsWin(char board[Hang][Lie], int hang, int lie);
int IsFull(char board[Hang][Lie], int hang, int lie);