Pacman项目是加州大学伯克利分校提供的一个可视化的AI学习平台。其主体利用python完成。该项目提供了丰富的说明文档,以及预先实现了一些简单的算法供参考各接口的使用。

http://ai.berkeley.edu/project_overview.html

本文利用Pac-Man平台实现简单的对抗搜索。

Part1 : Reflex Agent

提供的ReflexAgent有以下接口:

class ReflexAgent(Agent):
def getAction(self,gameState)
def evaluationFunction(self, currentGameState, action)

根据对getAction的分析:

def getAction(self, gameState) #根据当前的评估函数确定下一步的动作
#获取当前可能的下一步的方向 有stop east ...
legalMoves = gameState.getLegalActions()
#根据评估函数获取所有的下一步的权值
scores = [self.evaluationFunction(gameState, action) for action in legalMoves]
#获取最好的分数
bestScore = max(scores)
#根据最好的分数获取最好的行动选择
bestIndices = [index for index in range(len(scores)) if scores[index] == bestScore]
#从最好的行动集合中随机选择一个最为下一步的动作
chosenIndex = random.choice(bestIndices)
return legalMoves[chosenIndex]

可知evaluationFunction是这个Agent的灵魂,评估每一个输入的分数。根据提供的下面函数的接口,设计了一个简单的Pacman的AI。

初始版本的AI如该类所述,只提供了基本的反射。该agent所处环境包括以下内容

  • 食物到Agent的距离
  • 怪兽到Agent的距离
  • 超级豆子到Agent的距离
  • 下一个状态能否吃到豆子 或者被Ghost吃掉

计算出这些参数,给这些参数以固定的权值,就写出了最基础的AI

    def evaluationFunction(self, currentGameState, action):
# 获取当前游戏状态 其中G表示为Ghost %表示为墙 角标表示pacman 角标方向代表上一次选择的方向
successorGameState = currentGameState.generatePacmanSuccessor(action)
# print 'successorGameState\n',successorGameState # 获取这样移动后新的位置
newPos = successorGameState.getPacmanPosition()
# print 'newPos',newPos # 获取食物在图中的分布(二维数组,有失误为T没食物为F)
newFood = successorGameState.getFood()
curFood = currentGameState.getFood()
# print 'newFood',newFood # 获取Ghost的位置
newGhostStates = successorGameState.getGhostStates()
# print 'ghostState',newGhostStates[0].getPosition() # 获取吃超级豆子之后 Ghost害怕还剩余的时间
newScaredTimes = [ghostState.scaredTimer for ghostState in newGhostStates] # 对这个选择评估的分数
currscore = 0 if action == "Stop":
return -100 # 如果当前状态能够使ghost害怕,将所有的时间加入进来
for st in newScaredTimes:
currscore += st # 根据Ghost所在的位置,获取与当前位置的距离
ghost_distances = []
for gs in newGhostStates:
ghost_distances += [manhattanDistance(gs.getPosition(),newPos)] # 获取food所在的所有pos
foodList = newFood.asList()
curfoodList = curFood.asList() # 获取food所在的所有wall
wallList = currentGameState.getWalls().asList() # 保存food的距离
food_distences = [] # 获取所有食物到达当前位置的距离
for foodpos in foodList:
food_distences += [manhattanDistance(newPos,foodpos)] # 对食物的距离取反
inverse_food_distences=0;
if len(food_distences)>0 and min(food_distences) > 0:
inverse_food_distences = 1.0 / min(food_distences)
# 考虑了ghost与当前的距离,其权值更大
currscore += min(ghost_distances)*(inverse_food_distences**4)
# 获取当前系统判定的分数 又可能当前吃到了豆子 分数更高些
currscore+=successorGameState.getScore()
if newPos in curfoodList:
currscore = currscore * 1.1
return currscore

运行测试

python autograder.py -q q1

10次测试如下

Pacman emerges victorious! Score: 1228
Pacman emerges victorious! Score: 1253
Pacman emerges victorious! Score: 1246
Pacman emerges victorious! Score: 1255
Pacman emerges victorious! Score: 1247
Pacman emerges victorious! Score: 1257
Pacman emerges victorious! Score: 1244
Pacman emerges victorious! Score: 1260
Pacman emerges victorious! Score: 1261
Pacman emerges victorious! Score: 1258
Average Score: 1250.9
Scores: 1228.0, 1253.0, 1246.0, 1255.0, 1247.0, 1257.0, 1244.0, 1260.0, 1261.0, 1258.0
Win Rate: 10/10 (1.00)
Record: Win, Win, Win, Win, Win, Win, Win, Win, Win, Win

测试GUI

直接运行游戏GUI

python pacman.py -p ReflexAgent -k 2

Part 2 : MinMax

利用MinMax博弈树,这里模拟的Ghost可能不止一个,在计算Min节点的时候增加了对多Ghost的支持。

