C语言人工智能 |教你与智能AI对弈三子棋 从此不再孤单寂寞冷

我们知道 生活中 孤独寂寞的时候很多,既然我们手中已经掌握了编程的技术,那为何不利用我们的技能创建 一个智能Al来陪我们对弈下棋,让我们不再孤独呢?
于是这款 AI三子棋就因需而生了

这篇博客将手把手教会你 自己解决孤独!

1.主界面 询问是否孤独

void Main_menu() {
		printf("*************************n");
		printf("******1. 我很孤独********n");
		printf("******0. 我不孤独********n");
		printf("*************************n");
		printf("******2. 我不理解********n");
	}

 暖心AI询问是否孤独 ,来决定是否 帮助玩家派遣孤独

这里明显需要 用到选择结构 我们这里用switch语句来实现

代码:

int main() {
	srand((unsigned int)time(NULL)); 这是待会要用到的 随机数的初始化 暂时先不管
	int input; 用来储存玩家选的的序号
	do {
		Main_menu();
		printf("请选择序号:>");
		scanf("%d", &input); 存放玩家选择的序号
		switch (input) {
		case 1:   /如果你很孤独,AI就会陪伴你
			printf("n既然你很孤独,我们来下棋吧!n");
			printf("n需要看规则吗?nn"); /暖心AI的望闻问切
			printf("1. 需要,我不知道怎么下n");
			printf("2. 不需要,我是高手n");
			int tmp;/用来储存这次选择的序号
			scanf(" %d", &tmp);
			switch (tmp) {
			case 1:
				INFOR();/编写好的 信息模块,里面有游戏规则和其他信息,在文章后面我会有讲解
				break;
			case 2:
				printf("请:>n");
				game();/直接进入游戏模块
				break;
			}
			break;
		case 0: /不孤独的话就不要浪费时间了
			printf("不孤独就退出程序");
			break;
		case 2: /玩家 表示不理解 AI耐心的解释
			printf("n这是一款 能在孤独时陪你下棋的温暖AI!!!nn");
            break;
		default:
			printf("选择序号有误,请重新选择>n");

		}
	} while (input != 0);/当玩家选择0 就会退出


	return 0;
}

根据 玩家不同的选择 进入到不同的模块

当玩家选择 0,表示他并不孤独:

AI表示很无语,显然它不想浪费时间 

当玩家选择 2,表示他不理解:

AI会很耐心的像玩家解释 这是什么。

AI询问玩家 是否需要了解规则

 当玩家选择 1,表示他很孤独:

暖心AI会贴心的提出一起下棋的建议,并贴心的询问玩家是否 熟悉三子棋游戏的规则。

当玩家选择 1,他不知道规则:

这时就会进入我们编写好的 信息模块 里面有游戏规则和智能AI的说明信息

首先我们把 游戏规则和AI信息等需要说明的内容都封装到一个源文件里,在把函数的定义写到一个头文件

源文件内容:

#include"menu.h"/引上头文件

void Infor_menu() {
	printf("1.三子棋游戏规则n");
	printf("2.游戏简介n");
	printf("0.都懂了(EXIT)n");
}
void rules() {
	
	printf("1. 下棋走子 需要输入棋盘坐标,输入时用空格隔开n");
	printf("如 想在棋盘第一行第一列走子:应输入 "1 1" ; 三子一线即胜利:>n");
	printf("n");
}
void infor() {
	printf("该游戏的对手是凝聚了作者 目前所有的编程智慧结晶的AI副本n"); 
	printf("原本难度极大,为了派遣你的孤独和保护你的心理健康故意放水n若是不放水,难免日后留下难以消除的心理阴影 :>n");
	printf("n");

}

头文件内容:

#pragma once
#include<stdio.h>

/主菜单
void Main_menu();
/信息菜单
void Infor_menu();
/规则菜单
void rules();
/游戏简介
void infor();

然后再信息模块的函数中 根据玩家的选择 调用对应的函数,玩家想得到的信息就会被打印出来

