当前位置: 首页 > news >正文

从0到1实现五子棋游戏!!

在这里插入图片描述

Hello,好久不见宝子们,今天来给大家更一个五子棋的程序~

我们今天要讲的内容如下:
在这里插入图片描述

在这里插入图片描述

文章目录

  • 1.五子棋游戏介绍
    • 1.1 游戏玩法介绍:
  • 2.准备工作
    • 2.1 具体操作流程
  • 3.游戏程序主函数
  • 4.初始化棋盘
    • 4.1.定义宏变量
    • 4.2 初始化棋盘
  • 5.打印棋盘
    • 5.1 改进棋盘
  • 6.打印玩家下棋的界面
  • 7.打印电脑下棋的界面
    • 7.1详解随机生成函数
      • 7.1.1 rand函数介绍
    • 7.1.2 srand函数介绍
    • 7.1.3 time函数介绍
    • 7.1.4 设置随机数的范围
    • 7.2 基于电脑下棋界面的算法实现
  • 8.判断输赢的界面
    • 8.1 按所在行判断输赢
      • 8.1.1 代码实现
    • 8.2 按所在列判断输赢
      • 8.2.2 代码实现
    • 8.3 按棋盘的正对角线判断输赢
      • 8.3.2 代码实现
    • 8.4 按棋盘的副对角线判断输赢
      • 8.4.2 代码实现
    • 8.5 判断棋盘是否下满了
      • 8.5.1 代码实现
  • 9.最终运行效果展示
  • 10.五子棋游戏源代码展示

1.五子棋游戏介绍

1.1 游戏玩法介绍:

五子棋: 又称连珠、连五子、五目、五珠等,是一种两人对弈的棋类游戏。它使用一个棋盘,通常是15×15的方格,并使用黑白两种颜色的棋子。游戏的目标是先在棋盘上形成连续的五个自己颜色的棋子,可以是横、竖、斜线方向的连线。 与围棋类似,五子棋也是一种战略性的游戏,需要玩家在防守对手的同时,寻找机会形成自己的连珠。由于规则简单,易于上手,五子棋在全球范围内都有广泛的普及和流行。

2.准备工作

首先,在写这个五子棋程序的时候,我们得先在VS中创建3个文件,这三个文件的作用如下图所示:
在这里插入图片描述

从上图可以知道: 我们整个游戏的测试都是放在test.c上面去的,所以我们的主函数也是在test.c这个文件下写的。

2.1 具体操作流程

如下图:
在这里插入图片描述

我们只需在键盘中按下“CTRL+SHIFT+A”, 即可弹出上图的界面,然后在C++文件那栏点击以.c创建两个源文件test.cgame.c,然后在头文件那栏.h创建头文件game.h即可。

3.游戏程序主函数

从博主之前写的博客:
扫雷游戏增强版代码实现和详解(超详细~)
可以知道,在编写这类游戏项目的代码中,我们通常都会在test.c文件写上这个主函数~
代码如下:

#include<stdio.h>void menu() {printf("*****************************************************\n");printf("*****************   0. exit games   *****************\n");printf("*****************   1. play games   *****************\n");printf("*****************************************************\n");
}int main() {int input = 0;do {menu();printf("请选择>:");scanf("%d", &input);if (input == 1) {printf("玩游戏\n");}else if (input == 0) {printf("退出游戏\n");}else {printf("该数不在范围,请重新输入\n");}} while (input);return 0;
}

vs运行结果如下
在这里插入图片描述

从上图可以看出,玩家可以通过选择10玩游戏或者退出游戏,而如果玩家输入的数不在**01范围内,我们会让他重新输入数字,直到玩家输入0,该程序就会结束。因为在C语言中,0为假,非0为真,因此当我们输入0的时候,它将退出do…while循环。**

4.初始化棋盘

4.1.定义宏变量

在我们初始化棋盘之前,我们要先在game.h头文件先对行和列进行宏变量操作,如下:
在这里插入图片描述
这是因为后续如果我们要改变棋盘的行数和列数,直接在头文件这里修改宏变量就行,就不需要在
game.c实现函数的源文件上一个一个修改行数和列数。

在这里插入图片描述
同时,为了方便起见,我们顺便将库函数头文件全部放进game.h的头文件中,然后分别在test.cgame.c
源文件中加上#include"game.h这句话,这样的好处是:
如果之后要用到其他库函数,难免会用到其他头文件,那如果说我们把函数头文件放到game.h中,就不需要在test.cgame.c源文件中重复编写头文件。

4.2 初始化棋盘