实际运行游戏时候,可以用-k参数(<3)来选择Ghost个数。

与gameState.getNumAgents()对接,可以模拟多个Ghost,从而选择威胁最大的那个作为最终的min节点。

class MinimaxAgent(MultiAgentSearchAgent):
def getAction(self, gameState):
def max_value(state, currentDepth):
# 当前深度加一
currentDepth=currentDepth+1
# 若当前状态已经赢了或输了 或者 已经到达了规定的深度
if state.isWin() or state.isLose() or currentDepth == self.depth:
return self.evaluationFunction(state)
# 初始化v
v= float('-Inf')
# 对每个min分支求max
for pAction in state.getLegalActions(0):
v=max(v, min_value(state.generateSuccessor(0, pAction), currentDepth, 1))
return v
def min_value(state, currentDepth, ghostNum):
# 若当前状态已经赢了或输了
if state.isWin() or state.isLose():
return self.evaluationFunction(state)
# 初始化v
v=float('Inf')
# 对每个max分支求min 其中有多个Ghost 所有多个Ghost分支
for pAction in state.getLegalActions(ghostNum):
if ghostNum == gameState.getNumAgents()-1:
#所有Ghost的min找完了 开始找下一个max
v=min(v, max_value(state.generateSuccessor(ghostNum, pAction), currentDepth))
else:
#继续下一个Ghost
v=min(v, min_value(state.generateSuccessor(ghostNum, pAction), currentDepth, ghostNum+1))
return v # pacman下一个状态可能的行动
Pacman_Actions = gameState.getLegalActions(0) maximum = float('-Inf')
result = '' # 针对下一个状态 寻找获胜概率最高的move
for action in Pacman_Actions:
if(action != "Stop"):
currentDepth = 0
# 而所有的Ghost希望胜利概率最低的选择
currentMax = min_value(gameState.generateSuccessor(0, action), currentDepth , 1)
if currentMax > maximum:
maximum=currentMax
result =action
return result

当开始跑测试的时候,就发现在下面参数的情况下,走一步已经非常吃力了,其时间复杂度过高。

运行命令python pacman.py -p MinimaxAgent -k 2 -a depth=4

输出每步决策所消耗的时间,每步大致花费时间如下,Time的单位为ms:

Go  East Value is  43.0  Time:  1994.49194336
Go East Value is 50.0 Time: 6805.0690918
Go East Value is 58.0 Time: 4821.87817383
Go East Value is 67.0 Time: 3456.60791016
Go North Value is 76.0 Time: 1419.2019043
Go North Value is 63.0 Time: 3504.87597656
Go East Value is 72.0 Time: 2485.83081055
Go East Value is 103.0 Time: 1185.18701172

每步所花费时间大概在3s左右,当三个Agent的选择均非常多的情况下,每步所消耗的时间有可能会到达6s

急需AlphaBate剪枝,下面加入AlphaBate剪枝。

Part 3 : Alpha-Bate 剪枝

class AlphaBetaAgent(MultiAgentSearchAgent):
def getAction(self, gameState):
def max_value(state, alpha, beta, currentDepth):
# 当前深度加一
currentDepth=currentDepth+1
# 若当前状态已经赢了或输了 或者 已经到达了规定的深度
if state.isWin() or state.isLose() or currentDepth == self.depth:
return self.evaluationFunction(state)
v=float('-Inf')
# 对每个min分支求max
for pAction in state.getLegalActions(0):
if pAction!="Stop":
v=max(v, min_value(state.generateSuccessor(0, pAction), alpha, beta, currentDepth, 1))
# 若已经比beta要大了 就没有搜索下去的必要了
if v >= beta:
return v
# 更新alpha的值
alpha=max(alpha, v)
return v
def min_value(state, alpha, beta, currentDepth, ghostNum):
# 若当前状态已经赢了或输了
if state.isWin() or state.isLose():
return self.evaluationFunction(state)
# 初始化v
v=float('Inf')
# 对每个max分支求min 其中有多个Ghost 所有多个Ghost分支
for pAction in state.getLegalActions(ghostNum):
if ghostNum == gameState.getNumAgents()-1:
# 所有Ghost的min找完了 开始找下一个max
v=min(v, max_value(state.generateSuccessor(ghostNum, pAction), alpha, beta, currentDepth))
else:
# 继续下一个Ghost
v=min(v,
min_value(state.generateSuccessor(ghostNum, pAction), alpha, beta, currentDepth, ghostNum+1))
# 若比alpha还要小了 就没搜索的必要了
if v <= alpha:
return v
# 更新beta的值
beta=min(beta, v)
return v
# pacman下一个状态可能的行动
pacmanActions=gameState.getLegalActions(0)
maximum=float('-Inf')
# 初始化alpha bate
alpha=float('-Inf')
beta=float('Inf')
maxAction='' # 针对下一个状态 寻找获胜概率最高的move
for action in pacmanActions:
if action!="Stop":
currentDepth=0
# 而所有的Ghost希望胜利概率最低的选择
currentMax=min_value(gameState.generateSuccessor(0, action), alpha, beta, currentDepth, 1)
if currentMax > maximum:
maximum=currentMax
maxAction=action
print maximum
return maxAction

