【c语言】震惊!300行代码教你写出 N子棋 无敌AI人机(三子棋(井字棋)、四子棋、五子棋和六子棋等)

目录

  • 前言:
    • 1. 游戏规则与程序设计
    • 2. 极小化极大算法概述
    • 3. 极小化极大算法的实现步骤
      • (1)建立评估函数:
      • (2)极小化极大搜索:
      • (3)剪枝优化:(水平有限没有实现)
  • 实现效果视频展示
    • AI三子棋实现效果视频展示(两局平局)
    • AI四子棋实现效果视频展示(一输一赢)
    • AI五子棋实现效果视频展示(一输一赢)
    • AI六子棋实现效果(奋战半小时,被ai打败了)
  • 展示所创建的函数
  • 关键步骤代码解析
    • 1.判断N子棋游戏是否结束并返回胜利方 char check_win( )
    • 2. 判断N子棋游戏加权连子数,以及棋型并给予相应的得分 int evaluate_count( )
    • 3.极小化极大算法 int minimax( )
  • 完整代码+详细注释
    • 头文件 game.h
    • 源文件 game.c (300行代码)
    • 源文件 test.c

前言:

N子棋是一种经典的棋类游戏,它在计算机科学领域有着广泛的应用。本文将介绍如何使用C语言编写N子棋游戏程序,并详细解析使用极小化极大算法实现AI对战的方法。

1. 游戏规则与程序设计

N子棋游戏的规则已经在前文提到,我们需要先定义一个设定大小的棋盘数组作为游戏的核心数据结构。使用C语言的二维数组可以很方便地表示棋盘状态。玩家和AI轮流落子,通过输入坐标来在空位上下子,并判断是否获胜。

2. 极小化极大算法概述

极小化极大算法(Minimax Algorithm)是一种常用于博弈游戏的决策算法,在N子棋中也可以有效地应用。该算法通过递归搜索所有可能的落子情况,并给每个局面评分,以找到最优的走法

3. 极小化极大算法的实现步骤

(1)建立评估函数:

为了给每个局面评分,我们需要定义一个评估函数。评估函数的设计需要考虑棋子的布局、攻防情况等因素,以尽可能准确地评估当前局面的优劣。

(2)极小化极大搜索:

通过递归搜索所有可能的落子情况,每一步轮到AI时,它会选择对自己最有利的走法;每一步轮到玩家时,它会选择对AI最不利的走法。通过不断深入搜索并更新评分,AI可以找到最优的走法

(3)剪枝优化:(水平有限没有实现)

由于N子棋的搜索空间很大,为了减少搜索时间,我们可以使用剪枝技术,如Alpha-Beta剪枝。这种剪枝算法可以排除一些明显不会被选择的走法,从而加速搜索过程。

实现效果视频展示

AI三子棋实现效果视频展示(两局平局)

链接:https://live.csdn.net/v/314862?spm=1001.2014.3001.5501

AI三子棋

AI四子棋实现效果视频展示(一输一赢)

链接:https://live.csdn.net/v/314866?spm=1001.2014.3001.5501

AI 四子棋

AI五子棋实现效果视频展示(一输一赢)

链接:https://live.csdn.net/v/314872?spm=1001.2014.3001.5501

AI 五子棋

AI六子棋实现效果(奋战半小时,被ai打败了)

链接:https://live.csdn.net/v/314891?spm=1001.2014.3001.5501

AI 六子棋,我竟然被自己写的AI打败了

展示所创建的函数

//game.h
#pragma once
#include
#include
#include
#include

//定义棋盘大小(可更改)
#define board_size 15

// 定义玩家和AI的棋子类型
#define PLAYER 'X'
#define AI 'O'
// 定义符号
#define EMPTY ' '
#define NOFULL '-'
#define FULL '*'