如下图所示:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
从上两幅图可以得知:
当用户选择1玩游戏main函数将调用game函数,然后在test.cgame函数内部,先将棋盘全部初始化为0。
然后调用InitBoard函数,我们先在game.h中声明InitBoard函数,然后在game.c实现这个函数功能,将棋盘的每个位置全都初始化为空格。

5.打印棋盘

首先,我们最终想要打印出的效果如下图~
在这里插入图片描述

这里可能很多同学都没思路,不知道怎么去打印这个棋盘,没事,这里博主会提供思路~

如下图所示:
在这里插入图片描述
分析: 从上图,我们可以把打印数据打印分割线分别看为一组。然后细心观察的话,我们发现打印数据那行,每打印3个空格,就带有一个|
而因为我们之前是将棋盘元素全部初始化为空格,因此我们这里打印可以在该棋盘元素左右各增添一个空格
另外,我们需要注意的是: 这里的分割线一共是打印4行。

好了,分析了那么多,我们直接上代码~
在这里插入图片描述
代码如下:

void Displayboard(char board[ROW][COL], int row, int col) {int i = 0;for (i = 0; i < row; i++) {printf(" %c | %c | %c | %c | %c \n", board[i][0], board[i][1], board[i][2], board[i][3], board[i][4]);if (i < row - 1) {printf("---|---|---|---|---\n");}}
}

我们来看一下VS运行效果图吧~
在这里插入图片描述
虽然代码能打印出这个效果出来,但我们认为这个代码还是有缺陷的,为什么呢?
看下图:
在这里插入图片描述
这里我们假设将game.h头文件中的ROW行和COL列分别改为10,然后运行程序,然后发现每行打印的棋盘元素不足10个。
还是停留在原先一行打印五个棋盘元素这种情况。因此这个代码局限性很大,因此我们要对其进行改进。

5.1 改进棋盘

如果我们要根据行和列的数据,来打印相应大小的棋盘,这个我们该怎么想呢?
这里我们根据之前分析的图,再重新进行加工了一下。
如图
在这里插入图片描述
相信同学们看了博主分析的这个图,会对更加理解这个打印棋盘的原理是什么。

在这里插入图片描述
代码如下:

void Displayboard(char board[ROW][COL], int row, int col) {int i = 0;for (i = 0; i < row; i++) {for (int j = 0; j < col; j++) {printf(" %c ", board[i][j]);if (j < col - 1) {printf("|");}}printf("\n");if (i < row - 1) {for (int j = 0; j < col; j++) {printf("---");if (j < col - 1) {printf("|");}}printf("\n");}}}

vs运行效果图如下:
在这里插入图片描述
我们通过VS运行结果,发现运行的结果都确实跟我们预期打印的效果一模一样,无论game.h头文件中的ROWCOL的值怎么变化,它都会根据它的行和列的值打印相应的分割线棋盘元素,从而控制棋盘的大小。

6.打印玩家下棋的界面

这是我们最终想打印的效果,如图所示:
在这里插入图片描述
那我们该怎么打印出这个效果呢,接下来博主给你细细道来~

我们根据预期想打印的效果,经过分析,可以把这个图画出来,如下:
在这里插入图片描述
看到这里,想必很多同学都会深刻理解这个图的大部分内容。
但可能还有部分同学会有疑问,数组的下标不是从0开始吗?为什么玩家输入的坐标为3,3,然后对应的棋盘数组元素为board[2][2]呢?
这样设计代码逻辑有什么意义呢?

这是因为很多玩家并不是程序员啊,他们可不关心数组的下标是否从0开始,只会凭正常人的逻辑来输入坐标,并根据他们所输入坐标的值达到他们想要输出棋盘的效果哈。

好,那接下来我们就给大家放代码,相信同学们通过我们上面的解析和代码,能够彻底理解这个逻辑。

在这里插入图片描述
代码如下:

void playerMove(char board[ROW][COL], int row, int col) {int x = 0;int y = 0;printf("玩家下棋\n");while (1) {printf("请输入坐标,中间输入空格:");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col) {if (board[x - 1][y - 1] == ' ') {board[x - 1][y - 1] = '*';break;}else {printf("该坐标已被占用,重新输入\n");}}else {printf("坐标非法,请重新输入\n");}}
}

VS运行效果展示:
在这里插入图片描述
这里需要注意的是:test.c 源文件中,我们要将playerMove函数和Displayboard分别放入while语句中。这是为什么呢?
来看下图:
在这里插入图片描述
如果说我们把这两个函数不放到while语句中,当玩家输入了一次坐标,就回到主调main函数里面,又因为1为真,因此不会退出do…while循环,会重新回到上面的menu函数重新进行判断,这里就相当于已经玩了一次游戏,问玩家还要不要玩一次,这显然是不合理的。 因为玩家刚刚的棋局还没赢或输或平局,就直接结束游戏,这显然不符合五子棋游戏逻辑。
因此,我们要将playerMoveDisplayboard两个函数放到while循环里面。