利用了AlphaBate之后 在同样的参数下 运行速度明显增加。

但是由于效果还是不好,感觉是系统提供的启发函数不太完美。系统提供的接口如下:

class MultiAgentSearchAgent(Agent):
def __init__(self, evalFn = 'scoreEvaluationFunction', depth = '2'):
self.index = 0 # Pacman is always agent index 0
self.evaluationFunction = util.lookup(evalFn, globals())
self.depth = int(depth)

在第一部分的基础反射启发函数基础上,修改默认的启发函数如下:

def scoreEvaluationFunction(currentGameState,cur_score):
# 获取food所在的所有wall
wallList=currentGameState.getWalls().asList()
Pos = currentGameState.getPacmanPosition()
# 获取食物在图中的分布(二维数组,有失误为T没食物为F)
curFood=currentGameState.getFood()
# 获取Ghost的位置
GhostStates=currentGameState.getGhostStates()
# 获取吃超级豆子之后 Ghost害怕还剩余的时间
scaredTimes=[ghostState.scaredTimer for ghostState in GhostStates]
# 对这个选择评估的分数
currscore=0
# 根据Ghost所在的位置,获取与当前位置的距离
ghost_distances=[]
for gs in GhostStates:
ghost_distances+=[manhattanDistance(gs.getPosition(), Pos)]
ghost_index = 0;
min_ghost_distances = min(ghost_distances);
is_scared = False
for time in scaredTimes:
if time != 0:
is_scared = True
else:
is_scared = False
break
# 获取food所在的所有pos
curfoodList=curFood.asList()
# 保存food的距离
food_distences=[]
# 获取所有食物到达当前位置的距离
for foodpos in curfoodList:
food_distences+=[manhattanDistance(Pos, foodpos)]
# 对食物的距离取反
inverse_food_distences=0;
if len(food_distences) > 0 and min(food_distences) > 0:
inverse_food_distences=1.0 / min(food_distences) if is_scared and min_ghost_distances!=0:
# if min_ghost_distances < 10:
# min_ghost_distances = 800 min_ghost_distances
# else:
# min_ghost_distances = 600 min_ghost_distances
print "Ghost Scared!"
min_ghost_distances = min_ghost_distances * 0.8
# 考虑了ghost与当前的距离,其权值更大
if min(ghost_distances) == 0:
currscore+=inverse_food_distences
else:
currscore+= min_ghost_distances * (float(inverse_food_distences))
# 获取当前系统判定的分数 又可能当前吃到了豆子 分数更高些
currscore+=currentGameState.getScore()
return currscore

测试命令 python pacman.py -p AlphaBetaAgent -k 2 -a depth=4

输出每步决策所消耗的时间,每步大致花费时间如下,Time的单位为ms:

Go  East Value is  43.0  Time:  215.517822266
Go East Value is 50.0 Time: 820.08203125
Go East Value is 58.0 Time: 712.662841797
Go East Value is 69.0 Time: 320.084960938
Go North Value is 78.0 Time: 1038.60400391
Go North Value is 66.0 Time: 862.83203125
Go East Value is 76.0 Time: 774.194091797
Go East Value is 108.0 Time: 357.637207031
Go East Value is 113.5 Time: 812.993164062
Go North Value is 126.0 Time: 402.672851562

每步用时比没有剪枝的情况减少了一半以上

正常测试平均在1500 point左右,但仍然存在以下问题

  • 当两个Agent分别在Pacman的两边或两边存在同样数量的豆子时,左右徘徊
  • 因为使用MinMax博弈树 而Ghost在离Pacman足够远时威胁很小
  • 导致Agent Pacman在豆子吃了大部分的时候非常谨慎
  • 参数调配问题

由于这些问题,导致了Pacman在某些情况下依旧显得不够智能。在该问题的实际情况中:

  • Ghost其实并没有博弈的概念,所以大部分猜想是浪费时间的

  • Pacman游戏其实只需要局部考虑,无需过多全局考虑,也就是说:

    当Ghost离Agent足够远的时候,其实Ghost的行动对于Pacman影响不大,没必要过多考虑。

    但是比如围棋博弈,这种全局观念就很重要了。

  • 启发函数还是人为为Ghost制定反射