//初始化棋盘
void init_board(char board[board_size][board_size]);
//打印棋盘
void display_board(char board[board_size][board_size]);
//判断位置是否为空
int isPositionEmpty(char board[board_size][board_size], int x, int y);
//玩家下棋
void player_move(char board[board_size][board_size]);
// 判断当前位置是否为有效的落子点
bool isValidMove(int x, int y);
// 判断N子棋游戏加权连子数,以及棋型并给予相应的得分
char check_win(char board[board_size][board_size]);
// 判断N子棋游戏连子数
int evaluate_count(char board[board_size][board_size]);
//评估当前局面的得分
int evaluate(char board[board_size][board_size], int i, int j);
// 极小化极大算法
int minimax(char board[board_size][board_size], int depth, int isMaximizingPlayer, int i, int j);
// AI 下棋
void ai_move(char board[board_size][board_size]);

关键步骤代码解析

1.判断N子棋游戏是否结束并返回胜利方 char check_win( )

这段代码是一个用于判断五子棋游戏是否结束并返回胜利方的函数 check_win。函数接受一个二维字符数组 board 作为参数,表示棋盘,并返回一个字符表示游戏结果。

函数首先定义了四个方向:水平、垂直、左上到右下、右上到左下。然后通过两层循环遍历棋盘上的每个位置。对于每个位置,如果没有棋子,则跳过;否则,获取当前位置的颜色(玩家或AI)。

接下来,使用四个方向进行遍历并统计连续相同颜色的棋子数。遍历过程中,通过增量 dx 和 dy改变当前位置的坐标,不断在当前方向上移动。使用 isValidMove 函数判断新的位置是否合法(在棋盘范围内),并继续判断该位置的棋子颜色是否与当前颜色相同。如果是相同的颜色,则将计数器 count 自增,表示连续相同颜色棋子数增加了一个

如果任何一个方向上的连子数达到胜利条件WinNum,即五子棋规则中连成一排的棋子数),则返回胜利方的颜色

如果棋盘上没有空位置,表示平局,返回平局标志 FULL

//game.c
// 判断N子棋游戏是否结束并返回胜利方
char check_win(char board[board_size][board_size])
{
	// 定义四个方向:水平、垂直、左上到右下、右上到左下
	int directions[4][2] = { {1, 0}, {0, 1}, {1, 1}, {1, -1} };
	int n = board_size;// 棋盘大小

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (board[i][j] == EMPTY) {
				continue;  // 当前位置没有棋子,跳过
			}
			char player = board[i][j];
			for (int k = 0; k < 4; k++) { // 遍历四个方向
				int dx = directions[k][0]; // 方向的 x 坐标增量
				int dy = directions[k][1]; // 方向的 y 坐标增量
				int count = 1;  // 当前位置已有棋子,计数从1开始
				int x = i + dx;
				int y = j + dy;

				while (isValidMove(x, y) && board[x][y] == player) { // 沿当前方向连续相同颜色的棋子数
					count++;
					x += dx;
					y += dy;
				}

				if (count >= WinNum) {
					return player;  // 有一方获胜,返回胜利方
				}
			}
		}
	}

	// 判断棋盘是否已满,即平局
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (board[i][j] == EMPTY) {
				return NOFULL;  // 棋盘还未满
			}
		}
	}
	return FULL;  // 棋盘已满平局
}

2. 判断N子棋游戏加权连子数,以及棋型并给予相应的得分 int evaluate_count( )

首先,判断游戏是否胜利的函数 check_win 使用了一个二维数组 board 来表示棋盘,其中 board_size 是棋盘的大小。函数通过遍历棋盘上的每个位置,并检查每个位置是否有棋子,如果当前位置没有棋子则跳过。如果当前位置有棋子,则使用四个固定方向(水平、垂直、左上到右下、右上到左下)进行遍历,统计连续相同颜色的棋子数。如果有任何一个方向上的连子数达到胜利条件WinNum,即五子棋规则中连成一排的棋子数),则返回胜利方。如果棋盘上没有空位置,表示平局,返回平局标志

