通过基础的C语言知识,来实现三子棋的人工智能,让电脑可以实现自由落子、堵棋以及赢棋的功能。整体代码650行左右,适合刚接触编程的同学,在实现程序的同时,增加自己的编程知识,并且锻炼用逻辑思维思考的能力。
目录
整体结构组成
大致框架
游戏部分
生成棋盘
定义数组
初始化
打印棋盘
玩家下棋
电脑下棋(智能下棋)
自由落子
堵棋
赢棋
判断输赢
判定横三连
判断竖三连
判断斜三连(左下右上)
判断斜三连(左下右上)
判断平局
测试
改进
整体代码展示
text.c
game.h
game.c
游戏代码主要由三部分组成,text.c存放主函数,game.c存放游戏实现部分的函数,game.h声明在game.c中定义的函数。
text.c(菜单、主函数)
game.c(游戏内容)
game.h(声明游戏内容中所包含的函数)
为了避免有输入错误的情况,以及玩完一遍游戏还可以玩第二遍,这里整个游戏框架用do while循环,保证整个流程至少可以运行一次。
#include"game.h"
void menu()
{
printf("******************\n");
printf("***** 1.paly *****\n");
printf("***** 0.exit *****\n");
printf("******************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择(1/0):>");
scanf("%d", &input);
} while (input);
return 0;
}
随后,针对输入的值不同,来判定是进行游戏还是退出游戏,或是输入错误。这里可以用switch 选择语句完成该部分的判断。
switch (input)
{
case 0:
printf("退出游戏\n");
break;
case 1:
printf("开始游戏\n");
game();//游戏
Sleep(500);//等500毫秒
system("cls");//清空屏幕
break;
default:
printf("输入错误,请重新输入\n");
}
当我们完成一次游戏后,为了方便进行下一次玩,用system("cls")将我们的屏幕清空,但cls清空的速度非常快,这里用Sleep(500)让系统休眠500毫秒,方便我们看到游戏的结果。system和Sleep的使用均需要包含头文件
当完成了这部分的内容,整个游戏的大致框架就完成了,将game()先打上注释,这里测试一下能否正常运行。
游戏的部分整体流程如下:
三子棋的棋盘一共三行三列,共有九个空可以下棋,这用一个三行三列的数组来存放每个空的数据。
为了代码的灵活性,这里不要把行和列的值写死,用define定义的常量来写,方便以后代码的更新。
//game.h
#define ROW 3
#define COL 3
//text.c
#include"game.h"
void game()
{
char board[ROW][COL] = { 0 };
}
定义完数组后,为了更加贴合棋盘的样式,我们需要对数组进行初始化,将空格赋给每个元素。这里定义一个初始化棋盘的函数InitBoard,将数组,行和列的参数传给函数,这个函数的部分拿到game.c中完成,并且在game.h中声明一下。
//text.c
#include"game2.h"
void game()
{
char board[ROW][COL] = { 0 };
InitBoard(board, ROW, COL);//初始化
}
//game.h
//初始化部分
void InitBoard(char board[ROW][COL], int row, int col);
//game.c
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = ' ';//将空格赋给每个元素
}
}
}
打印棋盘时注意也不要把棋盘写死,这里我们定义DisPaly_Board函数,用for循环来打印棋盘,打印“ | | | ”时,将“ |”设定为一组,遍历到最后一列时,只打印“ ”;同理,打印“---|---|---”时,将“---|”设定为一组进行打印,遍历到最后一列时,只打印“---”,遍历到最后一行时都不打印。
//text.c
void game()
{
char board[ROW][COL] = {0};
//初始化
InitBoard(board, ROW, COL);
//打印棋盘
DisPaly_Board(board, ROW, COL);
}
//game.h
void DisPaly_Board(char board[ROW][COL], int row, int col);
//game.c
void DisPaly_Board(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
if (i < row - 1)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
}
}
}
这样写的好处是:当我们改变define定义的常量时,棋盘的大小也会随之改变。
例如将ROW和COL都改成10:
在玩家下棋这部分中,我们定义玩家通过输入坐标来进行下棋。需要注意的是:数组所定义的下标是从0开始的,而玩家认知的坐标是从1开始的(例如:玩家想输入第一行第一列的坐标是,输入的是(1,1),而不是(0,0)),这里需要将玩家输入的横、纵坐标减1,转换成数组的坐标。
在玩家输入完坐标后,我们还需要判断该坐标是否合法,如果玩家输入的坐标超限,或者该坐标已经下过棋了,那么需要重新输入坐标。
这里玩家下的棋,用 * 表示。定义PlayerMove用来实现玩家下棋的这部分。
//text.c
void game()
{
int ret = 0;
char board[ROW][COL] = {0};
//初始化
InitBoard(board, ROW, COL);
//打印棋盘
DisPaly_Board(board, ROW, COL);
while (1)
{
//玩家下棋
PlayerMove(board, ROW, COL);
//打印棋盘
DisPaly_Board(board, ROW, COL);
}
}
//game.h
void PlayerMove(char board[ROW][COL], int row, int col);
//game.c
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("玩家下棋:>\n");
scanf("%d%d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
}
printf("坐标非法,请重新输入\n");
}
}
当玩家下完棋后,我们需要再将棋盘打印一下。并且下棋是一个有来有回的过程,这里用一个循环将整个下棋的过程包含起来,直到分出胜负或平局跳出循环。
电脑下棋分为三个部分:赢棋、堵棋、自由落子。
这里的优先级是赢棋>堵棋>自由落子,在写代码的时候要遵循:能赢则赢,不能赢则堵,以上情况都不符合的时候下新棋的原则。(自由落子不等于下废棋,不能彻底随机)
定义三个函数:
赢棋函数:TryWin(如果函数执行并落子,返回0;否则返回1)
堵棋函数:Cut(如果函数执行并落子,返回0;否则返回1)
自由落子函数:ComputerMove(构建整体框架,根据前两个函数的返回值来判断是否落子)
需要注意的是,在下新棋的时候,为了不让该棋变成废棋,需要判定电脑落子附近的8个单元格内至少有一个棋子是玩家的落子。
具体方法如下:先让电脑随机生成一个坐标,然后判定该坐标是否同时满足条件1和条件2,如果满足就落子,如果不满足则重新生成新的坐标,所以这里需要用到while循环,根据优先度的排序,while循环的表达式则是Cut函数的返回值。
条件1:该位置是空地。
条件2:(x,y)相邻的8个元素中,至少有一个格子有玩家的落子。(条件2是为了防止电脑随机下棋下到角落里,该棋便成了废棋)
ComputerMove代码如下:
//game.h
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//电脑尝试三连
int TryWin(char board[ROW][COL], int row, int col);
//堵棋
int Cut(char board[ROW][COL], int row, int col);
//text.c
srand((unsigned int)time(NULL));//使用rand()需要在主函数中用srand修饰,提供随机标准。
//game.c
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:>\n");
if (TryWin(board, ROW, COL)==1)//先尝试三连
{
while (Cut(board, ROW, COL))//不能三连尝试堵棋
{
int x = rand() % row;
int y = rand() % col;
//控制范围,不要下的太偏了,新棋下的位置要在玩家落子的位置附近,以免变成废棋
if (board[x][y] == ' ' && (board[x - 1][y - 1] == '*' || board[x - 1][y] == '*' || board[x - 1][y - 1] == '*' || board[x][y - 1] == '*' || board[x][y + 1] == '*' || board[x + 1][y - 1] == '*' || board[x + 1][y] == '*' || board[x + 1][y + 1] == '*'))
{
board[x][y] = '#';
break;
}
}
}
}
堵棋的优先度在下新棋之上,在赢棋的优先度之下。
堵棋共有四种堵法:横着堵、竖着堵、左斜堵、右斜堵。
将堵棋的大致框架写出,并且为了代码的适用性,这里的代码也不要写死,既要适用三行三列的棋盘,也要同样适用于更大的棋盘。(五行五列、十行十列等等)
当棋盘中不需要堵棋时,返回1;而只要当其中一种情况成立,电脑便落子堵棋,返回0。
堵棋框架:
//game.c
//堵棋
int Cut(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//堵横着
//堵竖着的
//右斜(左上右下)
//左斜(左下右上)
}
}
return 1;
}
横着堵:
横着堵有四种情况:XXO、OXX,XOX,以及OXXO(在大棋盘中)。
这里采用for循环遍历来寻找棋盘中是否存在以上四种情况,针对不同的情况,坐标的搜索范围也不一致,这里注意数组的访问不要超出限制。
//堵横着
if (j < col - 2)//XOX型
{
if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '*')
{
board[i][j + 1] = '#';
return 0;
}
}
if (j < col - 1)//XXO或OXX型
{
if (board[i][j] == board[i][j + 1] && board[i][j] == '*')
{
if (j < col - 2 && board[i][j + 2] == ' ' && board[i][j - 1] == ' ')//两种情况都符合
{
int ch = rand() % 2;//两种情况都符合时,随机下
switch (ch)
{
case 0:
board[i][j + 2] = '#';
return 0;
case 1:
board[i][j - 1] = '#';
return 0;
}
}
//只符合其中一种情况
if (j < col - 2 && board[i][j + 2] == ' ')//XXO
{
board[i][j + 2] = '#';
return 0;
}
if (board[i][j - 1] == ' ')//OXX
{
board[i][j - 1] = '#';
return 0;
}
}
}
竖着堵:
竖着堵与横着堵的情况一致,也是四种情况,将横纵坐标的要求对调一下即可。
//堵竖着的
//X
//0
//X型
if (i < row - 2)
{
if (board[i][j] == board[i + 2][j] && board[i + 1][j] == ' ' && board[i][j] == '#')
{
board[i + 1][j] = '#';
return 0;
}
}
//X O
//X X
//O型 或 X型
if (i < row - 1)
{
if (board[i][j] == board[i + 1][j] && board[i][j] == '#')
{
if (i < row - 2 && board[i + 2][j] == ' ' && board[i - 1][j] == ' ')//符合两种情况随机堵
{
int ch = rand() % 2;
switch (ch)
{
case 0:
board[i + 2][j] = '#';
return 0;
case 1:
board[i - 1][j] = '#';
return 0;
}
}
if (i < row - 2 && board[i + 2][j] == ' ')//只有下方可以堵
{
board[i + 2][j] = '#';
return 0;
}
if (board[i - 1][j] == ' ')//只有上方可以堵
{
board[i - 1][j] = '#';
return 0;
}
}
}
左斜堵:
左斜堵的情况也是四种,但是需要注意的是,这里符合条件的横纵坐标范围与右斜有很大区别。
//堵斜着(左下右上)
// X
// 0
//X 型
if (i < row - 2 && j > 1)
{
if (board[i][j] == board[i + 2][j - 2] && board[i + 1][j - 1] == ' ' && board[i][j] == '#')
{
board[i + 1][j - 1] = '#';
return 0;
}
}
// X O
// X X
//O 型 或 X 型
if (i < row - 1 && j > 0)
{
if (board[i][j] == board[i + 1][j - 1] && board[i][j] == '#')
{
if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ' && board[i - 1][j + 1] == ' ')//符合两种情况随机堵
{
int ch = rand() % 2;
switch (ch)
{
case 0:
board[i + 2][j - 2] = '#';
return 0;
case 1:
board[i - 1][j + 1] = '#';
return 0;
}
}
if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ')//只有下方可以堵
{
board[i + 2][j - 2] = '#';
return 0;
}
if (board[i - 1][j + 1] == ' ')//只有上方可以堵
{
board[i - 1][j + 1] = '#';
return 0;
}
}
}
右斜堵:
//堵斜着(左上右下)
//X
// 0
// X型
if (i < row - 2 && j < col - 2)
{
if (board[i][j] == board[i + 2][j + 2] && board[i + 1][j + 1] == ' ' && board[i][j] == '#')
{
board[i + 1][j + 1] = '#';
return 0;
}
}
// X O
// X X
// O型 或 X型
if (i < row - 1 && j < col - 1)
{
if (board[i][j] == board[i + 1][j + 1] && board[i][j] == '#')
{
if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ' && board[i - 1][j - 1] == ' ')//符合两种情况随机堵
{
int ch = rand() % 2;
switch (ch)
{
case 0:
board[i + 2][j + 2] = '#';
return 0;
case 1:
board[i - 1][j - 1] = '#';
return 0;
}
}
if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ')//只有下方可以堵
{
board[i + 2][j + 2] = '#';
return 0;
}
if (board[i - 1][j - 1] == ' ')//只有上方可以堵
{
board[i - 1][j - 1] = '#';
return 0;
}
}
}
当我们把上述共十六种的情况都罗列出来后,电脑便会逢棋必堵,并且堵棋函数中所运用到的知识和方法都是C语言初阶的内容,并没有很高深的内容。
为了让我们的电脑更进一步,可以抓住玩家露出的破绽,我们再赋予电脑一个赢棋的函数。
而赢棋的优先级也是最高的,毕竟如果游戏都赢了,那就没有堵棋和下棋的事儿了。
赢棋的函数其实和堵棋的函数十分类似,可以说是99.9%都是一致的。
想象一下:在判定是否需要堵棋时,我们其实判定的是玩家是否会赢,如果出现玩家赢的可能,那么我们就要执行堵棋。我们只需要改变一下条件,改成判定电脑是否会赢,如果出现电脑赢的可能,那么就执行赢棋。
例如:
//堵棋
if (j < col - 2)//XOX型
{
if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '*')
{
board[i][j + 1] = '#';
return 0;
}
}
//赢棋
if (j < col - 2)//XOX型
{
if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '#')
{
board[i][j + 1] = '#';
return 0;
}
}
将堵棋的代码完全拷贝下来,并将表达式中的*改成#即可。
三子棋的输赢判定方式很简单,横、竖、斜三棋相连,为了不把代码写死,这里将分别对横、竖、斜(左下右上)、斜(右下左上)这四种进行分别判定。
将框架写好,随后在for循环的内部加入判定部分即可。
//game.c
int Judgment(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//判定部分
}
}
}
因为是三子棋,所以靠近边界的两行没有判断的价值,必然不会有第三个棋与之相连。
当判定为三个相连且不是空格时,只需返回其中一个元素即可,当接收函数返回值时,发现返回值是*,是玩家赢;返回值是#,则是电脑赢。
横着三个相等且不等于空格,将第一个元素返回。
if (i < row && j < col - 2)//三子棋,所以靠边界的两行没必要判断
{
if (board[i][j] == board[i][j + 1] && board[i][j + 1] == board[i][j + 2] && board[i][j] && board[i][j] != ' ')
{
return board[i][j];
}
}
竖着三个相等且不等于空格,将第一个元素返回。
if (i < row - 2 && j < col)
{
if (board[i][j] == board[i + 1][j] && board[i + 1][j] == board[i + 2][j] && board[i][j] != ' ')
{
return board[i][j];
}
}
斜三连的情况稍微复杂一点,正斜和反斜针对i和j的取值范围不同,并且坐标的变换也不一样。
if (i < row-2 && j > 1)
{
if (board[i][j] == board[i + 1][j - 1] && board[i + 1][j - 1] == board[i + 2][j - 2] && board[i][j] != ' ')
{
return board[i][j];
}
}
if (i < row-2 && j < col-2 )
{
if (board[i][j] == board[i + 1][j + 1] && board[i + 1][j + 1] == board[i + 2][j + 2] && board[i][j] != ' ')
{
return board[i][j];
}
}
除了输赢,还会有平局的情况发生,判断平局的方法很简单,遍历一遍整个数组,当发现没有‘ ’时,即平局了。这里如果平局则返回q,没有平局则返回c,判断平局的部分要放在判断输赢的后面。
//判断平局
int IsFull(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
if (IsFull(board, row, col))
{
return 'q';
}
return 'c';
当完成了所有代码后,这里我们用5×5的棋盘可以更好的测试一下。
当玩家先手下棋后,电脑在堵截玩家这方面还是咬的很死的,现在电脑有一个三连的机会,我们卖一个破绽,看看电脑能否抓住。
电脑不负众望抓住了机会,踏上了人工智能面向世界的第一步。我们这里设定的是玩家先手,玩家的优势稍微大一点。
如果想要设置电脑先手的话,还需要对电脑下棋部分的代码再加一点东西:即当整个棋盘是空的时候,电脑在中心区域随机下棋。(我们之前的代码一直是电脑负责对玩家的落子进行围追堵截,没有先手的情况)
其实整个代码还是有很大的优化空间的:
1、受到三子棋的限制,在3×3的棋盘太过无趣,但当三子棋在大棋盘中进行游玩时,先手一方必赢。
这里可以将三子棋改成五子棋,本质框架不变,但是需要堵棋的情况会比三子棋要多出一些。
2、电脑在堵棋时,如果是10×10的大棋盘,那么会出现可以两头堵的情况,在最初设定时,我们设置的是两头随机堵,但是现实中这样往往会错失良机。电脑的下棋也是如此,我们只是设置了一个随机坐标,保证在玩家落子的附近,但不能保证该坐标就是最优的选择。那么如何改进呢?
我们可以对整个棋盘设置权重P
当该坐标周围一圈附近8个元素中每多一颗电脑的棋子,P+10;
附近8个元素中每多一颗玩家的棋子,P+5;
该坐标周围第二圈附近16个元素中每多一颗电脑的棋子,P+5;
该坐标周围第二圈附近16个元素中每多一颗玩家的棋子,P+2。
以此类推,离坐标越远权重越低,每次电脑下棋前,遍历一遍棋盘,选择权重最高的方法下,如果有权重相同的情况,再选择周围空位最多的坐标。
除以上两点之外,其实还有更多细节之处可以优化,有兴趣的小伙伴可以自己尝试一下。
这里为了方便大家的测试与改进,我把全部的代码放在下面。如果有不足之处,还请大家多多指点。
//三子棋
#include"game.h"
void menu()
{
printf("******************\n");
printf("***** 1.paly *****\n");
printf("***** 0.exit *****\n");
printf("******************\n");
}
void game()
{
int ret = 0;
char board[ROW][COL] = {0};
//初始化
InitBoard(board, ROW, COL);
//打印棋盘
DisPaly_Board(board, ROW, COL);
while (1)
{
//玩家下棋
PlayerMove(board, ROW, COL);
//打印棋盘
DisPaly_Board(board, ROW, COL);
//判断输赢
ret = Judgment(board, ROW, COL);
if (ret != 'c')
{
break;
}
//电脑下棋
ComputerMove(board, ROW, COL);
//打印棋盘
DisPaly_Board(board, ROW, COL);
//判断输赢
ret = Judgment(board, ROW, COL);
if (ret != 'c')
{
break;
}
}
if (ret == '*')
{
printf("玩家赢了\n");
}
else if (ret == '#')
{
printf("电脑赢了\n");
}
else
printf("平局\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择(1/0):>");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出游戏\n");
break;
case 1:
printf("开始游戏\n");
game();//游戏
Sleep(500);
system("cls");//清空屏幕
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
#include
#include
#include
#include
#define ROW 5//在测试的时候,建议将行和列设置稍微大一点
#define COL 5
//初始化
void InitBoard(char board[ROW][COL], int row,int col);
//打印棋盘
void DisPaly_Board(char board[ROW][COL], int row, int col);
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
//电脑尝试三连
int TryWin(char board[ROW][COL], int row, int col);
//堵棋
int Cut(char board[ROW][COL], int row, int col);
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//判断输赢
int Judgment(char board[ROW][COL], int row, int col);
#include"game.h"
//初始化
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
//打印棋盘
void DisPaly_Board(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
if (i < row - 1)
{
int j = 0;
for (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;
while (1)
{
printf("玩家下棋:>\n");
scanf("%d%d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
}
printf("坐标非法,请重新输入\n");
}
}
//电脑下棋(堵棋)
int Cut(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//堵横着
if (j < col - 2)//XOX型
{
if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '*')
{
board[i][j + 1] = '#';
return 0;
}
}
if (j < col - 1)//XXO或OXX型
{
if (board[i][j] == board[i][j + 1] && board[i][j] == '*')
{
if (j < col - 2 && board[i][j + 2] == ' ' && board[i][j - 1] == ' ')//两种情况都符合
{
int ch = rand() % 2;//两种情况都符合时,随机下
switch (ch)
{
case 0:
board[i][j + 2] = '#';
return 0;
case 1:
board[i][j - 1] = '#';
return 0;
}
}
//只符合其中一种情况
if (j < col - 2 && board[i][j + 2] == ' ')//XXO
{
board[i][j + 2] = '#';
return 0;
}
if (board[i][j - 1] == ' ')//OXX
{
board[i][j - 1] = '#';
return 0;
}
}
}
//堵竖着的
//X
//0
//X型
if (i < row - 2)
{
if (board[i][j] == board[i + 2][j] && board[i + 1][j] == ' ' && board[i][j] == '*')
{
board[i + 1][j] = '#';
return 0;
}
}
//X O
//X X
//O型 或 X型
if (i < row - 1)
{
if (board[i][j] == board[i + 1][j] && board[i][j] == '*')
{
if (i < row - 2 && board[i + 2][j] == ' ' && board[i - 1][j] == ' ')//符合两种情况随机堵
{
int ch = rand() % 2;
switch (ch)
{
case 0:
board[i + 2][j] = '#';
return 0;
case 1:
board[i - 1][j] = '#';
return 0;
}
}
if (i < row - 2 && board[i + 2][j] == ' ')//只有下方可以堵
{
board[i + 2][j] = '#';
return 0;
}
if (board[i - 1][j] == ' ')//只有上方可以堵
{
board[i - 1][j] = '#';
return 0;
}
}
}
//堵斜着(左上右下)
//X
// 0
// X型
if (i < row - 2 && j < col - 2)
{
if (board[i][j] == board[i + 2][j + 2] && board[i + 1][j + 1] == ' ' && board[i][j] == '*')
{
board[i + 1][j + 1] = '#';
return 0;
}
}
// X O
// X X
// O型 或 X型
if (i < row - 1 && j < col - 1)
{
if (board[i][j] == board[i + 1][j + 1] && board[i][j] == '*')
{
if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ' && board[i - 1][j - 1] == ' ')//符合两种情况随机堵
{
int ch = rand() % 2;
switch (ch)
{
case 0:
board[i + 2][j + 2] = '#';
return 0;
case 1:
board[i - 1][j - 1] = '#';
return 0;
}
}
if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ')//只有下方可以堵
{
board[i + 2][j + 2] = '#';
return 0;
}
if (board[i - 1][j - 1] == ' ')//只有上方可以堵
{
board[i - 1][j - 1] = '#';
return 0;
}
}
}
//堵斜着(左下右上)
// X
// 0
//X 型
if (i < row - 2 && j > 1)
{
if (board[i][j] == board[i + 2][j - 2] && board[i + 1][j - 1] == ' ' && board[i][j] == '*')
{
board[i + 1][j - 1] = '#';
return 0;
}
}
// X O
// X X
//O 型 或 X 型
if (i < row - 1 && j > 0)
{
if (board[i][j] == board[i + 1][j - 1] && board[i][j] == '*')
{
if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ' && board[i - 1][j + 1] == ' ')//符合两种情况随机堵
{
int ch = rand() % 2;
switch (ch)
{
case 0:
board[i + 2][j - 2] = '#';
return 0;
case 1:
board[i - 1][j + 1] = '#';
return 0;
}
}
if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ')//只有下方可以堵
{
board[i + 2][j - 2] = '#';
return 0;
}
if (board[i - 1][j + 1] == ' ')//只有上方可以堵
{
board[i - 1][j + 1] = '#';
return 0;
}
}
}
}
}
return 1;
}
//电脑下棋(尝试三连)
int TryWin(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//堵横着
if (j < col - 2)
{
if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '#')//XOX型
{
board[i][j + 1] = '#';
return 0;
}
}
if (j < col - 1)//XXO或OXX型
{
if (board[i][j] == board[i][j + 1] && board[i][j] == '#')
{
if (j < col - 2 && board[i][j + 2] == ' ' && board[i][j - 1] == ' ')//两种情况都符合
{
int ch = rand() % 2;
switch (ch)
{
case 0:
board[i][j + 2] = '#';
return 0;
case 1:
board[i][j - 1] = '#';
return 0;
}
}
if (j < col - 2 && board[i][j + 2] == ' ')
{
board[i][j + 2] = '#';
return 0;
}
if (board[i][j - 1] == ' ')
{
board[i][j - 1] = '#';
return 0;
}
}
}
//堵竖着的
//X
//0
//X型
if (i < row - 2)
{
if (board[i][j] == board[i + 2][j] && board[i + 1][j] == ' ' && board[i][j] == '#')
{
board[i + 1][j] = '#';
return 0;
}
}
//X O
//X X
//O型 或 X型
if (i < row - 1)
{
if (board[i][j] == board[i + 1][j] && board[i][j] == '#')
{
if (i < row - 2 && board[i + 2][j] == ' ' && board[i - 1][j] == ' ')//符合两种情况随机堵
{
int ch = rand() % 2;
switch (ch)
{
case 0:
board[i + 2][j] = '#';
return 0;
case 1:
board[i - 1][j] = '#';
return 0;
}
}
if (i < row - 2 && board[i + 2][j] == ' ')//只有下方可以堵
{
board[i + 2][j] = '#';
return 0;
}
if (board[i - 1][j] == ' ')//只有上方可以堵
{
board[i - 1][j] = '#';
return 0;
}
}
}
//堵斜着(左上右下)
//X
// 0
// X型
if (i < row - 2 && j < col - 2)
{
if (board[i][j] == board[i + 2][j + 2] && board[i + 1][j + 1] == ' ' && board[i][j] == '#')
{
board[i + 1][j + 1] = '#';
return 0;
}
}
// X O
// X X
// O型 或 X型
if (i < row - 1 && j < col - 1)
{
if (board[i][j] == board[i + 1][j + 1] && board[i][j] == '#')
{
if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ' && board[i - 1][j - 1] == ' ')//符合两种情况随机堵
{
int ch = rand() % 2;
switch (ch)
{
case 0:
board[i + 2][j + 2] = '#';
return 0;
case 1:
board[i - 1][j - 1] = '#';
return 0;
}
}
if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ')//只有下方可以堵
{
board[i + 2][j + 2] = '#';
return 0;
}
if (board[i - 1][j - 1] == ' ')//只有上方可以堵
{
board[i - 1][j - 1] = '#';
return 0;
}
}
}
//堵斜着(左下右上)
// X
// 0
//X 型
if (i < row - 2 && j > 1)
{
if (board[i][j] == board[i + 2][j - 2] && board[i + 1][j - 1] == ' ' && board[i][j] == '#')
{
board[i + 1][j - 1] = '#';
return 0;
}
}
// X O
// X X
//O 型 或 X 型
if (i < row - 1 && j > 0)
{
if (board[i][j] == board[i + 1][j - 1] && board[i][j] == '#')
{
if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ' && board[i - 1][j + 1] == ' ')//符合两种情况随机堵
{
int ch = rand() % 2;
switch (ch)
{
case 0:
board[i + 2][j - 2] = '#';
return 0;
case 1:
board[i - 1][j + 1] = '#';
return 0;
}
}
if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ')//只有下方可以堵
{
board[i + 2][j - 2] = '#';
return 0;
}
if (board[i - 1][j + 1] == ' ')//只有上方可以堵
{
board[i - 1][j + 1] = '#';
return 0;
}
}
}
}
}
return 1;
}
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:>\n");
if (TryWin(board, ROW, COL) == 1)//先尝试三连
{
while (Cut(board, ROW, COL))//不能三连尝试堵棋
{
int x = rand() % row;
int y = rand() % col;
//控制范围,不要下的太偏了
if (board[x][y] == ' ' && (board[x - 1][y - 1] == '*' || board[x - 1][y] == '*' || board[x - 1][y - 1] == '*' || board[x][y - 1] == '*' || board[x][y + 1] == '*' || board[x + 1][y - 1] == '*' || board[x + 1][y] == '*' || board[x + 1][y + 1] == '*'))
{
board[x][y] = '#';
break;
}
}
}
}
//判断平局
int IsFull(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
//判断输赢
int Judgment(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//判断横行是否相连
if (i < row && j < col - 2)//三子棋,所以靠边界的两行没必要判断
{
if (board[i][j] == board[i][j + 1] && board[i][j + 1] == board[i][j + 2] && board[i][j] && board[i][j] != ' ')
{
return board[i][j];
}
}
//判断竖行是否相连
if (i < row - 2 && j < col)
{
if (board[i][j] == board[i + 1][j] && board[i + 1][j] == board[i + 2][j] && board[i][j] != ' ')
{
return board[i][j];
}
}
//判断斜行是否相连(左下右上)
if (i < row - 2 && j > 1)
{
if (board[i][j] == board[i + 1][j - 1] && board[i + 1][j - 1] == board[i + 2][j - 2] && board[i][j] != ' ')
{
return board[i][j];
}
}
//判断斜行是否相连(右下左上)
if (i < row - 2 && j < col - 2)
{
if (board[i][j] == board[i + 1][j + 1] && board[i + 1][j + 1] == board[i + 2][j + 2] && board[i][j] != ' ')
{
return board[i][j];
}
}
}
}
if (IsFull(board, row, col))//判断平局
{
return 'q';
}
return 'c';
}