算法学习:Pac-Man的简单对抗的更多相关文章

  1. OpenCV中Camshitf算法学习(补充)

    结合OpenCV中Camshitf算法学习,做一些简单的补充,包括: 实现全自动跟随的一种方法 参考opencv中的相关demo,可以截取目标物体的图片,由此预先计算出其色彩投影图,用于实际的目标跟随 ...

  2. Python之路,Day21 - 常用算法学习

    Python之路,Day21 - 常用算法学习   本节内容 算法定义 时间复杂度 空间复杂度 常用算法实例 1.算法定义 算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的 ...

  3. 算法学习之BFS、DFS入门

    算法学习之BFS.DFS入门 0x1 问题描述 迷宫的最短路径 给定一个大小为N*M的迷宫.迷宫由通道和墙壁组成,每一步可以向相邻的上下左右四格的通道移动.请求出从起点到终点所需的最小步数.如果不能到 ...

  4. 二次剩余Cipolla算法学习笔记

    对于同余式 \[x^2 \equiv n \pmod p\] 若对于给定的\(n, P\),存在\(x\)满足上面的式子,则乘\(n\)在模\(p\)意义下是二次剩余,否则为非二次剩余 我们需要计算的 ...

  5. 第四百一十五节,python常用排序算法学习

    第四百一十五节,python常用排序算法学习 常用排序 名称 复杂度 说明 备注 冒泡排序Bubble Sort O(N*N) 将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮 ...

  6. Kosaraju算法学习

    Kosaraju 算法学习 序 这星期捣鼓了一个新的算法--Kosaraju算法 今天分享给大家 简介 Kosaraju算法,其实与tarjan算法差不多.但是码量较小,容易记忆.其时间复杂度与tar ...

  7. 【转载】K-NN算法 学习总结

    声明:作者:会心一击 出处:http://www.cnblogs.com/lijingchn/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接, ...

  8. 算法学习之快速排序的C语言实现

    近几天在学习简单算法,今天看了一个快速排序和堆排序,堆排序还没搞懂,还是先把快速排序搞清楚吧 教程网上一艘一大堆,这里选择一个讲的比较通俗的的一个吧: http://blog.csdn.net/mor ...

  9. 【算法学习】manacher

    manacher太水了. 这篇blog不能称作算法学习,因为根本没有介绍…… 就贴个模板,太简单了…… #include<cstdio> #include<cstring> # ...

随机推荐

  1. NYOJ--114--某种序列(大数)

    某种序列 时间限制:3000 ms  |  内存限制:65535 KB 难度:4   描述 数列A满足An = An-1 + An-2 + An-3, n >= 3 编写程序,给定A0, A1 ...

  2. zabbix java api

    zabbix java api zabbix官方的api文档地址:https://www.zabbix.com/documentation/3.0/manual/api Zabbix功能 概观 Zab ...

  3. Alpha版与Beta版

    简单说说这两个词的意思,以后会稍加更多的补充. Alpha版意在对少数主要客户和市场进行数量有限的分发,用于演示目的的早期构造.其无意在实际环境中使用.使用Alpha版的所有人员必须了解确切内容和质量 ...

  4. 浅析MySQL中的Index Condition Pushdown (ICP 索引条件下推)和Multi-Range Read(MRR 索引多范围查找)查询优化

    本文出处:http://www.cnblogs.com/wy123/p/7374078.html(保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错误 ...

  5. android学习ViewFlipper的使用

    android系统自带的多页面管理控件,它可以实现子页面的自动切换 1,为ViewFlipper添加View 静态导入:在layout布局文件中直接导入 动态导入:通过addview方法进行导入 2, ...

  6. vbs系统监控

    vbs CPU 内存 硬盘监控脚本 On Error Resume Next Dim dwTotalMem, dwAvailMem, totalvolumn, freespace Const szRo ...

  7. Windows Server 2012 删除IIS之后 重新启动 桌面不出来 只出现一个命令提示框 解决方法

    今天本来准备卸载 再重新安装一下IIS的,然后卸载的时候 可能是不小心 把 .net framework 给卸掉了 .net framework 带着powershell 所以卸掉之后 桌面快捷程序都 ...

  8. Vue.js 入门

    背景 为了学习spring,准备写一个通讯录demo,实现增删改查功能. 前端页面同事推荐用vue.js,因为简单快速,当然前提是基于你对前端的html,css,js有一定的了解 资料 vue.js ...

  9. 读懂javascript深拷贝与浅拷贝

    1. 认识深拷贝和浅拷贝 javascript中一般有按值传递和按引用传递两种复制,按值传递的是基本数据类型(Number,String,Boolean,Null,Undefined),一般存放于内存 ...

  10. SpringMVC详解(五)------参数绑定

    参数绑定,简单来说就是客户端发送请求,而请求中包含一些数据,那么这些数据怎么到达 Controller ?这在实际项目开发中也是用到的最多的,那么 SpringMVC 的参数绑定是怎么实现的呢?下面我 ...