7.打印电脑下棋的界面

这是我们最终想要打印出来的效果,如下图所示:
在这里插入图片描述
我们发现,当玩家在3,3坐标处下棋了,电脑就随机在棋盘元素为空格的位置下棋。用#号来填充空格,那我们该如何实现这个电脑随机下棋的算法呢?别急,博主接下来带着你们来分析嘿嘿!

要想让电脑生成随机下棋的坐标,前提是得让它们的坐标生成随机数啊,那接下来,博主给大家详解3种随机生成数的函数。
在这里插入图片描述

7.1详解随机生成函数

7.1.1 rand函数介绍

我们知道,C语言提供了一个函数叫rand,这个函数是可以生成随机数的。
具体函数介绍可以看下面这两幅图:
在这里插入图片描述
在这里插入图片描述
这里的rand函数会返回一个伪随机数,这个随机数的范围是在0~RAND_MAX之间
这个RAND~MAX的大小是依赖编译器上实现的,但是大部分都编译器上都是32767
并且rand函数的使用需要包含一个头文件是:#inlcude <stdlib.h>

好,那这里我们就测试一下rand函数,这里多调用几次,看看效果怎么样~
这里我们会以动图的方法来演示:
在这里插入图片描述
通过仔细观察动图,我们发现VS两次运行中出现的数字都一模一样,这就说明有点问题了。

如果我们深入了解,不难发现,其实rand函数生成的随机数是伪随机的,那伪随机数不是真正的随机数,是通过某种算法生成的随机数。真正的随机数是无法预测下一个值是多少的,所以我们推断,rand函数是对一个叫种子的基准值进行运算生成的随机数。

之所以前面每次运行程序产生的随机数序列是一样,那是因为rand函数生成随机数的默认种子是1。如果要生成不同的随机数,就要让种子变化。

7.1.2 srand函数介绍

同样地,C语言又提供了一个函数教srand,它是用来初始化随机数的生成器的,它的函数原型如下:

void srand (unsigned int seed);

具体的函数介绍如下:
在这里插入图片描述
这里我们简单介绍一下:就是程序在调用rand函数之前要先调用srand函数,通过srand函数的参数seed来设置生成随机数的时候的种子,只要种子在变化,每次生成的随机数序列也就变化起来了。
所以: 如果说给srand的种子是随机的,rand就能生成随机数;在生成随机数的时候又需要一个随机数,这就矛盾了。

我们这里就把srand函数中的参数seed改为2,看看那个结果是否会变化。
如下图:
在这里插入图片描述
事实上: 当我们把seed种子数改为其他的数字,rand函数所生成的随机数也会改变的。

7.1.3 time函数介绍

在程序中我们一般是使用程序运行的时间作为种子的,因为时间时刻在发生变化的。
那在C语言中有一个函数叫time,就可以获得这个时间,time函数原型如下:

time_t time (time_t* timer);

具体的函数介绍如下:
在这里插入图片描述
我们这就简单讲一下吧:

  • time函数会返回当前的日历时间,其实返回的是19701月1日0时0分0秒到现在程序运行时间之间的差值,单位是秒。返回的类型是time_t类型的,time_t 类型本质上其实就是32位或者64位的整型类型。
  • time函数的参数timer如果是非NULL的指针的话,函数也会将这个返回的差值放在timer指向的内存中带回去。
  • 如果timerNULL,就只返回这个时间点差值。time函数返回的这个时间差也被叫做:
    时间戳
    需要注意的是: 使用time函数的时候务必加上头文件:#include<time.h>

但如果只是让time函数返回时间戳,我们就可以这样写:

time(NULL);//调用time函数返回时间戳,这里接受返回值

那我们就可以让生成随机数的代码改写成如下:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>int main()
{//使用time函数的返回值设置种子//因为srand的参数是unsigned int类型,我们将time函数的返回值强制类型转换srand((unsigned int)time(NULL));printf("%d\n", rand());printf("%d\n", rand());printf("%d\n", rand());printf("%d\n", rand());printf("%d\n", rand());return 0;
}

至于说它加上time函数,它每一次的运行结果是否相同 ,这个我们以动图的形式给同学们展示一下~
如下所示:
在这里插入图片描述
细心观察这个动图的话,我们发现VS这两次的运行结果都是不一样的。
另外,我们再提一下:srand函数是不需要频繁调用的,一次运行的程序中调用一次就够了。

