【算法】极小极大值搜索算法MinimaxSearch————井字棋的应用

极小极大值搜索算法MinimaxSearch

算法描述

极小极大值搜索算法,MinimaxSearch算法,是一种零和算法,是用来最小化对手的利益,最大化自己的利益的算法。
极小极大之搜索算法常用于棋类游戏等双方较量的游戏和程序,算是一种电脑AI算法。
该算法是一种总零和算法,即双方的利益对立,利益的总和为0。比如在井字棋中,总利益为0,以玩家A的视角看,A取得胜利获得的利益是1,则B在A获得胜利的情况下获得的利益则为-1,反之亦然,双方的利益总和为0。

棋类游戏是双方交替下棋的游戏,每一次落子都代表落子的一方在当前情况下要选择对自己利益最大化的一步棋,极小极大值搜索算法就是模拟利益选择的过程,来找到对自己利益最大化的那一步的算法。

使用极小极大搜索算法,通常要建立一棵搜索树在这里插入图片描述
如图是一个简单的搜索树,树的最低层是在搜索深度下可以评估到的各种情况产生的利益(对于某一方而言)。
如图搜索深度为4,也就是这个搜索树可以看到之后4步的情况,根据4步之后的各种情况,来选择根节点这一层该走哪一步棋,最多可以取得多少的利益。
从玩家A的视角来看,玩家A先落子,所以他希望这一步棋可以最大化他的利益,所以玩家A需要在下一层中找到利益最大的那一种情况。而在玩家A落子之后,玩家B落子的时候,希望这一步棋可以最小化玩家A的利益,所以在第二层中,玩家B需要找到第三层中玩家A利益最小化的一步。
以此类推,直到达到搜索深度的最后,在这一步评估各种情况下的利益值,然后从下往上,根据每一层需要的是最大值还是最小值,来一步一步往上传递到最高层,让最高层知道自己应该走哪一步。

在极小极大值算法中,最关键的就是评估函数,用来评估各种情况下产生的利益。

井字棋

在学习了极小极大搜索算法之后,我们来做一个简单的井字棋游戏来练习。
代码有参考博客极小值极大值算法-井字棋
(代码经过评论区指正已经改正)

#include<iostream>
#include<stdlib.h>
#include<conio.h>
#include<algorithm>
using namespace std;

#define N 3
#define STEP 9
#define MAN 1
#define COM -1
#define SEARCHDEPTH 9

int isEnd();
int Evaluate();
int MinimaxSearch(int depth);
void Init();



struct pos
{
	int x;
	int y;
};


int board[3][3] = { {0,0,0},{0,0,0} ,{0,0,0}};

int player = 0;

pos BestPos;

int curDepth = 0;

bool isGameover=false;



void Init()
{
	for (int i = 0;i < N;i++)
	{
		for (int j = 0;j < N;j++)
		{
			board[i][j] = 0;
		}
	}
	player = MAN;
	isGameover = false;
	curDepth = 0;
}

void DrawBoard()
{
	system("cls");
	cout << "-------" << endl;
	cout << "|" <<board[0][0]<< "|" << board[0][1] << "|" << board[0][2] << "|" << endl;
	cout << "-------" << endl;
	cout << "|" << board[1][0] << "|" << board[1][1] << "|" << board[1][2] << "|" << endl;
	cout << "-------" << endl;
	cout << "|" << board[2][0] << "|" << board[2][1] << "|" << board[2][2] <<"|"<<endl;
	cout << "-------" << endl;
}

int Evaluate()
{
	int value = isEnd();
	if (value == MAN)return INT_MAX;
	if (value == COM)return INT_MIN;

	return value;
}