其次,计算加权连子数以及棋型得分的函数 evaluate_count 也使用了一个二维数组 board 来表示棋盘。函数遍历棋盘上的每个位置,并检查每个位置是否有棋子,如果没有棋子则跳过。如果当前位置有棋子,则使用四个固定方向进行遍历,统计连续相同颜色的棋子数。根据当前棋子的颜色和连子数,给出相应的得分。AI得分的计算基于连子数的平方,而玩家得分则是减去连子数的平方。同时,根据不同的棋型(如活五、活四、活三等),给予特定的得分

这两个函数的实现细节还依赖了其他辅助函数,如判断当前位置是否合法的 isValidMove 函数、判断目标位置是否为空的 isPositionEmpty 函数等(详细可见完整代码部分)。

//game.c
// 判断N子棋游戏是否结束并返回胜利方
char check_win(char board[board_size][board_size])
{
	// 定义四个方向:水平、垂直、左上到右下、右上到左下
	int directions[4][2] = { {1, 0}, {0, 1}, {1, 1}, {1, -1} };
	int n = board_size;// 棋盘大小

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (board[i][j] == EMPTY) {
				continue;  // 当前位置没有棋子,跳过
			}
			char player = board[i][j];
			for (int k = 0; k < 4; k++) { // 遍历四个方向
				int dx = directions[k][0]; // 方向的 x 坐标增量
				int dy = directions[k][1]; // 方向的 y 坐标增量
				int count = 1;  // 当前位置已有棋子,计数从1开始
				int x = i + dx;
				int y = j + dy;

				while (isValidMove(x, y) && board[x][y] == player) { // 沿当前方向连续相同颜色的棋子数
					count++;
					x += dx;
					y += dy;
				}

				if (count >= WinNum) {
					return player;  // 有一方获胜,返回胜利方
				}
			}
		}
	}

	// 判断棋盘是否已满,即平局
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (board[i][j] == EMPTY) {
				return NOFULL;  // 棋盘还未满
			}
		}
	}
	return FULL;  // 棋盘已满平局
}

// 判断N子棋游戏加权连子数,以及棋型并给予相应的得分
int evaluate_count(char board[board_size][board_size])
{
	int directions[4][2] = { {1, 0}, {0, 1}, {1, 1}, {1, -1} };
	// 定义四个方向的增量数组,分别表示横向、纵向、左上到右下斜向、右上到左下斜向
	int n = board_size;
	int count;
	int sorce = 0; // 初始化计数器和得分
	for (int i = 0; i < n; i++) 
	{
		for (int j = 0; j < n; j++)
		{
			if (board[i][j] == EMPTY) 
			{
				continue;  // 当前位置没有棋子,跳过
			}
			char player = board[i][j];
			for (int k = 0; k < 4; k++) 
			{
				int dx = directions[k][0];
				int dy = directions[k][1];
				count = 1;  // 当前位置已有棋子,计数从1开始
				int x = i + dx;
				int y = j + dy;

				while (isValidMove(x, y) && board[x][y] == player) 
				{
					count++;
					x += dx;
					y += dy;
				} // 在当前方向上统计连续的棋子个数,直到边界或者不是同一种棋子

				if (player == AI)
				{
					sorce += 20 * (int)pow(count, 2);// AI连成其他长度的连子,基于平方的得分增加
				}
				if (player == PLAYER)
				{
					sorce -= 30 * (int)pow(count, 2); // 玩家连成其他长度的连子,基于平方的得分减少
				}

				// 判断棋型并给予相应的得分
				if (count == WinNum)
				{
					if (player == AI)
						sorce += 1000;  // “活五”,AI得分加1000
					else if (player == PLAYER)
						sorce -= 1500;  // “活五”,玩家得分减1500
				}
				else if (count == WinNum - 1)
				{
					if ((player == AI && isPositionEmpty(board, x + dx, y + dy) && isPositionEmpty(board, i - dx, j - dy)) ||
						(player == PLAYER && isPositionEmpty(board, x + dx, y + dy) && isPositionEmpty(board, i - dx, j - dy)))
					{
						if (player == AI)
							sorce += 800;  // “活四”,AI得分加800
						else if (player == PLAYER)
							sorce -= 1200;  // “活四”,玩家得分减1200
					}
				}
				else if (count == WinNum - 2)
				{
					if ((player == AI && isPositionEmpty(board, x + dx, y + dy) && isPositionEmpty(board, x - 2 * dx, y - 2 * dy) &&
						isPositionEmpty(board, i - dx, j - dy) && isPositionEmpty(board, i + 2 * dx, j + 2 * dy)) ||
						(player == PLAYER && isPositionEmpty(board, x + dx, y + dy) && isPositionEmpty(board, x - 2 * dx, y - 2 * dy) &&
							isPositionEmpty(board, i - dx, j - dy) && isPositionEmpty(board, i + 2 * dx, j + 2 * dy)))
					{
						if (player == AI)
							sorce += 300;  // “活三”,AI得分加300
						else if (player == PLAYER)
							sorce -= 400;  // “活三”,玩家得分减400
					}
				}


			}
		}
	}
	return sorce;
}

