1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > C语言自制小游戏:三子棋(井字棋)智能下棋补充

C语言自制小游戏:三子棋(井字棋)智能下棋补充

时间:2023-04-14 10:46:33

相关推荐

C语言自制小游戏:三子棋(井字棋)智能下棋补充

目录

一.前期准备二.逻辑与程序实现1.连珠①横向连珠②纵向连珠③斜向连珠④函数完善2.堵棋①横向堵棋②纵向堵棋③斜向堵棋3.整理函数①整理AI_Computer函数②修改ComputerMove函数三.增加胜率1.控制第一步2.最特殊的一种情况3函数完善四.总结

一.前期准备

这篇博客并不包含三子棋大体的内容设计,只有怎样让计算机下的棋更智能一些的讲解如果想看怎样实现三子棋大体程序设计内容还请跳转:

C语言自制小游戏:三子棋(井字棋)游戏(超详细)

在进行下面的内容开始前,需要你首先完善好大体的三子棋程序内容设计再向下进行

本篇文章写的很啰嗦,就是为了重复反复去推敲种种方式和可能性,可能这种啰嗦的表达方式,能让你理解的更加简单

二.逻辑与程序实现

这次我们要实现的是让计算机能更加智能的判断应该在哪个坐标下下入棋子,从而实现对玩家的“围追堵截”

在思考这个问题前,我们不妨将计算机替换成自己,想象一下,如果是你下棋,你是怎样判断此时应该在哪个坐标下下入棋子呢?

我们要从下面分成几种可能性逐个去分析

注意:要保持换位思考的概念,这里你把自己看成电脑去处理下面的“残局”案例,要记住你现在是后手下棋并且棋子是‘#’

这里我们创建一个函数去存放下面可能性中所生成的代码

——game.h

int AI_Computer(char arr[ROW][COL], int row, int col);

在这里你肯定会不理解为什么创建该函数,后面我会娓娓道来

1.连珠

①横向连珠

当出现下列“残局”你不想输掉游戏时,想必你肯定会在含有两颗‘#’井号的棋子中找到空白处放入‘#’井号棋子,下面是“残局”的例局

那我们如何在代码中让计算机在下棋时遇到该残局做出连珠这部操作呢,首先在之前的代码中电脑每一步都是随机下棋的,其实我们可以在之前的ComputerMove这个函数中调用新创建的AI_Computer函数去实现判断并下棋这两部操作

大体思路:首先让计算机遍历二维数组中的行,寻找有没有符合条件的行,此行中有且仅有两个元素是‘#’并且另一个元素是‘ ’空格字符,就可确认本行,然后记录‘ ’空格字符的坐标,最后将其坐标赋值为‘#’ 至此完成

我们创建两个变量,count变量去进行计数工作,index变量去记录下标

——game.c

int count = 0; //count负责记录该行'#'的个数int index = -1; //index记录坐标

要注意下面3个点:

这里我们要记录‘#’的数量,所以count负责记录该行‘#’的个数因为已经遍历行了,我们很容易可以得到当前的行号,所以index记录列坐标就可以之所以index的初值是-1而不是0是因为0也可以作为坐标出现在数组中,操作不方便,计算机无法知道这个0是因为坐标就是0所以为0,还是因为你没有操作index,所以赋值为-1在后期只要检验index是否>=0就可以轻易的知道index所传达的信息因为函数要返回int型,所以一旦AI_Computer函数完成了自己的使命,我们就立即return 0,返回的0可以作为一种标注,后面会用到

——game.c的AI_computer函数中写入