信息模块代码

void INFOR() {
	int input = 1;

	while (input) {
		Infor_menu();/提示玩家选择对应的序号
		printf("请选择序号:");
		scanf("%d", &input);
		printf("n");
		switch (input) {
		case 1:rules();/查看规则
			break;
		case 2:infor();/AI信息
			break;
		case 0:
			printf("都懂了就来下棋吧!n");/进入游戏
			game();
			break;
		default:
			printf("输入无效,请重新输入n");

		}
	}
}

因为是循环体 所有玩家可以 依次查看不同的信息 ,最后进入游戏,也就是调用game函数。

开始下棋,基情对弈

1.游戏主体

下棋是怎么实现的呢?

不多说,先浏览一下代码,我会再把每一部分拿出来慢慢说明:

void game() {
	/储存数据 二维数组
	char Board[ROW][COL];
	/初始化棋盘 - 初始化为空格
	InitBoard(Board,ROW,COL);
	/接受游戏状态信息
    char A ;
	/打印棋盘 — 打印数组内容
	DisplayBoard(Board, ROW, COL);
    进入游戏 你一步 我一步 直到游戏结束
	while(1) {
        /玩家走- 实现赋值的函数
		Player_Move(Board, ROW, COL);
        /走完打印棋盘
        DisplayBoard(Board, ROW, COL);
        /判断游戏状态
		 A = Is_win(Board, ROW, COL);/判断游戏状态的函数
		if (A != 'C')/如果棋盘未满 还没有分出胜负就会返回字符c
			break;/如果不是c就结束游戏
        /AI走
		Computer_Move(Board, ROW, COL);
		/AI思考很快 为了不让玩家自卑故意等一下再走棋
        Sleep(1000);
        /走完打印棋盘
		DisplayBoard(Board, ROW, COL);
        /判断游戏状态
		A = Is_win(Board, ROW, COL);
		if (A != 'C')
			break;
	}

    当游戏结束 查看是平局了还是一方赢了
    /判断游戏状态的函数会根据游戏的状态返回不同的值
	switch (A) {/打印出对应的结果后,返回到主界面 询问是否还是孤独 
	case 'P':
		Sleep(1000);
		printf("平局了,再来一把吧!n");
		break;
	case'X':
		Sleep(1000); 
		printf("你赢了,好厉害!n");
		break;
	case'O':
		Sleep(1000); 
		printf("电脑赢了,笨蛋!n");
		break;
	}
}

很清楚的是,我们还是通过 把游戏需要用到的动作封装好依次调用,这样就能很流畅的进行玩家和智能AI的对弈

2.功能封装

 所以我们依旧是把游戏里的函数定义放到一个源文件,函数声明放到一个头文件

头文件代码:

#pragma once
/游戏里需要的库函数
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
#define ROW 3/定义棋盘行数
#define COL 3/定义棋盘列数
/初始化棋盘的函数
void InitBoard(char Board[ROW][COL], int row, int col);
/打印棋盘的函数
void DisplayBoard(char Board[ROW][COL], int row, int col);
/玩家走- 实现赋值的函数
void Player_Move(char Board[ROW][COL], int row ,int col);
/电脑走- 限制随机 赋值的函数
void Computer_Move(char Board[ROW][COL], int row, int col);
/判断是否需要结束游戏
char Is_win(char Board[ROW][COL], int row, int col);
/判断棋盘是否满了的函数
int Is_full(char Board[ROW][COL], int row, int col);

源文件代码:


#include"game.h" /引上头文件 
完成函数的定义
初始化棋盘 先将数组的元素都设为0
void InitBoard(char Board[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++)
			Board[i][j] = ' ';
	}
}
打印函数 
void DisplayBoard(char Board[ROW][COL], int row, int col) {
	int r;
	int c;
	for (r = 0; r < row; r++) {
		for (c = 0; c < col; c++) {
			printf(" % c ", Board[r][c]);
			if (c + 1 < col)
				printf("|");
		}
		printf("n");
		printf("---|---|---n");

	}

}
玩家下棋的函数
void Player_Move(char Board[ROW][COL], int row, int col) {
	printf("玩家走:>");
	int ro, co;
	while (1) {
		scanf("%d %d", &ro, &co);
		if (ro <= row && co <= col && ro > 0 && co > 0 && Board[ro - 1][co - 1] == ' ') {
			Board[ro - 1][co - 1] = 'X';
			break;
		}
		else {
			if (!(ro <= row && co <= col && ro > 0 && co > 0))
			{
				printf("输错了 重新输入:>n");
			}
			else
			{
				printf("这个点(%d,%d)已经有棋子了,不许叠罗汉n",ro,co);
			}
			DisplayBoard(Board, row, col);
		}
	}
}
人工智能AI下棋的函数
void Computer_Move(char Board[ROW][COL], int row, int col) {
	int ro, co;
	printf("AI走:>n");
	while (1) {
		ro = rand() % row;
		co = rand() % col;
		if (Board[ro][co] == ' ') {
			Board[ro][co] = 'O';
			break;
		}
	}

}
判断棋盘是否下满的函数
int Is_full(char Board[ROW][COL], int row, int col) {
	int i = row;
	int j = col;
	int flag = 1;
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			if (Board[i][j] == ' ')
				flag = 0;
		}
	}
	return flag;
}
判断输赢的函数
char Is_win(char Board[ROW][COL], int row, int col) {
	int i;
	int j;
	int flag;
	flag= 0;
	for (i = 0; i < row; i++, flag = 0) {
		
		for (j = 1; j < col; j++) {
			if (' ' == Board[i][j - 1] || ' ' == Board[i][j]) {
				continue;
			}
			if (Board[i][j] == Board[i][j-1]) {
				flag++;
				if (flag == 2)
					return Board[i][j];
			}
			
		}
		
	}

	for (j = flag = 0; j < col; j++, flag = 0) {

		for (i = 1; i < row; i++) {

			if (' ' == Board[i-1][j] || ' ' == Board[i][j]) {
				continue;
			}
			if (Board[i][j] == Board[i-1][j])
				flag++;
			if (2 == flag)
				return Board[i][j];
		}
		
	}
	
	for (i = 1,flag = 0; i < row; i++) {
		
		if ((Board[i][i] == Board[i - 1][i - 1])&&Board[i][i]!=' ') {
			flag++;
		}
		if (2==flag) {
			return Board[i][i];
		}
	}

	for (i = 1, j = 1, flag = 0; i < row&&j>=0; i++,j--) {
		//0,2 //1,1 //2,0 
		if ((Board[i][j] == Board[i - 1][j + 1]) && Board[i][j] != ' ') {
			flag++;
		}
		if (2 == flag) {
			return Board[i][j];
		}

	}
	
	if(Is_full(Board,row,col)){
		return 'P';
	}
	return 'C';
}

接下来我们就来逐一分析 这些功能是怎么实现的

(1) 初始化棋盘的函数

我们下棋就是不断地修改 一个二维数组(棋盘)的值,所以第一步当然是创建好一个 二维数组

将它的值都赋为 空格。

初始化棋盘 先将数组的元素都设为0
void InitBoard(char Board[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++)
			Board[i][j] = ' ';
	}
}

(2) 打印棋盘的函数       

接下来就是打印棋盘了,我们并不是单纯是将 数组的每一个元素打印出来,而是要耍一点花样,

打印出棋盘该有的样子:

打印棋盘的函数
void DisplayBoard(char Board[ROW][COL], int row, int col) {
	int r;
	int c;
	for (r = 0; r < row; r++) {
		for (c = 0; c < col; c++) {
			printf(" % c ", Board[r][c]);/ 打印空格 字符 空格
			if (c + 1 < col)
				printf("|");/每个格子用|符号隔开
		}
		printf("n");/换行
		printf("---|---|---n");/每行之间加一行间隔

	}
}

 (3)玩家下棋的函数