3.极小化极大算法 int minimax( )

这段代码是一个使用极小化极大算法评估当前局面得分的函数 minimax,该函数接受一个二维字符数组 board 作为参数,表示棋盘状态,以及当前搜索深度 depth、当前玩家是否为最大化玩家 isMaximizingPlayer 以及当前位置 (i, j)

函数首先判断是否达到指定深度游戏已经结束,如果是则返回当前局面的得分,调用 evaluate 函数计算基本得分。如果游戏未结束,则根据当前玩家是最大化玩家还是最小化玩家进行不同的处理。

如果是最大化玩家(即AI),函数会初始化最大得分为负无穷大,并通过两层循环遍历棋盘上的每个位置。对于每个空格的位置,假设AI在该位置下棋,然后递归调用 minimax 函数切换到最小化玩家,继续搜索子节点的得分。在递归结束后,将该位置恢复为空格,并更新最大得分 maxEval

如果是最小化玩家(即玩家),函数会初始化最小得分为正无穷大,并通过两层循环遍历棋盘上的每个位置。对于每个空格的位置,假设玩家在该位置下棋,然后递归调用 minimax 函数切换到最大化玩家,继续搜索子节点的得分。在递归结束后,将该位置恢复为空格,并更新最小得分 minEval

最终,如果当前玩家为最大化玩家,则返回最大得分 maxEval;如果当前玩家为最小化玩家,则返回最小得分 minEval

需要注意的是,这段代码中使用了一个评估函数 evaluate,用于计算每个局面的得分在 evaluate 函数中,首先调用 evaluate_count 函数计算基本得分,然后根据棋盘中心的距离对基本得分进行调整离中心越远得分越低。最后返回调整后的得分

//game.c
//评估当前局面的得分
int evaluate(char board[board_size][board_size], int i, int j)
{
	int sorce = evaluate_count(board);// 先调用evaluate_count函数计算基本得分
	sorce += (1000/sqrt((i + 1 - board_size/2) * (i + 1 - board_size/2) + (j + 1 - board_size/2) * (j + 1 - board_size/2))/ board_size);
	// 根据棋盘中心的距离对基本得分进行调整,离中心越远,得分越低
	return sorce;
}