int AI_Computer(char arr[ROW][COL], int row, int col){for (int i = 0; i < row; i++)//遍历行{count = 0;index = -1;for (int j = 0; j < col; j++)//记录每一行的元素{//如果是'#'那记数器count的数量就加以加一if (arr[i][j] == '#'){count++;}if (arr[i][j] == ' '){index = j;}}//row是3不要忘记,写成row方便以后要改成4子棋等等使用//当此行的'#'等于2时并且index的坐标>=0就可以进行赋值操作了if (count == (row - 1) && index >= 0){arr[i][index] = '#';return 0;}}}

②纵向连珠

经过了上面的横向连珠,我想纵向连珠也不难推敲,只需要在含有两个‘#’的中找到空白处再次放入‘#’井号棋子,下面是“残局”的例局

纵向连珠前我们要注意以下几个点:

这次我们用count来记录列中有几个‘#’我们这次遍历列,所以index用来记录行号因为纵向连珠的代码也是要写在AI_Computer函数中的,所以之前我们在横向连珠中调用过count和index,所以这次用之前我们把count再重置为0,index为-1如果成功下棋记得return 0

——game.c的AI_computer函数中写入

count = 0; //重置countindex = -1; //重置indexfor (int j = 0; j < col; j++){count = 0;index = -1;int count = 0; //记录玩家棋子的个数int index = -1; //记录要下入的下标for (int i = 0; i < row; i++){if (arr[i][j] == '#'){count++;}if (arr[i][j] == ' '){index = i;}}if (count == (col - 1) && index >= 0){arr[index][j] = '#';return 0;}}

③斜向连珠

连珠的最后一步就是斜向连珠了,这个也不难,只需要在含有两个‘#’的对角线方向中找到空白处再次放入‘#’井号棋子,下面是“残局”的例局

斜向连珠前我们要注意以下几个点:

count继续记录数量在方向↘的斜线中你会发现都是[0,0] [1,1] [2,2]这样横纵一样的数字坐标,所以在方向↘的判断中只需要index记录行或列一个数字即可,这我们统一遍历每一行去判断。在方向↙的斜线中需要记录[0,2] [1,1] [2,0]这样的坐标,这样的情况下,我们还是遍历行,要记住cow的值一直为3,所以我们只需要利用cow的加减运算就可以写出想要的坐标,并赋值给index两条斜线方向的代码分开写如果成功下棋记得return 0

——game.c的AI_computer函数中写入

count = 0;index = -1;//判断方向↘的斜线是否要连珠for (int i = 0; i < row; i++){if (arr[i][i] == ch){count++;}if (arr[i][i] == '#'){index = i;}if (count == (row - 1) && index >= 0){arr[index][index] = '#';return 0;}}count = 0;index = -1;//判断方向的斜线↙是否要连珠for (int i = 0; i < row; i++){if (arr[i][col - 1 - i] == ch){count++;}if (arr[i][col - 1 - i] == '#'){index = i;}if (count == (col - 1) && index >= 0){arr[index][col - 1 - index] = '#';return 0;}}

④函数完善

因为AI_Computer函数的返回值int是需要在后期判断你是否下棋的,所以我们这里假设如果运行完AI_Computer后我们没有进行下棋这部操作,我们在最后返回return 1

int AI_Computer(char arr[ROW][COL], int row, int col){//判断横向连珠代码//判断纵向连珠代码//判断斜向连珠代码return 1;}

2.堵棋

要看好这里的是堵棋子,而不是“赌棋”!!!

堵棋的思路和连珠的方法大致相仿,只需要把判断是否有两个‘#’井号棋子改成是否有两个‘*’星号棋子

注意:要保持换位思考的概念,这里你把自己看成电脑去处理下面的“残局”案例,要记住你现在是后手下棋并且棋子是‘#’

①横向堵棋

在含有两颗‘*’星号棋子中找到空白处放入‘#’井号棋子,下面是“残局”的例局

那我们如何在代码中让计算机在下棋时遇到该残局做出堵棋这部操作呢

大体思路:首先让计算机遍历二维数组中的行,去寻找有没有符合条件的行,此行中有且仅有两个元素是‘*’,然后去寻找该行中是否有只一个元素为空格‘ ’字符并记录坐标,最后将其坐标赋值上‘#’

——game.c的AI_computer函数中写入

int AI_Computer(char arr[ROW][COL], int row, int col){for (int i = 0; i < row; i++)//遍历行{count = 0;index = -1;for (int j = 0; j < col; j++)//记录每一行的元素{//如果是'#'那记数器count的数量就加以加一if (arr[i][j] == '*'){count++;}if (arr[i][j] == ' '){index = j;}}//row是3不要忘记,写成row方便以后要改成4子棋等等使用//当此行的'#'等于2时并且index的坐标>=0就可以进行赋值操作了if (count == (row - 1) && index >= 0){arr[i][index] = '#';return 0;}}}

当你完成横向堵棋代码的书写后,你会发现,堵棋的代码和连珠高度相似,只有在判断是‘*’星号棋子,还是‘#’井号棋子的时候不用,只要修改一下这个字符就可以使得AI_Computer函数做到既能连珠也能堵棋,所以我们在AI_Computer的参数中外加一个char类型的ch参数,当你想调用AI_Computer时我们就可以直接修改新加的ch就可以做到连珠或堵棋这两种操作

修改AI_Computer函数

——game.h

int AI_Computer(char arr[ROW][COL], int row, int col,char ch);

知道为什么要新建个参数ch后我们就可以修改代码了,其实只需要把对应的位置改成ch即可

②纵向堵棋

在含有两个‘*’星号棋子的中找到空白处再次放入‘#’井号棋子,下面是“残局”的例局

③斜向堵棋

连珠的最后一步就是斜向连珠了,这个也不难,只需要在含有两个‘*’星号棋子的对角线方向中找到空白处再次放入‘#’井号棋子,下面是“残局”的例局

3.整理函数

①整理AI_Computer函数

因为我们在之前发现了应该在AI_Computer假如加一个新参数ch,在调用函数时传入‘*’就可实现连珠在传入‘#’就可以实现堵棋这两个操作,所以我们整理一下整个函数

在这里插入代码片int AI_Computer(char arr[ROW][COL], int row, int col,char ch){int count = 0; //判断用记录玩家棋子的个数int index = -1; //判断用记录要下入的下标//判断横向是否要连珠或堵棋for (int i = 0; i < row; i++){count = 0;index = -1;for (int j = 0; j < col; j++){if (arr[i][j] == ch){count++;}if (arr[i][j] == ' '){index = j;}}if (count == (row - 1) && index >= 0){arr[i][index] = '#';return 0;}}count = 0;index = -1;//判断竖向是否要连珠或堵棋for (int j = 0; j < col; j++){count = 0;index = -1;int count = 0; //记录玩家棋子的个数int index = -1; //记录要下入的下标for (int i = 0; i < row; i++){if (arr[i][j] == ch){count++;}if (arr[i][j] == ' '){index = i;}}if (count == (col - 1) && index >= 0){arr[index][j] = '#';return 0;}}count = 0;index = -1;//判断两个斜向是否要连珠或堵棋for (int i = 0; i < row; i++){if (arr[i][i] == ch){count++;}if (arr[i][i] == ' '){index = i;}if (count == (row - 1) && index >= 0){arr[index][index] = '#';return 0;}}count = 0;index = -1;for (int i = 0; i < row; i++){if (arr[i][col - 1 - i] == ch){count++;}if (arr[i][col - 1 - i] == ' '){index = i;}if (count == (col - 1) && index >= 0){arr[index][col - 1 - index] = '#';return 0;}}return 1;}

②修改ComputerMove函数

到这里我们就可以把AI_Computer函数与ComputerMove函数链接起来

因为在ComputerMove函数中有随机下棋的代码了

所以我们先想一下连珠 堵棋 随机下棋哪一样优先级更高

如果出现上面的情况,该你下入‘#’棋子,你是先去下3,3堵棋还是下1,2获得胜利,肯定是1,2直接获得胜利完成本局所以优先级:连珠>堵棋

如果出现这种情况,该你下入‘#’棋子,你肯定会下3,3去堵棋,并不是去调用随机下棋,不然你会输掉游戏,所以优先级:堵棋>随机下棋

优先级:连珠>堵棋>随机下棋

所以我们可以修改一下ComputerMove函数,当AI_Computer函数返回1说明没有检查到该连珠或堵棋的操作时我们再去进行随机下棋,在ComputerMove函数中建立一个count用于记录

void ComputerMove(char arr[ROW][COL], int row, int col){printf("电脑走:>\n");int x = 0;int y = 0;int count = 0;count = AI_Computer(arr, row, col, '#');if (count == 1){count = AI_Computer(arr, row, col, '*');}if (count == 1){while (1){//生成随机坐标,模除是为了把坐标控制在0-2x = rand() % row;y = rand() % col;//如果输入的坐标被占用我们就利用while循环再次生成随机数直到找到一个空坐标if (arr[x][y] == ' '){arr[x][y] = '#';break;}}}}

至此大体的智能连珠和堵棋的操作完成,电脑能做到比较简单的判断

三.增加胜率

完成了大体的连珠和堵棋的操作,你会发现电脑还是做不到100%的胜利,想要增强电脑的胜利,我们必须还要去分析整个棋局

1.控制第一步

因为三子棋的棋盘非常大的小,纵观整个棋盘,中心的点占据了主导位置控制整整4条方向的线,只要占领中心点胜率会高很多,所以我们让电脑优先占领中心点而四个角点可以控制三条方向上的线,比上不足比下有余最弱的只有四周的点位(也就是中心点正上方,正下方,正左方,正右方的四个点),只能控制两条方向线,第一步占领这四个点胜率会下降很多

总的来说优先级:中心点>角点>四周点

所以我们可以先记录一下回合,只要到了第一回合,如果玩家不占领中心点,电脑就去占领中心点,如果玩家占领了中心点,电脑就去占领角点因为玩家在第一回合不可能同时占领中心点和角点,所以没必要管理四周的点位这种分析仅限于第一回合,后面的回合还需要使用连珠堵棋和随机下棋

2.最特殊的一种情况

当出现

第一回合

玩家占领角点,电脑占领中心点第二回合

玩家占领第一回合角点的对点,电脑占领空闲的角点第三回合

玩家占领最后一个空闲的角点时,会出现横向和竖向两条线上,分别都有两个玩家的‘*’星号棋子时并且这两行没有电脑‘#’井字棋子占点,你会发现玩家将绝杀,无聊电脑堵住那个点,玩家必然会胜利

所以这种特殊的情况我们需要单独分析,也就是说当第一回合电脑占到中心点时,我们就去判断另一种情况的可能性

来到第二回合时,当玩家的棋子变成了对点,我们就让电脑不再在角点下棋,只让它在(正上,正下,正左,正右)下棋即可

3函数完善

修改test.c中的game函数,在最开始创建一个回合变量round去记录回合数,game函数回合一开始就去round++;即可记录回合数修改test.c中的game函数,在最开始创建一个激活变量activation去记录是否激活“最特殊的一种情况”修改game.c中ComputerMove函数中的参数,增加一个回合参数round,当round=1时为第一回合,我们就去对棋盘进行分析判断,然后决定电脑下第一步的坐标修改game.c中ComputerMove函数中的参数,增加一个激活参数&activation,注意这里要传地址,因为activation存储在game函数中,不传值到了下个回合将会直接失效ComputerMove函数中activation为1就激活“最特殊的一种情况”判断,为0不激活,激活的前提是:在第一回合时,电脑在中心点下棋

——game.h修改ComputerMove函数

void ComputerMove(char arr[ROW][COL], int row,int col,int round,int*activation);

——test.c修改game函数

void game(){char arr[ROW][COL] = {0 };InitBoard(arr,ROW,COL);PrintBoard(arr, ROW, COL);char ret = 0;int round = 0;//回合数int activation = 0;while (1){round++;PlayerMove(arr, ROW, COL);PrintBoard(arr, ROW, COL);ret = Is_Win(arr, ROW, COL);if (ret != 'C'){break;}ComputerMove(arr, ROW, COL,round,&activation);PrintBoard(arr, ROW, COL);ret = Is_Win(arr, ROW, COL);if (ret != 'C'){break;}}if (ret == '*'){printf("玩家胜利\n");}if (ret == '#'){printf("电脑胜利\n");}if (ret == 'Q'){printf("平局\n");}}

——game.c修改Computer函数

void ComputerMove(char arr[ROW][COL], int row, int col,int round,int*activation){printf("电脑走:>\n");int x = 0;int y = 0;int count = 0;if (*activation == 1){int cnt1 = 0;int cnt2 = 0;for (int i = 0; i < row; i++)//遍历行{//记录向↘的斜线方向是否有两个'*'if (arr[i][i] == '*'){cnt1++;}else{//记录向↙的斜线方向是否有两个'*'if (arr[i][row - 1] == '*'){cnt2++;}}//如果检测到那就开始规划电脑下棋的坐标if (cnt1 == (row - 1) || cnt2 == (row - 1)){while (1){x = rand() % row;y = rand() % col;//规划此时电脑下棋的坐标只能是正上,正下,正左,正右if (x!=y&&x!=(row-1-y)){arr[x][y] = '#';*activation = 0;return;}}}}}if (round == 1)//判断是否为第一回合{if (arr[(row - 1) / 2][(col - 1) / 2] == ' '){arr[(row - 1) / 2][(col - 1) / 2] = '#';//在第一回合,当电脑在正中心下棋,就激活“最特殊的一种情况”的判断*activation = 1;return;}if (arr[(row - 1) / 2][(col - 1) / 2] == '*'){while (1){x = rand() % row;if (x == 0 || x == row - 1){break;}}while (1){y = rand() % row;if (y == 0 || y == row - 1){break;}}arr[x][y] = '#';return;}}//判断是否连珠count = AI_Computer(arr, row, col, '#');if (count == 1){//判断是否堵棋count = AI_Computer(arr, row, col, '*');}if (count == 1){//随机下棋while (1){//生成随机坐标,模除是为了把坐标控制在0-2x = rand() % row;y = rand() % col;//如果输入的坐标被占用我们就利用while循环再次生成随机数直到找到一个空坐标if (arr[x][y] == ' '){arr[x][y] = '#';break;}}}}

四.总结

至此完成整个三子棋智能下棋的程序,如果你使用了上面的代码,你作为玩家能赢的了电脑,请你来下面评论,理论上来说,这种“弱结构”性的游戏,只要稍加分析,就很容易改变战局。

如果你想让这个游戏有些趣味性,可以去掉增加胜率的代码,只让电脑去连珠或堵棋,因为写入增加胜利的代码后,玩家不可能胜利,只能和电脑打成平局或者让电脑胜利

11000字的博客写了很久,还是希望你能留下一个赞或者关注我一下,谢谢啦!!!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。