玩家下棋 的过程由三个过程组成:>

1. 玩家输入行 和 列 

2.判断玩家输入的行列是否在棋盘的范围内

3.判断这个点是否已经下过,若已经下过,便温馨给予提示,重新输入

4.若该行列合法,将玩家输入的行列都减一,就得到了该点在二维数组里的下标,把这个值赋值为字符X

5.最后一部,打印赋值后的棋盘,玩家下棋的这个过程就完毕了

玩家下棋的函数
void Player_Move(char Board[ROW][COL], int row, int col) {
	printf("玩家走:>");
	int ro, co;
	while (1) {
		scanf("%d %d", &ro, &co);
		if (ro <= row && co <= col && ro > 0 && co > 0 && Board[ro - 1][co - 1] == ' ') {
			Board[ro - 1][co - 1] = 'X';
			break;
		}
		else {
			if (!(ro <= row && co <= col && ro > 0 && co > 0))
			{
				printf("输错了 重新输入:>n");
			}
			else
			{
				printf("这个点(%d,%d)已经有棋子了,不许叠罗汉n",ro,co);
			}
			DisplayBoard(Board, row, col);成功赋值后 打印棋盘
		}
	}
}

(4)AI下棋的函数

我们创建的这个人工智能AI是来陪玩家下棋的,自然不能太聪明,若是把玩家弄自闭了,不但起不到陪伴的作用,玩家怒火中烧,结局可能会很难收场,于是乎我们通过随机数的方式,让我们的暖心AI的智能恰到好处

我们这样来实现:

1.生成两个随机值

2.分别对棋盘的最大行,列取模,这样就能直接组成一个二维数组范围内的下标

3.判断这个点是否被下过,若已经被下过,则重新生成

4.若没有被下过,赋值

5.打印

AI下棋的函数
void Computer_Move(char Board[ROW][COL], int row, int col) {
	int ro, co;/用来装AI要下的行列
	printf("AI走:>n");
	while (1) {/依旧用循环结构,成功赋值则跳出
		ro = rand() % row;/随机数对棋盘的行取模,得到一个棋盘范围的行号
		co = rand() % col;/同理对棋盘的列取模,得到一个棋盘范围的列号
		if (Board[ro][co] == ' ') {/判断是否没下过,若这个点已经下过,则重新生成
			Board[ro][co] = 'O';/赋值
			break;跳出
		}
	}
}

有一点要注意的是,为了避免每一次下出现一样的随机数,我们在main函数的第一句,用时间戳来初始化我们的随机数生成,使每一次产生的随机数都不一样:

int main() {
	srand((unsigned int)time(NULL)); 

(5)判断棋盘是否下满的函数

我们知道三子棋有三种结果:

1.玩家赢

2.AI赢

3.棋盘下满了,都没赢,也就是平局的结果

所以我们等会儿判断游戏输赢的函数中会调用这个函数来看是平局还是没下完,要接着对弈。

判断棋盘是否下满的函数
int Is_full(char Board[ROW][COL], int row, int col) {
	int i = row;
	int j = col;
	int flag = 1;
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			if (Board[i][j] == ' ')
				flag = 0;
		}
	}
	return flag;
}

我们把每个点都遍历一遍,如果有空格,表明没满,返回0,

如果没有空格,表明棋盘满了,返回1.

(6)判断游戏输赢的函数

我们在对弈中,每落子一次,我们就判断一次输赢(调用一次这个函数),只有在双方都没赢且棋盘没有满的情况下,才继续下棋。

具体这样实现:

1.判断 每行有没有出现三个连续相同的字符(空格除外),若有退出,返回这个字符的值(我们暖心AI和玩家的字符时不一样的),若没有⬇

2.判断 每列有没有出现三个连续相同的字符(空格除外),若有退出,返回这个字符的值(我们暖心AI和玩家的字符时不一样的),若没有⬇

3.判断 左对角线……

4.判断 右对角线……

经过以上四步,表明没有三个连续相同的棋子,那么就剩两种情况了:

i.棋盘没满,还未分出胜负,接着下棋

ii.棋盘满了,平局,退出游戏

这时我们就要用到上面的 判断棋盘是否已满的函数了⬇

5.判断棋盘是否下满,下满返回字符P表示平局,未下满返回C表示未分出胜负 接着下

需要注意的是这个函返回的字符表示调用时游戏的输赢状态调用它的函数会根据返回的值来进行操作

代码如下:

我们定义一个flag变量来数相同字符的数量

char Is_win(char Board[ROW][COL], int row, int col) {
	int i;
	int j;
	int flag;
	flag= 0;
	for (i = 0; i < row; i++, flag = 0) {
		
		for (j = 1; j < col; j++) {
			if (' ' == Board[i][j - 1] || ' ' == Board[i][j]) {
				continue;
			}
			if (Board[i][j] == Board[i][j-1]) {
				flag++;
				if (flag == 2)
					return Board[i][j];
			}
			
		}
		
	}

	for (j = flag = 0; j < col; j++, flag = 0) {

		for (i = 1; i < row; i++) {

			if (' ' == Board[i-1][j] || ' ' == Board[i][j]) {
				continue;
			}
			if (Board[i][j] == Board[i-1][j])
				flag++;
			if (2 == flag)
				return Board[i][j];
		}
		
	}
	
	for (i = 1,flag = 0; i < row; i++) {
		
		if ((Board[i][i] == Board[i - 1][i - 1])&&Board[i][i]!=' ') {
			flag++;
		}
		if (2==flag) {
			return Board[i][i];
		}
	}

	for (i = 1, j = 1, flag = 0; i < row&&j>=0; i++,j--) {
		//0,2 //1,1 //2,0 
		if ((Board[i][j] == Board[i - 1][j + 1]) && Board[i][j] != ' ') {
			flag++;
		}
		if (2 == flag) {
			return Board[i][j];
		}

	}
	
	if(Is_full(Board,row,col)){
		return 'P';
	}
	return 'C';
}

通过这样的流程,可以实现每一方落完子都能获取到当前游戏的输赢状态。

这时我们再回到游戏主体函数,进入对弈之后的代码

  进入游戏 你一步 我一步 直到游戏结束
	while(1) {
        /玩家走- 实现赋值的函数
		Player_Move(Board, ROW, COL);
        /走完打印棋盘
        DisplayBoard(Board, ROW, COL);
        /判断游戏状态
		 A = Is_win(Board, ROW, COL);/判断游戏状态的函数
		if (A != 'C')/如果棋盘未满 还没有分出胜负就会返回字符c
			break;/如果不是c就结束游戏
        /AI走
		Computer_Move(Board, ROW, COL);
		/AI思考很快 为了不让玩家自卑故意等一下再走棋
        Sleep(1000);
        /走完打印棋盘
		DisplayBoard(Board, ROW, COL);
        /判断游戏状态
		A = Is_win(Board, ROW, COL);
		if (A != 'C')
			break;
	}

    当游戏结束 查看是平局了还是一方赢了
    /判断游戏状态的函数会根据游戏的状态返回不同的值
	switch (A) {/打印出对应的结果后,返回到主界面 询问是否还是孤独 
	case 'P':
		Sleep(1000);
		printf("平局了,再来一把吧!n");
		break;
	case'X':
		Sleep(1000); 
		printf("你赢了,好厉害!n");
		break;
	case'O':
		Sleep(1000); 
		printf("电脑赢了,笨蛋!n");
		break;
	}
}

不难发现,这样一来

只有判断输赢的函数返回C才继续下,不然就跳出,跳出后再进入一个switch语句来看具体是那种结局

结尾

相信通过通过以上的分析,这个暖心AI陪下三子棋的流程就能很清晰的掌握了,希望这篇博客能对大家的代码能力和心理健康起到积极的影响。希望在你一个人如孤狼般奋进,默默成为大牛的路上,有一这位人工智能陪你下三子棋,抚慰你寂寞的心。

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