哈喽亲爱的小伙伴们大家好,真的是好久不见。前一阵子过完年之后进行了漫长的“请年茬”,现在刚刚开学,从今天开始更新,基本上不出意外还是两天一篇左右。上一期给小伙伴们讲解了数组,这期来给大家简单介绍一个小程序应用,那就是三子棋。
五子棋相信大家都玩过,其实远离基本上是完全相同的,就是改一下胜利条件的代码,三子棋就是9宫格嘛,三子连线即为胜利,现在我们就来简单的写一下这个代码。
首先呢,我们肯定是先写出main函数,然后写出程序大框架。例如下代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//菜单函数,在屏幕上打印菜单
void menu()
{
printf("************* 三子棋游戏 ************\n");
printf("************0.play 1.exit************\n");
}
//测试函数,整个程序流程
void test()
{
int a;
scanf("%d", &a);
switch (a)
{
case 0:
{
printf("三子棋游戏\n");
break;
}
case 1:
{
printf("退出游戏!\n");
break;
}
default:
{
printf("请输入正确指令!\n");
}
}
}
//主函数,程序入口
int main()
{
menu();
test();
return 0;
}
运行截图:
这样我们程序大框就写好了,再简单加以修饰。那么首先当我们玩完一局游戏时,应该再一次弹出菜单,而不是直接退出游戏,相信大家很容易想到,应该用循环来解决。事实上什么循环都可以实现我们想要的结果,我们应该根据情况选择。对于这个小游戏来说,我们显然希望上来直接运行游戏,然后玩完一把在进入循环,显然用我们的do while循环比较合适。由于while判断的是括号为真进入循环,那么我们就可以巧妙利用一下,直接用菜单时我们选择的结果作为条件,把退出游戏选项改为0,这样只要我们输入的不是0(即我们不想退出游戏),那么while自然判定为继续循环,完美的符合了我们的需求,如下代码:
void test()
{
int a;
do
{
menu();
scanf("%d", &a);
switch (a)
{
case 0:
{
printf("退出游戏!\n");
break;
}
case 1:
{
printf("三子棋游戏\n");
break;
}
default:
{
printf("请输入正确指令!\n");
}
}
} while (a);
}
运行结果:
现在我们大框就写好了,该我们的重头戏游戏函数了。显然当我们玩这个游戏的时候,我们选择1不是为了看屏幕打印出一句三子棋游戏,而应该是真正的游戏,现在我们开始写一下游戏的代码。
现在我们开始思考,当我们选择了1,我们希望看到的时一个棋盘,所以我们应该在game()函数中先写一个打印棋盘函数,例如下代码:
void game()
{
Dispiayboard(borad, ROW, COL);
}
打印函数包括三个参数:棋盘(数组),行,列
然而事实上,我们想写出打印函数,必须得有棋盘给它打印啊,也就是打印函数传参得是实参啊,所以其实我们game函数最开始不是打印棋盘,而是创建棋盘并初始化棋盘。只不过想让玩家看见的是打印棋盘,这就是我们所说的用户视角和程序员视角。所以game函数开头应该是这样的:
#define ROW 3
#define COL 3
void game()
{
//创建棋盘
char borad[ROW][COL] = {0};
//初始化棋盘
Initializeboard(borad, ROW, COL);
//打印棋盘
Dispiayboard(borad, ROW, COL);
}
这里行列我们采用#define来定义,这是因为在这个程序中行列我们要多次使用行列值,如果我们后期想要修改,一个一个改显然是不现实的,采用#define来进行全局定义,利于后期修改维护,比如把这个代码升级为五子棋。
下面我们先来写初始化棋盘函数,其实就是简单的把数组的值初始化为空格,这样在我们没有下棋的时候,打印出来就是“空位”一样。
具体实现使用双层for循环,例如下代码:
void Initializeboard(char borad[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
borad[i][j] = ' ';
}
}
}
然后我们来实现具体的函数。由于我们在vs中写代码,是不能进行贴图的,那么我们就要先办法利用符号来使输出结果尽可能像一个棋盘。所以当我们利用符号和printf函数来写打印棋盘的函数,依旧是双层for循环:
void Dispiayboard(char borad[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", borad[i][j]);
if (j != col-1)
{
printf("|");
}
}
printf("\n");
if (i != col-1)
{
for (int n = 0; n < col; n++)
{
printf("---");
if (n != col-1)
{
printf("|");
}
}
printf("\n");
}
}
}
运行截图:
具体代码就不做详细讲解了,其实就是为了让打印出的结果看起来更像棋盘,我们把#define的定义改成5,结果如图,说明我们写的代码是利于后期维护的,说这里的原因是更好帮助大家理解为什么用#define定义行列,在写代码时行列相关数据都用含row,col的表达式来写,利于代码后期的更新处理。
好了,写到这里,我们就可以简单的打印出棋盘,接下来就是下棋过程的写作。增加game函数内容:
void game()
{
//创建棋盘
char borad[ROW][COL] = {0};
//初始化棋盘
Initializeboard(borad, ROW, COL);
//打印棋盘
Dispiayboard(borad, ROW, COL);
while (1)
{
//玩家下棋,根据返回值制造结果
int ret1 = Playermove(borad, ROW, COL);
//返回值为0说明胜利
if (ret1 == 0)
{
//打印棋盘
Dispiayboard(borad, ROW, COL);
printf("\n");
Win();
printf("\n\n\n");
break;
}
//返回值为2表示平局
else if (ret1 == 2)
{
//打印棋盘
Dispiayboard(borad, ROW, COL);
printf("\n");
Draw();
printf("\n\n\n");
break;
}
//电脑下棋,根据返回值制造结果
int ret2 = Computermove(borad, ROW, COL);
//打印结果
Dispiayboard(borad, ROW, COL);
if (ret2 == 0)
{
printf("\n");
Defeat();
printf("\n\n\n");
break;
}
else if (ret2 == 2)
{
printf("\n");
Draw();
printf("\n\n\n");
break;
}
}
}
分别实现:
//玩家下棋
int Playermove(char borad[ROW][COL], int row, int col)
{
int x, y, z;
//利用do while循环和状态值z实现坐标不合法时重新输入
do
{
z = 1;
printf("请输入要下的坐标:\n");
scanf("%d%d", &x, &y);
//验证当前是否被占用或者超出范围
if (borad[x-1][y-1] == ' ')
{
//坐标可用改变状态值z以实现跳出循环
z = 0;
}
else
{
printf("坐标不合法(被占用或者不在范围),请重新输入!\n");
}
} while (z);
//将玩家下棋位置标记为“*”
borad[x-1][y-1] = '*';
//返回输赢结果
return Iswin(borad, '*', x-1, y-1);
}
//电脑下棋
int ret2 = Computermove(borad, ROW, COL);
//打印结果
Dispiayboard(borad, ROW, COL);
if (ret2 == 0)
{
printf("\n");
Defeat();
printf("\n\n\n");
break;
}
else if (ret2 == 2)
{
printf("\n");
Draw();
printf("\n\n\n");
break;
}
相对而言,胜利条件的判定比较难写,考察一个人的逻辑思维,代码中我写了详细的注释,判断胜利代码如下:
//判断输赢,根据返回值,0为胜利(玩家代码调用玩家胜利,电脑代码调用电脑的胜利,此函数只用于判定是否n字连珠,后面在game函数打印输赢),2为平局,1继续游戏
int Iswin(char borad[ROW][COL], char type, int x, int y)
{
//横向判定“---”
int temp1 = 1;
temp1 += Cleft(borad, type, x, y);
temp1 += Cright(borad, type, x, y);
//竖向判定“|”
int temp2 = 1;
temp2 += Cover(borad, type, x, y);
temp2 += Cdown(borad, type, x, y);
//左斜判定“/”
int temp3 = 1;
temp3 += Cldial(borad, type, x, y);
temp3 += Cldiar(borad, type, x, y);
//右斜判定“\”
int temp4 = 1;
temp4 += Crdial(borad, type, x, y);
temp4 += Crdiar(borad, type, x, y);
if ((temp1==NUM) || (temp2==NUM) || (temp3==NUM) || (temp4==NUM))
{
return 0;
}
else
{
//判断平局函数
return Isfull(borad, ROW, COL);
}
}
其实三子棋的判断条件相当好写,完全可以直接用if写,虽然情况比较多,但是易于理解,这里为了程序的后期更改方便(比如改为5字棋),用了递归查同累计的方法判定输赢,例如下代码:
//下面用了八个递归查同,即向左递归查与此坐标“符号”相同的位置的个数
//向左查同
int Cleft(char borad[ROW][COL], char type, int x, int y)
{
int count = 0;
if((y-1 >= 0) && (borad[x][y-1] == type))
{
count ++;
count += Cleft(borad, type, x, y-1);
}
return count;
}
//向右查同
int Cright(char borad[ROW][COL], char type, int x, int y)
{
int count = 0;
if((y+1 < COL) && (borad[x][y+1] == type))
{
count ++;
count += Cright(borad, type, x, y+1);
}
return count;
}
//向上查同
int Cover(char borad[ROW][COL], char type, int x, int y)
{
int count = 0;
if((x-1 >= 0) && (borad[x-1][y] == type))
{
count ++;
count += Cover(borad, type, x-1, y);
}
return count;
}
//向下查同
int Cdown(char borad[ROW][COL], char type, int x, int y)
{
int count = 0;
if((x+1 < ROW) && (borad[x+1][y] == type))
{
count ++;
count += Cdown(borad, type, x+1, y);
}
return count;
}
//向左斜左查同
int Cldial(char borad[ROW][COL], char type, int x, int y)
{
int count = 0;
if((x+1 < ROW) && (y-1 >= 0) && (borad[x+1][y-1] == type))
{
count ++;
count += Cldial(borad, type, x+1, y-1);
}
return count;
}
//向左斜右查同
int Cldiar(char borad[ROW][COL], char type, int x, int y)
{
int count = 0;
if((x-1 >= 0) && (y+1 < COL) && (borad[x-1][y+1] == type))
{
count ++;
count += Cldiar(borad, type, x-1, y+1);
}
return count;
}
//向右斜左查同
int Crdial(char borad[ROW][COL], char type, int x, int y)
{
int count = 0;
if((x-1 >= 0) && (y-1 >= 0) && (borad[x-1][y-1] == type))
{
count ++;
count += Crdial(borad, type, x-1, y-1);
}
return count;
}
//向右斜右查同
int Crdiar(char borad[ROW][COL], char type, int x, int y)
{
int count = 0;
if((x+1 < ROW) && (y+1 < COL) && (borad[x+1][y+1] == type))
{
count ++;
count += Crdiar(borad, type, x+1, y+1);
}
return count;
}
//平局判定,遍历棋盘发现没有空位置且之前并没有判定输赢
int Isfull(char borad[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (borad[i][j] == ' ')
{
return 1;
}
}
}
return 2;
}
那么到这里这个三子棋小游戏就做好了,效果图:
当然啦,这个机器人并没有算法支持它下棋,用的时随机生成坐标,是个“人工智障” ,小伙伴自己玩可以把平局当成目标,还是有所难度滴。好啦,这期内容就到这里啦,想把三子棋升级的话只需要更改下图的三个值,行row,列col,n子连珠n的值:
比如改成9 9 5 就是9行9列的5子棋。另写一篇文章给大家完整代码。如果觉得这篇文章对你有帮助欢迎点赞转发评论区交流,关注小白阿g,让小白不再白学,亲爱的小伙伴们下期见。