int isEnd()
{
	int i, j;
	int count = 0;
	for (i = 0;i < N;i++)   //行
	{
		count = 0;
		for (j = 0;j < N;j++)
			count += board[i][j];
		if (count == 3 || count == -3)
			return count / 3;
	}
	for (j = 0;j < N;j++)   //列
	{
		count = 0;
		for (i = 0;i < N;i++)
			count += board[i][j];
		if (count == 3 || count == -3)
			return count / 3;
	}
	count = 0;
	count = board[0][0] + board[1][1] + board[2][2];
	if (count == 3 || count == -3)
		return count / 3;
	count = board[0][2] + board[1][1] + board[2][0];
	if (count == 3 || count == -3)
		return count / 3;
	return 0;
}


int MinimaxSearch(int depth)
{
	int value=0;
	if (player == MAN) value = INT_MIN;
	if (player == COM) value = INT_MAX;
	if (isEnd() != 0)
	{
		return Evaluate();
	}
	if (depth == SEARCHDEPTH)
	{
		value = Evaluate();
		return value;
	}

	for (int i = 0;i < N;i++)
	{
		for (int j = 0;j < N;j++)
		{
			if (board[i][j] == 0)
			{
				if (player == MAN)
				{
					board[i][j] = MAN;
					player = COM;
					int nextvalue = MinimaxSearch(depth + 1);
					player = MAN;
					if (value < nextvalue)
					{
						value = nextvalue;
						if (depth == curDepth)
						{
							BestPos.x = i;
							BestPos.y = j;
						}

					}

				}
				else if (player == COM)
				{
					board[i][j] = COM;
					player = MAN;
					int nextvalue = MinimaxSearch(depth + 1);
					player = COM;
					if (value>nextvalue)
					{
						value = nextvalue;
						if (depth == curDepth)
						{
							BestPos.x = i;
							BestPos.y = j;
						}

					}
				}
				board[i][j] = 0;
			}

		}
	}

	return value;
}

void COMplay()
{
	MinimaxSearch(curDepth);
	board[BestPos.x][BestPos.y] = COM;
	curDepth++;
	player = MAN;
}

void MANplay(int x, int y)
{
	board[x][y] = MAN;
	curDepth++;
	player = COM;
	
}

void input()
{
	if (_kbhit())
	{
		char c = _getch();
		if (!isGameover)
		if (c >= '1' && c <= '9')
		{
			int posnum = c - '1';
			int posx = posnum / 3;
			int posy = posnum % 3;
			MANplay(posx, posy);
			if (isEnd() == 0&&curDepth<8)
			{
				COMplay();
				if (isEnd() != 0)
				{
					isGameover = true;
				}
			}
			else
			{
				isGameover = true;
			}
		}
		if (isGameover)
		{
			if (c == 'r' || c == 'R')
			{
				Init();
			}
		}
	}

}

void Logic()
{
	if (isGameover)
	{
		if (isEnd() == MAN)
		{
			cout << "游戏结束 玩家胜利" << endl;
		}
		else if (isEnd() == COM)
		{
			cout << "游戏结束 电脑胜利" << endl;
		}
		else
		{
			cout << "游戏结束 平局" << endl;
		}

		cout << "按R键重开" << endl;
	}

}

int main()
{
	Init();
	while (1)
	{

		input();
		Logic();
		DrawBoard();

	}
}

拓展Alpha-Beta剪枝算法

现在我们做的是简单的井字棋游戏,游戏一共最多只有9步,所以我们的搜索深度可以一直搜索到底,我们的搜索树也不会特别大。

但是如果我们要做一个围棋,或者是五子棋的极大极小搜索,那所产生的搜索树将会呈指数级的增长,我们没有办法将整个棋局搜索到底。
而减少搜索深度虽然可以减少搜索树的大小,但是也会降低人工智能的精度,治标不治本。
为了让我们的算法在五子棋和围棋这样复杂的棋局中也可以使用,我们需要对我们的搜索树进行剪枝,来减除一些不必要的运算。
而这一部分的算法就是Alpha-Beta算法
Alpha-Beta算法,未完待续哈哈哈哈哈(下次再写【)

  • 7
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值