三子棋游戏(利用基础语法实现人工智能)

通过基础的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的使用均需要包含头文件<windows.h>

当完成了这部分的内容,整个游戏的大致框架就完成了,将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。

以此类推,离坐标越远权重越低,每次电脑下棋前,遍历一遍棋盘,选择权重最高的方法下,如果有权重相同的情况,再选择周围空位最多的坐标。

除以上两点之外,其实还有更多细节之处可以优化,有兴趣的小伙伴可以自己尝试一下。

整体代码展示

这里为了方便大家的测试与改进,我把全部的代码放在下面。如果有不足之处,还请大家多多指点。

text.c

//三子棋
#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;
}

game.h

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>


#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);

game.c

#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';
}

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>