// 极小化极大算法
int minimax(char board[board_size][board_size], int depth, int isMaximizingPlayer, int i, int j)
{
	// 如果达到指定深度或游戏结束,则返回当前局面的得分
	if (depth == 0 || check_win(board) != NOFULL) 
	{
		return evaluate(board, i, j); // 如果游戏结束,则返回当前局面的得分
	}

	if (isMaximizingPlayer) // 最大化玩家(AI)
	{
		int maxEval = -1000000;// 初始化最大得分为负无穷大
		int i, j;
		for (i = 0; i < board_size; i++) {
			for (j = 0; j < board_size; j++) {
				if (board[i][j] == EMPTY) { // 检查该位置是否为空格
					board[i][j] = AI; // 假设AI在该位置下棋
					int eval = minimax(board, depth - 1, 0, i, j); // 递归搜索子节点,切换到最小化玩家
					board[i][j] = EMPTY; // 恢复该位置为空格
					if (eval > maxEval) {
						maxEval = eval; // 更新最大得分
					}
				}
			}
		}
		return maxEval;
	}
	else {  // 最小化玩家(玩家)
		int minEval = 1000000; // 初始化最小得分为正无穷大
		int i, j;
		for (i = 0; i < board_size; i++) {
			for (j = 0; j < board_size; j++) {
				if (board[i][j] == EMPTY) { // 检查该位置是否为空格
					board[i][j] = PLAYER; // 假设玩家在该位置下棋
					int eval = minimax(board, depth - 1, 1, i, j); // 递归搜索子节点,切换到最小化玩家
					board[i][j] = EMPTY; // 恢复该位置为空格
					if (eval < minEval) {
						minEval = eval; // 更新最小得分
					}
				}
			}
		}
		return minEval;
	}
}

完整代码+详细注释

头文件 game.h

//game.h
#pragma once
#include
#include
#include
#include

//定义棋盘大小(可更改)
#define board_size 15

// 定义玩家和AI的棋子类型
#define PLAYER 'X'
#define AI 'O'
// 定义符号
#define EMPTY ' '
#define NOFULL '-'
#define FULL '*'

//初始化棋盘
void init_board(char board[board_size][board_size]);
//打印棋盘
void display_board(char board[board_size][board_size]);
//判断位置是否为空
int isPositionEmpty(char board[board_size][board_size], int x, int y);
//玩家下棋
void player_move(char board[board_size][board_size]);
// 判断当前位置是否为有效的落子点
bool isValidMove(int x, int y);
// 判断N子棋游戏加权连子数,以及棋型并给予相应的得分
char check_win(char board[board_size][board_size]);
// 判断N子棋游戏连子数
int evaluate_count(char board[board_size][board_size]);
//评估当前局面的得分
int evaluate(char board[board_size][board_size], int i, int j);
// 极小化极大算法
int minimax(char board[board_size][board_size], int depth, int isMaximizingPlayer, int i, int j);
// AI 下棋
void ai_move(char board[board_size][board_size]);

源文件 game.c (300行代码)

//game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
extern int WinNum;

//初始化棋盘
void init_board(char board[board_size][board_size])
{
	int i = 0;
	int j = 0;
	for (i = 0; i < board_size; i++)
	{
		for (j = 0; j < board_size; j++)
		{
			board[i][j] = EMPTY;
		}
	}
}

//打印棋盘
void display_board(char board[board_size][board_size])
{
	int i = 0;
	int j = 0;
	int tmp = 0;
	printf(" ");
	for (i = 1; i <= board_size; i++)
	{
		if (10 == i)
		{
			printf(" ");
		}
		printf("  %2d", i);
	}
	printf("\n");
	for (i = 0; i <= 2 * board_size; i++)
	{
		if (i % 2 == 0)
		{
			for (j = 0; j <= board_size; j++)
			{
				if (j != 0)
				{
					printf("---|");
				}
				else
				{
					printf("  |");
				}
			}
		}
		else
		{
			for (j = 0; j <= board_size; j++)
			{
				if (j == 0)
				{
					printf("%2d|", i / 2 + 1);
				}
				else
				{
					tmp = i / 2;
					printf(" %c |", board[tmp][j - 1]);
				}
			}
		}

		printf("\n");
	}
}

// 判断位置是否为空
int isPositionEmpty(char board[board_size][board_size], int x, int y) 
{
	return board[x][y] == EMPTY;
}