7.1.4 设置随机数的范围

1.如果我们要生成0~99之间的随机数,方法如下:

rand()%100;//余数的范围是0~99

2.如果要生成1~100之间的随机数,方法如下:

rand()%100+1;//%100的余数是0~99,0~99的数字+1,范围是1~100

3.如果要生成100~200的随机数,方法如下:

1.100 + rand()%(200-100+1)
2               //余数的范围是0~100,加100后就是100 ~200。

总结: 如果要生成a~b的随机数,方法如下:

a+rand()%(b-a+1)

好,讲到这里,我们就回到一开始的问题,我们不是要求电脑随机数吗?
那首先我们横坐标要先得到的是0~row-1的数字,纵坐标得到的是0~col-1的数字。这是因为数组的下标是从0开始的,所以我们设计电脑下棋算法的时候,不能让他从1开始遍历。
我们根据上面的分析,可以把这幅图给画出来,如下:
请添加图片描述
相信同学们看了博主分析和做的图,应该能够很好地理解随机数函数的用法以及设计电脑算法的原理。

7.2 基于电脑下棋界面的算法实现

在这里插入图片描述

如下图所示:
在这里插入图片描述这里我们还需要一下: 就是要在game.h引入两个头文件分别是#include <stdlib.h>#include<time.h>,然后在test.c源文件中加上srand((unsigned int)time(NULL));这行代码,能确保在game.c中用那个随机生成函数rand才能够生成真正的随机数。
同时在test.c源文件中还要把ComputerMoveDisplayboard这两个函数分别放在while循环里面,不然当玩家下了一次棋,电脑下了一次棋,这个游戏就提前终止了,这显然是不符合游戏逻辑的。

代码实现:

void ComputerMove(char board[ROW][COL], int row, int col) {int x = 0;int y = 0;printf("电脑下棋\n");while (1) {x = rand() % row;y = rand() % col;if (board[x][y] == ' ') {board[x][y] = '#';break;}}
}

VS运行效果图
在这里插入图片描述

从运行结果来看: 我们发现当玩家输入3 3坐标时,此时在棋盘元素2 2的位置由一个空格变为一个*
当玩家输出一个*的时候,此时电脑就随机在棋盘元素的某个位置下棋。也是当棋盘元素为空格的情况,才将那个点的坐标由空格变为一个#

8.判断输赢的界面

在前面,我们已经写了很多行代码了,但是我们发现这个代码没有判断输赢的界面,这会导致什么问题呢?如下图,我们来看一下~
在这里插入图片描述
从图中:我们可以直观地发现,当玩家和电脑的输出的元素已经把整个棋盘给塞满了,那么此时游戏应该判断输赢,而不应该卡在电脑下棋的界面,所以这显然也是不符合游戏逻辑的。
因此我们要写一个判断棋盘输赢的算法出来

那我们该如何设计输赢的算法呢?
我们可以这么想,如下图:
在这里插入图片描述
相信大家看了我们这个图,或许会有点思路了,但可能还是不太清楚具体如何实现这个算法,那我们接下来就讲一下代码实现吧~
如下图所示:
在这里插入图片描述
代码解读: 我们可以现在test.c源文件中的game函数内部创建一个ret的变量,然后在while循环内部,
当玩家或电脑落一次子,就调用一次那个Iswin函数,这个函数专门是根据本场棋局玩家和电脑的情况,返回相对应的字符。然后当Iswin返回的是字符'C',表明棋局还没结束,则不会退出while循环,玩家和电脑可以继续下棋。
而当Iswin返回到不是字符'C',则表明本次棋局已经分出胜负了,那么就要用break语句退出while循环,然后我们用几个if语句来进行判断即可。

那可能讲到这里,或许有同学已经豁然开朗,但还是不太知道怎么去实现Iswin函数的游戏代码呢?那接下来博主会分四种情况来进行分析。

8.1 按所在行判断输赢

这里我们画了个图,方便同学们理解,如图所示:
在这里插入图片描述
这里我们主要是针对棋局的来判断输赢,如果所在那行全是同一字符,那么就能判断玩家赢还是电脑赢。
需要注意的是:我们判断某一行的输赢,还要让这行的棋盘元素都不为空格,这是因为空格字符是棋盘元素一开始初始化就有的,因此不能拿它作为判断输赢的依据。

8.1.1 代码实现

那根据前面的分析,以及二维数组相关的知识,我们也能顺理成章地把这个代码给写出来。
代码如下:

char Iswin(char board[ROW][COL], int row, int col) {for (int i = 0; i < row; i++) {int count = 0;for (int j = 0; j < col; j++) {if (board[i][0] == board[i][j] && board[i][j] != ' ') {count++;}}if (count == 5) {return board[i][0];}}

这里我们主要是定义了一个count变量,用它来枚举所在行是否为相同元素,且不为空格,如果在内层for循环中count的值已经自增到5时,那么就直接返回这行中的任意一个棋盘元素回去。

8.2 按所在列判断输赢

这里我们画了个图,方便同学们理解,如图所示:
在这里插入图片描述
这里我们主要是针对棋局的来判断输赢,如果所在那列全是同一字符,并且所在那列都不是空白字符,那么就能判断玩家赢还是电脑赢。

8.2.2 代码实现

那根据前面的分析,以及二维数组相关的知识,我们也能顺理成章地把这个代码给写出来。
代码如下:

for (int i = 0; i < row; i++) {int count = 0;for (int j = 0; j < col; j++) {if (board[0][i] == board[j][i] && board[j][i] != ' ') {count++;}}if (count == 5) {return board[0][i];}
}

这里我们主要是定义了一个count变量,用它来枚举所在那列是否为相同元素,且不为空格,如果在内层for循环中count的值已经自增到5时,那么就直接返回这列中的任意一个棋盘元素回去。

8.3 按棋盘的正对角线判断输赢

这个难免会有同学不理解什么叫正对角线是什么意思?我们这里画了个图,方便大家理解一下~
如图:
在这里插入图片描述
相信大家看了这个图,自己应该能够很好地理解什么叫正对角线

8.3.2 代码实现

那根据前面博主画图的分析,我们也能顺理成章地把这个代码给写出来。
代码如下:

	int count = 0;for (int i = 0; i < row; i++) {if (board[0][0] == board[i][i] && board[i][i] != ' ') {count++;}if (count == 5) {count = 0;return board[i][i];}}

这里我们不需要些两个for循环判断正对角线,只需写一个for循环就行。
因此我们定义那个count变量就需要在for循环外部定义,以免循环体执行一次,它的count就置为0。那当count当值自增到5的时候,则返回正对角线其中一个字符回去,这样就能知道谁赢了,那游戏自然就结束了。

8.4 按棋盘的副对角线判断输赢

那同样的,副对角线也是一样的道理,如下图所示:
在这里插入图片描述

相信大家看了这个图,自己应该能够很好地理解什么叫副对角线以及行数i和列数j的关系。

8.4.2 代码实现

那根据前面博主画图的分析,我们也能顺理成章地把这个代码给写出来。

	count = 0;for (int i = 0; i < row; i++) {if (board[0][row-1] != board[i][row-i-1] || board[i][row - i - 1] == ' ') {break;}else {count += 1;}if (count == 5) {return board[0][row-1];}}count = 0;

代码解读: 同样地,这里用一个for循环就足够了,然后将那个列数j替换为row-i-1。当变量count的值自增到5的时候,那么就返回该棋盘元素的副对角线的其中一个元素回去即可。
当然,这里有同学有疑问,为什么要将在for循环外层要先将count置为0呢?这里我们解释一下~
我们先来看一下这个图:
在这里插入图片描述
这个是game.c源文件,专门是实现游戏函数的功能,在该源文件的118行,我们首先是先定义了一个变量count,并把count的值初始化为0。如果说在判断棋盘元素为正对角线for循环中,如果说count的值没有自增到5,自增到1-4范围内,而没有对其重新初始化为0,这会影响后面判断棋盘元素为副对角线的结果。因此我们要及时把count的值及时置为0,才能有效地避免运行时出现的错误。

8.5 判断棋盘是否下满了

在前面实现那么多游戏功能,看似功能已经比较完善了,但是还是有一点缺陷的,就是如果说这次棋局大家都把棋盘下满了,并且还没分出胜负,这显然是有问题的,因此我们在game.c源文件中再写一个FullBoard函数,这是用来判断棋盘是否全部空格都填充完毕,是的话就直接返回字符'Q',这就意味着平局。

好,在实现FullBoard函数功能之前,我们先写个if语句来调用FullBoard函数。
具体如下图:
在这里插入图片描述
然后具体的FullBoard函数实现我们就写成这样子
具体如下:
在这里插入图片描述
FullBoard函数内部,我们就用双层for循环枚举所有的棋盘元素,但凡如果有一个棋盘元素是空格,我们都会返回0。如果枚举所有棋盘元素,发现所有的棋盘元素都不是空格了,那我们就返回1
那我们再看下图:
在这里插入图片描述
如果这个FullBoard函数返回的结果是1,那if(1)为真,直接就返回字符'Q'。那就意味着平局。但如果FullBoard函数返回的结果是0,那if(0)是为假。就不会执行if语句return 'Q'那句话,就会执行if后面那条语句return 'C',这也就意味着本次棋局还没结束,玩家和电脑将会继续进行下棋。

8.5.1 代码实现


int FullBoard(char board[ROW][COL], int row, int col) {int i = 0;for (i = 0; i < row; i++) {int j = 0;for (j = 0; j < col; j++) {if (board[i][j] == ' ') {return 0;}}}return 1;
}
count = 0;
if (FullBoard(board, ROW, COL)) {return 'Q';
}
return 'C';//继续游戏

同时呢,我们也顺便将这个FullBoard函数放到game.h的头文件中声明一下。
具体如下:
在这里插入图片描述

9.最终运行效果展示

大家可以看一下这个动图,这里我们简单地演示了五子棋的运行效果~
请添加图片描述

10.五子棋游戏源代码展示

接下来,博主给大家分别展示test.c源文件和game.c源文件和game.h头文件全部的源码。
1.test.c源代码展示

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"void menu() {printf("*****************************************************\n");printf("*****************   0. exit games   *****************\n");printf("*****************   1. play games   *****************\n");printf("*****************************************************\n");
}void game() {char board[ROW][COL] = { 0 };InitBoard(board, ROW, COL);Displayboard(board, ROW, COL);char ret = 0;while (1) {playerMove(board, ROW, COL);Displayboard(board, ROW, COL);ret = Iswin(board, ROW, COL);if (ret != 'C')break;ComputerMove(board, ROW, COL);Displayboard(board, ROW, COL);ret = Iswin(board, ROW, COL);if (ret != 'C')break;}if (ret == '*') {printf("玩家获胜!\n");}else if (ret == '#') {printf("电脑获胜!\n");}else if (ret == 'Q') {printf("双方平局\n");}}int main() {int input = 0;srand((unsigned int)time(NULL));do {menu();printf("请选择>:");scanf("%d", &input);if (input == 1) {//printf("玩游戏\n");game();}else if (input == 0) {printf("退出游戏\n");}else {printf("该数不在范围,请重新输入\n");}} while (input);return 0;
}

2.game.h源代码展示

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 5
#define COL 5
void InitBoard(char board[ROW][COL],int row,int col);
void Displayboard(char board[ROW][COL], int row, int col);
void playerMove(char board[ROW][COL], int row, int col);
void ComputerMove(char board[ROW][COL], int row,int col);
int Full(char board[ROW][COL], int row, int col);
char Iswin(char board[ROW][COL], int row, int col);

3.game.c源代码展示

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"void InitBoard(char board[ROW][COL], int row, int col) {int i = 0;for (i = 0; i < row; i++) {int j = 0;for (j = 0; j < col; j++) {board[i][j] = ' ';}}
}void Displayboard(char board[ROW][COL], int row, int col) {int i = 0;for (i = 0; i < row; i++) {for (int j = 0; j < col; j++) {printf(" %c ", board[i][j]);if (j < col - 1) {printf("|");}}printf("\n");if (i < row - 1) {for (int j = 0; j < col; j++) {printf("---");if (j < col - 1) {printf("|");}}printf("\n");}}}void playerMove(char board[ROW][COL], int row, int col) {int x = 0;int y = 0;printf("玩家下棋\n");while (1) {printf("请输入坐标,中间输入空格:");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col) {if (board[x - 1][y - 1] == ' ') {board[x - 1][y - 1] = '*';break;}else {printf("该坐标已被占用,重新输入\n");}}else {printf("坐标非法,请重新输入\n");}}}void ComputerMove(char board[ROW][COL], int row, int col) {int x = 0;int y = 0;printf("电脑下棋\n");while (1) {x = rand() % row;y = rand() % col;if (board[x][y] == ' ') {board[x][y] = '#';break;}}}int FullBoard(char board[ROW][COL], int row, int col) {int i = 0;for (i = 0; i < row; i++) {int j = 0;for (j = 0; j < col; j++) {if (board[i][j] == ' ') {return 0;}}}return 1;
}char Iswin(char board[ROW][COL], int row, int col) {for (int i = 0; i < row; i++) {int count = 0;for (int j = 0; j < col; j++) {if (board[i][0] == board[i][j] && board[i][j] != ' ') {count++;}}if (count == 5) {return board[i][0];}}for (int i = 0; i < row; i++) {int count = 0;for (int j = 0; j < col; j++) {if (board[0][i] == board[j][i] && board[j][i] != ' ') {count++;}}if (count == 5) {return board[0][i];}}int count = 0;for (int i = 0; i < row; i++) {if (board[0][0] == board[i][i] && board[i][i] != ' ') {count++;}if (count == 5) {count = 0;return board[i][i];}}count = 0;for (int i = 0; i < row; i++) {if (board[0][row - 1] != board[i][row - 1 - i] || board[i][row - 1 - i] == ' ') {break;}else {count += 1;}if (count == 5) {return board[0][row - 1];}}count = 0;if (FullBoard(board, ROW, COL)) {return 'Q';}return 'C';//继续游戏}

大家可以借鉴一下博主实现五子棋游戏的代码逻辑,可以放到VS软件中自行测试一下,如果可以的话,大家可以另外增加一些小功能。
比如: 如何设计让电脑下棋更加牛逼,玩家很难下赢电脑等等功能……
大家可以尽量发挥自己的想象力,创造出更有意思的游戏功能嘿嘿!

好,写到这里,五子棋游戏的介绍就完毕啦!

如果大家对博主某些知识点讲得不够透彻的话,可以在评论区指出,博主会解答一下的。

**当然如果大家在本篇博客中收获满满,可以给博主这篇文章一键三连吗! **
在这里插入图片描述
** 谢谢大家啦!!! **

相关文章:

从0到1实现五子棋游戏!!

Hello&#xff0c;好久不见宝子们&#xff0c;今天来给大家更一个五子棋的程序~ 我们今天要讲的内容如下&#xff1a; 文章目录 1.五子棋游戏介绍1.1 游戏玩法介绍&#xff1a; 2.准备工作2.1 具体操作流程 3.游戏程序主函数4.初始化棋盘4.1.定义宏变量4.2 初始化棋盘 5.打印…...

二叉树(C/C++)

本篇将较为详细的介绍二叉树的相关知识&#xff0c;以及二叉树的实现。对于二叉树的相关知识&#xff0c;本篇介绍了其概念、特殊的二叉树、性质还有存储结构。 接着对于实现二叉树的每个函数都有其思路讲解&#xff0c;主要的函数分为&#xff1a;遍历&#xff1a;前中后序遍历…...

Django学习笔记-ModelForm使用(完全依赖)

1.创建模型 ,code,name,sex,entrydate 2.模型映射 python manage.py makemigrations myapp01,python manage.py migrate 3.创建模型表单,继承forms.ModelForm,Meta:元数据,models需引入,fields填写引用的模型变量 4.创建testModelForm.html,添加urls 5.views编写testmodelfo…...

动态规划之使用最小花费爬楼梯【LeetCode】

动态规划之使用最小花费爬楼梯 LCR 088. 使用最小花费爬楼梯解法1解法2 LCR 088. 使用最小花费爬楼梯 LCR 088. 使用最小花费爬楼梯 解法1 状态表示&#xff08;这是最重要的&#xff09;&#xff1a;dp[i]表示以第i级台阶为楼层顶部&#xff0c;到达第i层台阶的最低花费。 状…...

双指针---解决实际问题

...

每天一个数据分析题(一百七十八)

在大样本&#xff08;样本量为n&#xff09;下进行某一列数据&#xff08;A列&#xff09;均值的区间估计时&#xff0c;假设点估计的值计算为a&#xff0c;显著性水平为0.05&#xff0c;z0.025为给定的显著性水平下的正态分布的临界值&#xff0c;则使用EXCEL的计算方法正确的…...

Ethernet/IP转Modbus TCP网关

产品功能 1 YC-EIP-TCP工业级EtherNet/IP 网关 2 Modbus TCP 转 EtherNet/IP 3支持ModBus主从站 4 即插即用 无需编程 轻松组态 ,即实现数据交互 5导轨安装 支持提供EDS文件 6 EtherNET/IP与ModBus互转数据透明传输可接入PLC组态 支持CodeSys/支持欧姆龙PLC 支持罗克韦尔(AB) 典…...

const详解

文章目录 简介什么是const呢?const 的使用1.用来定义常量2.和指针相关的const3.函数与const4.类中使用const(重点) c中去掉const属性验证不同对象的调用const修饰类内成员 c中的const 和 c中的const 的区别c中的const为什么c中的const常量又不能通过指针修改呢 const 和 #defi…...

多方面浅谈互联网技术

目录 方向一&#xff1a;物联网技术概述 方向二&#xff1a;物联网技术的应用 方向三&#xff1a;物联网发展所需技术和创新挑战 物联网技术&#xff08;Internet of Things&#xff0c;IoT&#xff09;是一种将各种智能设备、传感器、电子产品等连接起来&#xff0c;通过互…...

Oracle EBS GL 外币折算逻辑

背景 由于公司财务在10月份期间某汇率维护错误,导致帐套折算以后并合传送至合并帐套生成合并日记帐凭证的借贷金额特别大,但是财务核对的科目余额有没有问题,始终觉得合并日记帐生成会计分发有问题,需要我们给出外币折算逻辑。 基础设置 汇率 Path: GL->设置->币种-&…...

Java面试题之mysql

Mysql 1. MySQL的索引原理是什么?什么是索引&#xff1f;以及索引的优缺点&#xff1f;2. 解释一下B树和B树的区别及各自定义?3. MyISAM索引和Innodb索引的区别&#xff1f;4. 什么是聚簇索引&#xff1f;辅助索引&#xff1f;5.非聚簇索引一定会回表查询么&#xff1f;6. 什…...

抖音直播封禁申诉话术怎么讲?抖音直播封号怎么申请解封?

一.抖音直播封禁申诉话术怎么讲? 1. 了解封禁原因&#xff1a;首先&#xff0c;您需要清楚自己为何被封禁。抖音通常会在封禁时给出原因&#xff0c;如违规内容、恶意行为等。了解原因有助于您针对性地构建申诉话术。 2. 表达诚挚歉意&#xff1a;在申诉话术中&#xff0c;首…...

使用Jenkins部署前端Vue项目和后端Java服务

Jenkins安装相关插件&#xff0c;供后续使用&#xff08;Dashboard - Manage Jenkins - Plugins&#xff09; Maven Integration plugin https://plugins.jenkins.io/maven-plugin CloudBees Docker Build and Publish pluginhttps://plugins.jenkins.io/docker-build-publish…...

刷题——显示屏

目录 题目描述 输入格式 输出格式 输入输出样例 说明/提示 解 题目描述 液晶屏上&#xff0c;每个阿拉伯数字都是可以显示成 35 的点阵的&#xff08;其中 X 表示亮点&#xff0c;. 表示暗点&#xff09;。现在给出数字位数&#xff08;不超过 100100&#xff09;和一串数…...

WEB服务器-Tomcat(黑马学习笔记)

简介 服务器概述 服务器硬件 ● 指的也是计算机&#xff0c;只不过服务器要比我们日常使用的计算机大很多。 服务器&#xff0c;也称伺服器。是提供计算服务的设备。由于服务器需要响应服务请求&#xff0c;并进行处理&#xff0c;因此一般来说服务器应具备承担服务并且保障…...

第五节:Vben Admin权限-前端控制方式

系列文章目录 第一节:Vben Admin介绍和初次运行 第二节:Vben Admin 登录逻辑梳理和对接后端准备 第三节:Vben Admin登录对接后端login接口 第四节:Vben Admin登录对接后端getUserInfo接口 第五节:Vben Admin权限-前端控制方式 文章目录 系列文章目录前言一、Vben Admin权…...

蓝桥杯备赛第二篇(背包问题)

1. 01 背包&#xff08;采用状态压缩&#xff09; public static void main(String[] args) {Scanner scanner new Scanner(System.in);int M scanner.nextInt();int N scanner.nextInt();int[] value new int[N 1];int[] weight new int[N 1];int[] dp new int[M 1];…...

【postgresql 基础入门】带过滤条件的查询,where子句中的操作符介绍,案例展示,索引失效的大坑就在这里

查询数据-过滤数据 ​专栏内容&#xff1a; postgresql内核源码分析手写数据库toadb并发编程 ​开源贡献&#xff1a; toadb开源库 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#…...

vue项目打包获取git commit信息并输出到打包后的指定文件夹中

需求背景&#xff1a; 前端项目经常打包&#xff0c;发包部署&#xff0c;为了方便测试及运维发现问题时与正确commit信息对比 实现方式&#xff1a; 使用Node.js的child_process模块来执行git命令 实现步骤&#xff1a; 1.在package.json的同级目录下新建一个version.js文件。…...

vue 移动端app预览和保存pdf踩坑

需求 使用Vue开发h5&#xff0c;嵌套到Android和IOS的Webview里&#xff0c;需要实现pdf预览和保存功能&#xff0c;预览pdf的功能&#xff0c;我这边使用了三个库&#xff0c;pdf5&#xff0c;pdf.js&#xff0c;vue.pdf&#xff0c;现在把这三个库在app端的坑分享一下。先说…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

无人机侦测与反制技术的进展与应用

国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机&#xff08;无人驾驶飞行器&#xff0c;UAV&#xff09;技术的快速发展&#xff0c;其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统&#xff0c;无人机的“黑飞”&…...

Golang——7、包与接口详解

包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...

Python 高效图像帧提取与视频编码:实战指南

Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...