目录

  • 一.前期准备
  • 二.逻辑与程序实现
    • 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个点:

  1. 这里我们要记录‘#’的数量,所以count负责记录该行‘#’的个数
  2. 因为已经遍历行了,我们很容易可以得到当前的行号,所以index记录列坐标就可以
  3. 之所以index的初值是-1而不是0是因为0也可以作为坐标出现在数组中,操作不方便,计算机无法知道这个0是因为坐标就是0所以为0,还是因为你没有操作index,所以赋值为-1在后期只要检验index是否>=0就可以轻易的知道index所传达的信息
  4. 因为函数要返回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;}}
}

②纵向连珠

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

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

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

③斜向连珠

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

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

  1. count继续记录数量
  2. 方向↘的斜线中你会发现都是[0,0] [1,1] [2,2]这样横纵一样的数字坐标,所以在方向↘的判断中只需要index记录行或列一个数字即可,这我们统一遍历每一行去判断。在方向↙的斜线中需要记录[0,2] [1,1] [2,0]这样的坐标,这样的情况下,我们还是遍历行,要记住cow的值一直为3,所以我们只需要利用cow的加减运算就可以写出想要的坐标,并赋值给index
  3. 两条斜线方向的代码分开写
  4. 如果成功下棋记得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字的博客写了很久,还是希望你能留下一个赞或者关注我一下,谢谢啦!!!

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

  1. C语言简易小游戏--三子棋

    小时候,在学校和小伙伴除了互相追逐着玩闹玩游戏以外,相信很多小伙伴们也有和朋友在课堂上拿一张纸玩井字棋的美好回忆.(如下图酱紫啦~) 那今天呢,我们就用C语言制作一款简易的井字棋来练习一下对代码的感觉 ...

  2. 用c语言实现小游戏三子棋

    小游戏之三子棋 小游戏之三子棋 三子棋的基本玩法 游戏制作思路 制作步骤 1.菜单 2.初始化棋盘 3.打印棋盘 4.玩家下棋 5.电脑下棋 6.判断胜利 完整代码 ​​​​​​​小结 三子棋的基本玩 ...

  3. C语言趣味小游戏——三子棋

    全篇无任何废话,本文的解释大多数都在代码段中,所以一定要看代码,边看边学边理解. 这只是初学者入门的一个小游戏,不难懂,没有什么复杂的内容 可以先学习一下比三子棋还简单的猜数字小游戏 C语言趣味小游戏 ...

  4. C语言第十课:编写井字棋游戏(综合练习1)

    目录 前言: 一.文件建立: 1.头文件game.h: 2.函数定义文件game.c: 3.工程测试文件test.c: 二.编写井字棋游戏: 1.程序整体执行思路: 2.menu菜单函数实现: 3.g ...

  5. 【C语言从入门到入土】——“井字棋”

    目录 1.游戏规则 2.设计思路 1.棋盘的初始化 2.打印棋盘 3.玩家操作 4.电脑下棋 6.输赢判断 7.完整代码展示 1.游戏规则 井字棋又叫三子棋,作为童年经典小游戏,相信各位无论是在上课摸 ...

  6. 组合游戏系列5: 井字棋、五子棋AlphaGo Zero 算法实战

    来源 | MyEncyclopedia 上一篇我们从原理层面解析了AlphaGo Zero如何改进MCTS算法,通过不断自我对弈,最终实现从零棋力开始训练直至能够打败任何高手.在本篇中,我们在已有的N ...

  7. (超详解)C语言实现小游戏三子棋

    目录 一,实现目标 二,整体思路 1.创立三个文件:text.c(实现测试的逻辑)                                                            ...

  8. 【c语言五子棋】自定义类型五子棋/井字棋:胜负判断

    一.算法思路 由于五子棋规则比较简单,我们可以胜负判断分为以下几个方面分别判断: 1:横向判断 2,竖向判断 3.斜向判断(从左下到右上) 4.斜向判断(从左上到右下) 二.算法原理(算法来源) 参考 ...

  9. qt制作棋牌游戏之XO棋(井字棋)

    原理很简单,就是点击鼠标进行下子,电脑与你进行博弈 没事做做还是挺有意思的. 源码在下面: mylabel.h #ifndef MYLABEL_H #define MYLABEL_H#include ...

最新文章

  1. linux pycharm 数字键盘失效
  2. Linux shell的和||--转载
  3. Oracle 生成随机密码
  4. 手机MODEM 开发(30)--- VoLTE无线功能
  5. _CrtCheckMemory
  6. php 易宝支付,网站接入易宝支付遇上的问题
  7. 按网络管理模式 计算机网络可分为,计算机网络应用 按网络管理模式分类
  8. 基于SSM的Java图书管理系统
  9. Axure RP7.0学习记录
  10. 一款Java开源的Springboot 即时通讯 IM 聊天系统,附源码下载地址!
  11. Linux双独立显卡SLI,完美的解决方案:双显卡不需要使用双水冷Tt提供SLi冷却解决方案...
  12. 2021年危险化学品经营单位主要负责人考试及危险化学品经营单位主要负责人找解析
  13. 打印100以内的质数
  14. 生活污水处理厂工程脱水车间设计、果汁饮料厂工艺流程及车间平面布置CAD设计、水处理车间工艺图、氯乙烯分离车间平面布置图、乳品车间设备布置图、核桃乳饮料厂工艺流程及车间平面布置CAD设计……
  15. 基于VTK的PACS系统的开发 心路历程2
  16. 机器学习与统计建模 —— 归一化和标准化
  17. zabbix邮件报警发送至qq邮箱
  18. KEIL中左侧project 的文件编译后没有显示.h文件
  19. 安卓期末作品简单_小东说:写ios和安卓系统的人到底有多牛?
  20. 封装网络请求 - iOS网络篇

热门文章

  1. 想象未来人工智能的发展,人工智能构建未来世界
  2. php parse url 反向,php parse_url()函数
  3. java列出100以内的素数_Java判断100以内的素数
  4. 高级软考项目管理课第一章后习题
  5. windows下载bypass火绒
  6. FLutter 官方推荐的二个动画插件lottie和Rive(flare)动画
  7. 汇编语言乘法和除法指令
  8. android自定义抽奖转盘
  9. 疫情危机下,欧洲风投青睐的初创趋势
  10. Pro是专业版,Lite是精简版,Plus是增强版