//玩家下棋
void player_move(char board[board_size][board_size])
{
	int x = 0;
	int y = 0;
	while (1)
	{
		scanf("%d%d", &y, &x);
		//判断输入是否合法
		if ((x >= 1 && x <= board_size) && (y >= 1 && y <= board_size))
		{
			if (isPositionEmpty(board, x - 1, y - 1))
			{
				board[x - 1][y - 1] = PLAYER;
				break;
			}
			else
			{
				printf("该位置已有棋子, 请重新输入!\n");
			}
		}
		else
		{
			printf("输入非法,请重新输入!\n");
		}
	}
}

// 判断当前位置是否为有效的落子点
bool isValidMove(int x, int y) 
{
	// 判断 (x, y) 是否在棋盘范围内,这里假设棋盘大小为board_size
	if (x < 0 || x >= board_size || y < 0 || y >= board_size) 
	{
		return false;
	}
	return true;
}

// 判断五N子棋游戏是否结束并返回胜利方
char check_win(char board[board_size][board_size])
{
	// 定义四个方向:水平、垂直、左上到右下、右上到左下
	int directions[4][2] = { {1, 0}, {0, 1}, {1, 1}, {1, -1} };
	int n = board_size;// 棋盘大小

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (board[i][j] == EMPTY) {
				continue;  // 当前位置没有棋子,跳过
			}
			char player = board[i][j];
			for (int k = 0; k < 4; k++) { // 遍历四个方向
				int dx = directions[k][0]; // 方向的 x 坐标增量
				int dy = directions[k][1]; // 方向的 y 坐标增量
				int count = 1;  // 当前位置已有棋子,计数从1开始
				int x = i + dx;
				int y = j + dy;

				while (isValidMove(x, y) && board[x][y] == player) { // 沿当前方向连续相同颜色的棋子数
					count++;
					x += dx;
					y += dy;
				}

				if (count >= WinNum) {
					return player;  // 有一方获胜,返回胜利方
				}
			}
		}
	}

	// 判断棋盘是否已满,即平局
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (board[i][j] == EMPTY) {
				return NOFULL;  // 棋盘还未满
			}
		}
	}
	return FULL;  // 棋盘已满平局
}

// 判断五N子棋游戏加权连子数,以及棋型并给予相应的得分
int evaluate_count(char board[board_size][board_size])
{
	int directions[4][2] = { {1, 0}, {0, 1}, {1, 1}, {1, -1} };
	// 定义四个方向的增量数组,分别表示横向、纵向、左上到右下斜向、右上到左下斜向
	int n = board_size;
	int count;
	int sorce = 0; // 初始化计数器和得分
	for (int i = 0; i < n; i++) 
	{
		for (int j = 0; j < n; j++)
		{
			if (board[i][j] == EMPTY) 
			{
				continue;  // 当前位置没有棋子,跳过
			}
			char player = board[i][j];
			for (int k = 0; k < 4; k++) 
			{
				int dx = directions[k][0];
				int dy = directions[k][1];
				count = 1;  // 当前位置已有棋子,计数从1开始
				int x = i + dx;
				int y = j + dy;

				while (isValidMove(x, y) && board[x][y] == player) 
				{
					count++;
					x += dx;
					y += dy;
				} // 在当前方向上统计连续的棋子个数,直到边界或者不是同一种棋子

				if (player == AI)
				{
					sorce += 20 * (int)pow(count, 2);// AI连成其他长度的连子,基于平方的得分增加
				}
				if (player == PLAYER)
				{
					sorce -= 30 * (int)pow(count, 2); // 玩家连成其他长度的连子,基于平方的得分减少
				}

				// 判断棋型并给予相应的得分
				if (count == WinNum)
				{
					if (player == AI)
						sorce += 1000;  // “活五”,AI得分加1000
					else if (player == PLAYER)
						sorce -= 1500;  // “活五”,玩家得分减1500
				}
				else if (count == WinNum - 1)
				{
					if ((player == AI && isPositionEmpty(board, x + dx, y + dy) && isPositionEmpty(board, i - dx, j - dy)) ||
						(player == PLAYER && isPositionEmpty(board, x + dx, y + dy) && isPositionEmpty(board, i - dx, j - dy)))
					{
						if (player == AI)
							sorce += 800;  // “活四”,AI得分加800
						else if (player == PLAYER)
							sorce -= 1200;  // “活四”,玩家得分减1200
					}
				}
				else if (count == WinNum - 2)
				{
					if ((player == AI && isPositionEmpty(board, x + dx, y + dy) && isPositionEmpty(board, x - 2 * dx, y - 2 * dy) &&
						isPositionEmpty(board, i - dx, j - dy) && isPositionEmpty(board, i + 2 * dx, j + 2 * dy)) ||
						(player == PLAYER && isPositionEmpty(board, x + dx, y + dy) && isPositionEmpty(board, x - 2 * dx, y - 2 * dy) &&
							isPositionEmpty(board, i - dx, j - dy) && isPositionEmpty(board, i + 2 * dx, j + 2 * dy)))
					{
						if (player == AI)
							sorce += 300;  // “活三”,AI得分加300
						else if (player == PLAYER)
							sorce -= 400;  // “活三”,玩家得分减400
					}
				}


			}
		}
	}
	return sorce;
}

