回溯算法(井字游戏)

本文介绍了井字游戏的回溯算法,包括极大极小策略的运用,以及如何根据盘面状态评估位置价值。文章还讨论了在回溯过程中如何通过Alpha和Beta剪枝来优化搜索效率,提高计算机的决策能力。提供了代码分析和实现思路,帮助理解回溯算法在游戏策略中的应用。
摘要由CSDN通过智能技术生成

一、问题描述

三连棋双方都是智力卓越的话,很容易完成平局;在人机对战情况下, 计算机的策略是保证自己不输,并等待玩家失误的机会,当玩家失误时候, 计算机抓住这个失误并取得胜利;
** 极大极小策略**
使用一个求值函数来对一个位置的好坏量化;能使得计算机获胜的位置,其值+1; 平局为0; 计算机输的-1;通过考察盘面能够确定这局棋输赢的位置叫做终端位置(终端位置:有时候只下到一半,计算机发现玩家失误了,计算机一定能赢了,不用等到完全落完棋子);
终端位置是使用immediateHumanWin函数或是immediateCompWin函数能判断的;

如果一个位置不是终端位置, 该位置的值通过递归假设双方都选择最优棋步确定(计算机和玩家都不失误),叫做极大极小策略;计算机试图使这个位置极大化, 玩家试图使这个位置极小化;

每次轮到计算机下棋的时候, 计算机都对棋盘中的空位做挨个做测试,得到每一个空位的值,从中选择最大的作为这一步的下子点;
每次轮到玩家要下棋的时候, 计算机都对棋盘的所有空位都挨个做测试,得到每一个空位的值,从中选择一个最小的值作为这一步的下子点;

二、代码分析

一个井字游戏代码的链接:
https://github.com/YinWenAtBIT/Data-Structure/tree/master/tic_tac_toe
https://blog.csdn.net/yw8355507/article/details/48868173
下面是自己写的代码,但是这个代码有错误,还没有找出,上面链接的代码是正确的,可以参考;

假设当前棋盘的情况如下图所示,将棋盘的9个空间分为两个集合, 其中Q表示已经落子的集合, S表示空闲集合;假设接下来轮到计算机下棋,计算机会在集合S中选择一个i点用于防止本轮的棋子;如何从集合中选择i点执行以下的策略——极大极小策略:
判断的标准是value变量,有三个取值:
1) 当计算机赢,设置COM_WIN为 1
2) 当平局, 设置DRAW为0
3) 当计算机输, 设置COM_LOSS为-1

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

#define N 3
char Chess[N][N] = {
   0};
const char COMP = 'O';
const char HUMAN = 'X';

//极大极小策略
//是计算机获胜的位置,可以得到值1; 平局0; 人获胜的值-1;
#define COMP_LOSS	-1
#define DRAW		0
#define COMP_WIN	1

对于计算机,使用findCompMove返回S集合的value值
findCompMove函数分为三部分;
1) 当前集合Q和S导致棋子落满了, 是平局,返回value = DRAW;
2)在当前的棋面上, 在S集中选择一个i位置放置COMP, 如果放置之后计算机胜利,就返回,并且将i赋值给bestMove返回上一层调用;
3)如果本次无论如何放置都不会成功,就需要在集合S中的value值来判断;
假设value一开始为最低的COM_LOSS, 循环尝试S集合中的每一个点i, 递归调用计算(S - i)的value值得到responseValue, 得到最大的value值为value = max(value, respenseValue);
当能够得到一个最大的value的时候,记录下点i的位置为bestMove表明本次轮流下应该下子的点;最后, 将value的值做函数返回;
在这里插入图片描述
轮到计算机放置棋子时候findCompMove的函数, findCompMove 的目的是在集合S中找到一个点来放置COMP, 这个点就是bestMove;findCompMove 函数返回的是极大极小策略的比较标准,在递归调用中, 一般将比较标准的值的结果作为递归函数的返回值;
回溯算法在实现上需要循环与递归调用的结合, 循环保证对当前所有可能情况都去尝试, 递归保证了在不符合标准的时候(尝试失败的时候),递归调用函数返回,重新选择下一种情况重新尝试;
findCompMove 函数的代码如下:

int findCompMove(int *bestMove) {
   
	int i, responseValue;
	int dc;	//无用的值
	int value;	//空闲点的值

	if (fullChess())
		value = DRAW;
	//在空闲点集合S中选择一个点放置COMP,然后测试计算机是否胜利
	else if (immediateCompWin(bestMove))	
		return COMP_WIN;
	else {
   
		value = COMP_LOSS;//空闲点的值初始化为最小
		*bestMove = 1;
		for (i = 1; i <= N * N; i++) {
   
			if (isEmpty(i)) {
   //S集合是现在棋盘上的空闲点
				place(i, COMP);	//假设在i位置落子
				responseValue = findHumanMove(&dc);//测试(S-i) 空闲点的值
				unPlace(i);	//恢复位置i

				//responseValue表示空闲点(S-i)时候的空闲点值
				//value表示空闲点S时候的空闲先值
				//根据极大极小值策略, 计算机是想让空闲点的值最大化,因此取得max(value, responseValue)
				//如果在空闲点S中选择一个点i能使的value增大为responseValue,就在位置i上落子
				//如果当前空闲空间S中任何一个i都不能使得空闲值增加, 就返回
				if (responseValue > value)
				{
   
					value = responseValue;
					*bestMove = i;
				}

			}
		}
	}
	//函数返回空闲点的值
	//value = 1, 表示剩余空闲点中又让计算机赢的组合,计算机有赢的可能
	//value = 0, 计算机有平局的可能
	//value = -1 计算机有输的可能
	//由于有value = max(value, responseValue),因此上述三种情况有优先级
	//只要存在计算机赢的可能, value就返回1
	//当不存在计算机赢的机会, 但是只要存在平局的机会, value就返回0
	//当即不存在计算机赢的机会, 也不存在平局的机会,value才返回-1;
	return value;
}

findHumanMove 函数的代码实现如下:

int findHumanMove(int *bestMove) {
   
	int i, responseValue;
	int dc;
	int value;

	if (fullChess())
		value = DRAW;
	else if (immediateHumanWin(bestMove))
		return COMP_LOSS;
	else {
   
		value = COMP_WIN; 
		*bestMove = 1;
		for (i = 1; i <= N * N; i++) {
   
			if (isEmpty(i)) {
   
				place(i, HUMAN);
				responseValue = findCompMove(&dc);
				unPlace(i);

				if (responseValue < value) {
   
					value = responseValue;
					*bestMove = i
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值