//评估当前局面的得分
int evaluate(char board[board_size][board_size], int i, int j)
{
	int sorce = evaluate_count(board);// 先调用evaluate_count函数计算基本得分
	sorce += (1000/sqrt((i + 1 - board_size/2) * (i + 1 - board_size/2) + (j + 1 - board_size/2) * (j + 1 - board_size/2))/ board_size);
	// 根据棋盘中心的距离对基本得分进行调整,离中心越远,得分越低
	return sorce;
}

// 极小化极大算法
int minimax(char board[board_size][board_size], int depth, int isMaximizingPlayer, int i, int j)
{
	// 如果达到指定深度或游戏结束,则返回当前局面的得分
	if (depth == 0 || check_win(board) != NOFULL) 
	{
		return evaluate(board, i, j); // 如果游戏结束,则返回当前局面的得分
	}

	if (isMaximizingPlayer) // 最大化玩家(AI)
	{
		int maxEval = -1000000;// 初始化最大得分为负无穷大
		int i, j;
		for (i = 0; i < board_size; i++) {
			for (j = 0; j < board_size; j++) {
				if (board[i][j] == EMPTY) { // 检查该位置是否为空格
					board[i][j] = AI; // 假设AI在该位置下棋
					int eval = minimax(board, depth - 1, 0, i, j); // 递归搜索子节点,切换到最小化玩家
					board[i][j] = EMPTY; // 恢复该位置为空格
					if (eval > maxEval) {
						maxEval = eval; // 更新最大得分
					}
				}
			}
		}
		return maxEval;
	}
	else {  // 最小化玩家(玩家)
		int minEval = 1000000; // 初始化最小得分为正无穷大
		int i, j;
		for (i = 0; i < board_size; i++) {
			for (j = 0; j < board_size; j++) {
				if (board[i][j] == EMPTY) { // 检查该位置是否为空格
					board[i][j] = PLAYER; // 假设玩家在该位置下棋
					int eval = minimax(board, depth - 1, 1, i, j); // 递归搜索子节点,切换到最小化玩家
					board[i][j] = EMPTY; // 恢复该位置为空格
					if (eval < minEval) {
						minEval = eval; // 更新最小得分
					}
				}
			}
		}
		return minEval;
	}
}

// AI 下棋
void ai_move(char board[board_size][board_size])
{
	int bestEval = -1000000;// 初始化最优得分为负无穷大
	int bestRow = -1;// 初始化最优位置的行号
	int bestCol = -1;// 初始化最优位置的列号
	int depth = 0;//递归深度,深度越高耗时越长
	// 根据棋盘大小确定递归深度
	if (board_size < 4){
		depth = 6;
	}
	else if (board_size < 5){
		depth = 5;
	}
	else if (board_size < 7){
		depth = 4;
	}
	else if (board_size < 9){
		depth = 3;
	}
	else{
		depth = 2;
	}

	int i, j;
	for (i = 0; i < board_size; i++) {
		for (j = 0; j < board_size; j++) {
			if (board[i][j] == EMPTY) {
				board[i][j] = AI; // 假设AI在该位置下棋
				int eval = minimax(board, depth, 0, i, j); // 调用极小化极大算法计算得分
				board[i][j] = EMPTY; // 恢复该位置为空格
				if (eval > bestEval) {
					bestEval = eval; // 更新最优得分
					bestRow = i;// 更新最优位置的行号
					bestCol = j;// 更新最优位置的列号
				}
			}
		}
	}
	// 在最优位置下棋
	board[bestRow][bestCol] = AI;
}

源文件 test.c

//test.c
#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

//定义胜利条件
int WinNum = 0;

void menu()
{
	printf("********************\n");
	printf("****** 1.play ******\n");
	printf("****** 0.exit ******\n");	
	printf("********************\n");
	printf("请选择:>");
}

bool display_win(char board[board_size][board_size])
{
	if (check_win(board) == PLAYER)
	{
		display_board(board); // 最终显示棋盘
		printf("玩家胜利!\n");
		return true;
	}
	else if (check_win(board) == AI)
	{
		display_board(board); // 最终显示棋盘
		printf("AI 胜利!\n");
		return true;
	}
	else if (check_win(board) == FULL)
	{
		display_board(board); // 最终显示棋盘
		printf("平局!\n");
		return true;
	}
	return false;
}

void game(char board[board_size][board_size])
{
	int is_player_turn = 0; // 玩家先手
	printf("************************\n");
	printf("****** 1.玩家先手 ******\n");
	printf("****** 0.AI先手  *******\n");
	printf("************************\n");
	printf("请输入:>");
	scanf("%d", &is_player_turn);
	int turn = 0; // 回合数
	init_board(board);

	// 游戏循环
	while (1) 
	{
		turn++;
		if (is_player_turn)
		{
			system("cls");// 清屏
			printf("***** %d子棋游戏!*****\n", WinNum);
			display_board(board); // 显示棋盘
			printf("请玩家输入坐标下棋:>");
			player_move(board); // 玩家下棋
			if (display_win(board))
				break;
			system("cls");
			display_board(board); // 显示棋盘
			printf("AI正在下棋!\n");
			ai_move(board); // AI 下棋
			if (display_win(board))
				break;
		}
		else
		{
			system("cls");// 清屏
			printf("***** %d子棋游戏!*****\n", WinNum);
			display_board(board); // 显示棋盘
			printf("AI正在下棋!\n");
			if (turn == 1)
			{
				board[board_size/2][board_size / 2] = AI;
			}
			else
			{
				ai_move(board); // AI 下棋
				if (display_win(board))
					break;
			}
			system("cls");// 清屏
			printf("***** %d子棋游戏!*****\n", WinNum);
			display_board(board); // 显示棋盘
			printf("请玩家输入坐标下棋:>");
			player_move(board); // 玩家下棋
			if (display_win(board))
				break;
		}
	}
	printf("总共进行了 %d 回合。\n", turn);
}

int main()
{
	while (1)
	{
		menu();
		int input = 0;
		scanf("%d", & input);
		if (input)
		{
			while (1)
			{
				printf("请输入连胜棋子个数:>");
				scanf("%d", &WinNum);
				if (WinNum > board_size)
				{
					printf("输入的连胜棋子个数已超过棋盘大小,请重新输入,或者调整棋盘大小\n");
				}
				else
				{
					printf("**** %d子棋游戏开始 ****\n", WinNum);
					break;
				}
			}
		}
		else
		{
			printf("已退出%d子棋游戏!\n", WinNum);
			break;
		}
		char board[board_size][board_size] = { 0 }; //创建棋盘
		game(board);
	}
	return 0;
}

你可能感兴趣的:(C语言,人工智能,c语言,开发语言)