一、项目介绍
- 项目介绍网页
- 项目代码下载
- 本项目是采用Berkeley的CS188课程内容实习二的内容,在这个项目中,我们将为经典版本的Pacman 设计自动算法,包括幽灵。在此过程中,我们将实现 minimax 和 expectimax 搜索并尝试评估函数设计
- 完成作业只需要完成5个题目,按照项目介绍的步骤进行完成,主要是在multiAgents.py文件中进行补充代码
二、代码详情
1、Question1:Reflex Agent
略
2、Question2:Minimax
- 套用ppt上的minmax算法伪代码。
- pacman作为max玩家,要在各ghost做出对于ghost最优的action下找到最优应对。
- 核心的是min_value和max_value函数互相递归调用。
- 麻烦的是本游戏中ghost可能不止一个,min_value函数中我采用递归的方式先得到每个ghost各走一步后的所有state,并对每个state评max_value,选出最小者。
- 另外要注意pacman不应该采取"stop"动作,否则无法通过测试(因为autograder认为总有比stop更优的解)
class MinimaxAgent(MultiAgentSearchAgent):
"""
Your minimax agent (question 2)
"""
def getAction(self, gameState):
"""
Returns the minimax action from the current gameState using self.depth
and self.evaluationFunction.
Here are some method calls that might be useful when implementing minimax.
gameState.getLegalActions(agentIndex):
Returns a list of legal actions for an agent
agentIndex=0 means Pacman, ghosts are >= 1
gameState.generateSuccessor(agentIndex, action):
Returns the successor game state after an agent takes an action
gameState.getNumAgents():
Returns the total number of agents in the game
gameState.isWin():
Returns whether or not the game state is a winning state
gameState.isLose():
Returns whether or not the game state is a losing state
"""
"*** YOUR CODE HERE ***"
GhostIndex = [i for i in range(1, gameState.getNumAgents())]
#pacman作为max玩家,要在各ghost做出对于ghost最优的action下找到最优应对。
#核心的minvalue和maxvalue函数互相递归调用。
#麻烦的是本游戏中ghost可能不止一个,
#minvalue函数中我采用递归的方式先得到每个ghost各走一步后的所有state,并对每个state评maxvalue,选出最小者。
# 根据当前状态,判断游戏是否胜利或者结束
def term(state, d):
return state.isWin() or state.isLose() or d == self.depth
# 采用递归方式把ghost每个走过的状态检查一遍
def min_value(state, d, ghost): # minimizer
if term(state, d):
return self.evaluationFunction(state)
v = 10000000000000000 # β初始值为无穷大
for action in state.getLegalActions(ghost):
if ghost == GhostIndex[-1]: # 递归的查找最小值,如果ghost==ghostindex[-1]的话,则在大数中进行查找,反之
v = min(v, max_value(state.generateSuccessor(ghost, action), d + 1))
else:
v = min(v, min_value(state.generateSuccessor(ghost, action), d, ghost + 1))
# print(v)
return v
def max_value(state, d): # maximizer
if term(state, d):
return self.evaluationFunction(state)
v = -10000000000000000 # α初始值为无穷小
for action in state.getLegalActions(0):
# 递归查找最大值
v = max(v, min_value(state.generateSuccessor(0, action), d, 1))
# print(v)
return v
res = [(action, min_value(gameState.generateSuccessor(0, action), 0, 1)) for action in
gameState.getLegalActions(0)]
res.sort(key=lambda k: k[1])
return res[-1][0]
# util.raiseNotDefined()
3、Question3: Alpha-Beta Pruning
- 在minimax基础上加入减枝操作。
- 注意不能先把每个ghost各走一步后的所有state都找出来后再排查,因为这样会展开大量多余结点。
- 应该得到一个state后立即判断是否需要剪枝,如果剪枝了则不要做后面的展开了,会省很多资源。
- 注意剪纸比较的条件中没有等号。
class AlphaBetaAgent(MultiAgentSearchAgent):
"""
Your minimax agent with alpha-beta pruning (question 3)
"""
def getAction(self, gameState):
"""
Returns the minimax action using self.depth and self.evaluationFunction
"""
"*** YOUR CODE HERE ***"
GhostIndex = [i for i in range(1, gameState.getNumAgents())]
inf = 1e100 # 无穷大
# 不能先把每个ghost各走一步后的所有state都找出来后再排查,因为这样会展开大量多余结点。
# 应该得到一个state后立即判断是否需要剪枝,如果剪枝了则不要做后面的展开了,会省很多资源
# 根据当前状态,判断游戏是否胜利或者结束
def term(state, d):
return state.isWin() or state.isLose() or d == self.depth
def min_value(state, d, ghost, A, B): # minimizer
if term(state, d):
return self.evaluationFunction(state)
v = inf # β初始值为无穷大
for action in state.getLegalActions(ghost):
# 需要在递归过程中一边展开一遍检查,及时剪枝
if ghost == GhostIndex[-1]: # 下一个是的pacman最大值
v = min(v, max_value(state.generateSuccessor(ghost, action), d + 1, A, B))
else: # 下一个是的next-ghost最小值
v = min(v, min_value(state.generateSuccessor(ghost, action), d, ghost + 1, A, B))
if v < A: # 剪枝
return v
B = min(B, v)
return v
def max_value(state, d, A, B): # maximizer
if term(state, d):
return self.evaluationFunction(state)
v = -inf # α初始值为无穷小
for action in state.getLegalActions(0):
v = max(v, min_value(state.generateSuccessor(0, action), d, 1, A, B))
if v > B:
return v
A = max(A, v)
return v
def alphabeta(state):
v = -inf
act = None
A = -inf
B = inf
for action in state.getLegalActions(0): # 求最大值
tmp = min_value(gameState.generateSuccessor(0, action), 0, 1, A, B)
if v < tmp: # 和 v = max(v, tmp) 相同
v = tmp
act = action
if v > B: # 剪枝
return v
A = max(A, tmp)
return act
return alphabeta(gameState)
util.raiseNotDefined()
4、Question4:Expectimax
- 本题考虑了不是每一次ghost都能做出对ghost最好的动作,因此不是对ghost采取action后所有state的各值取最小,而是对他们求期望。
- 由题意,ghost采取各action概率相同,所以只需要对各state的值求平均即可,其他部分和minimax中的一致。
class ExpectimaxAgent(MultiAgentSearchAgent):
"""
Your expectimax agent (question 4)
"""
def getAction(self, gameState):
"""
Returns the expectimax action using self.depth and self.evaluationFunction
All ghosts should be modeled as choosing uniformly at random from their
legal moves.
"""
"*** YOUR CODE HERE ***"
GhostIndex = [i for i in range(1, gameState.getNumAgents())]
# 本题考虑了不是每一次ghost都能做出对ghost最好的动作,因此不是对ghost采取action后所有state的各值取最小,而是对他们求期望。
# 由题意,ghost采取各action概率相同,所以只需要对各state的值求平均即可。其他部分和minimax中的一致。
def term(state, d):
return state.isWin() or state.isLose() or d == self.depth
def exp_value(state, d, ghost): # minimizer
if term(state, d):
return self.evaluationFunction(state)
v = 0
# 每一种概率为1/count
# count为pacam走了一步之后,ghost可以走成多少种状态
# 有可能pacman走完游戏就结束了,此时count = 0,此时只需要直接取各sumvalue最大值即可
prob = 1 / len(state.getLegalActions(ghost))
for action in state.getLegalActions(ghost):
if ghost == GhostIndex[-1]:
v += prob * max_value(state.generateSuccessor(ghost, action), d + 1)
else:
v += prob * exp_value(state.generateSuccessor(ghost, action), d, ghost + 1)
# print(v)
return v
def max_value(state, d): # maximizer
if term(state, d):
return self.evaluationFunction(state)
v = -10000000000000000
for action in state.getLegalActions(0):
v = max(v, exp_value(state.generateSuccessor(0, action), d, 1))
# print(v)
return v
res = [(action, exp_value(gameState.generateSuccessor(0, action), 0, 1)) for action in
gameState.getLegalActions(0)]
res.sort(key=lambda k: k[1])
return res[-1][0]
util.raiseNotDefined()
5、Question5:Evaluation Function
- 题目要求不是简单地用getSocre()函数作为评分标准。
- 进过反复调参进行测试,最后测试出一种能够通过q5的评价函数
def betterEvaluationFunction(currentGameState):
"""
Your extreme ghost-hunting, pellet-nabbing, food-gobbling, unstoppable
evaluation function (question 5).
DESCRIPTION: based on number 1, added situation values about closestghostdistance, capsules, etc.
"""
"*** YOUR CODE HERE ***"
newPos = currentGameState.getPacmanPosition()
newFood = currentGameState.getFood()
newGhostStates = currentGameState.getGhostStates()
newScaredTimes = [ghostState.scaredTimer for ghostState in newGhostStates]
walls = currentGameState.getWalls()
# 如果不是新的ScaredTimes,则新状态为ghost:返回最低值
newFood = newFood.asList()
ghostPos = [(G.getPosition()[0], G.getPosition()[1]) for G in newGhostStates]
scared = min(newScaredTimes) > 0
if currentGameState.isLose():
return float('-inf')
if newPos in ghostPos:
return float('-inf')
# 如果不是新的ScaredTimes,则新状态为ghost:返回最低值
closestFoodDist = sorted(newFood, key=lambda fDist: util.manhattanDistance(fDist, newPos))
closestGhostDist = sorted(ghostPos, key=lambda gDist: util.manhattanDistance(gDist, newPos))
score = 0
fd = lambda fDis: util.manhattanDistance(fDis, newPos)
gd = lambda gDis: util.manhattanDistance(gDis, newPos)
if gd(closestGhostDist[0]) <3:
score-=300
if gd(closestGhostDist[0]) <2:
score-=1000
if gd(closestGhostDist[0]) <1:
return float('-inf')
if len(currentGameState.getCapsules()) < 2:
score+=100
if len(closestFoodDist)==0 or len(closestGhostDist)==0 :
score += scoreEvaluationFunction(currentGameState) + 10
else:
score += ( scoreEvaluationFunction(currentGameState) + 10/fd(closestFoodDist[0]) + 1/gd(closestGhostDist[0]) + 1/gd(closestGhostDist[-1]) )
return score
# util.raiseNotDefined()
三、测试
1、Reflex Agent
输入如下指令进行测试:
python autograder.py -q q1 --no-graphics
测试结果:
可以看到10个测试案例都通过了,平均分为:1218.3
2、Minimax
输入如下指令:
python autograder.py -q q2 --no-graphics
测试结果:
可以看到测试案例都通过了,但是由于MinimaxAgent测试还是有缺陷
所以最终得分只有:84
3、Alpha-Beta Pruning
输入如下指令:
python autograder.py -q q3 --no-graphics
测试结果:
可以看到测试案例都通过了,但是由于smallClassic测试还是有缺陷
所以最终得分只有:84
4、Expectimax
输入如下指令:
python autograder.py -q q4 --no-graphics
测试结果:
最终得分:84
5、Evaluation Function
输入如下指令:
python autograder.py -q q5 --no-graphics
测试结果:
最终平均得分:1107.4
四、算法对比
1、在smallClassic、 depth=4情况下分别使用 MiniMax、 Alpha-Beta
Pruning、 Expectimax进行游戏(比较同一运行规模下,运行的时间)。
- (1)MinMax算法表现:
python pacman.py -p MinimaxAgent -l minimaxClassic -a depth=4
- (2)Alpha-Beta Pruning算法表现:
python pacman.py -p AlphaBetaAgent -l smallClassic -a depth=4
- (3)Expectimax算法表现:
python pacman.py -p ExpectimaxAgent -l smallClassic -a depth=4
发现:
- 在运行时间上面,同样深度情况下 MiniMax和 Expectimax展开结点数和时间基本持平,三种算法的运行时间很明显能感到Alpha- Beta Pruning要快于其他两个,说明剪枝的重要性。
2、在smallClassic、 depth=2(由于选择depth=4会花费很长时间)情况下分别使用 MiniMax、 Alpha-Beta Pruning、 Expectimax算法各进行10次游戏,然后比较胜率(比较同一运行规模下,同一运行次数,比较成功率)。
- (1)MinMax算法表现:
python pacman.py --frameTime 0 -p MinimaxAgent -a depth=0 -l smallClassic -n 10
- (2)Alpha-Beta Pruning算法表现:
python pacman.py --frameTime 0 -p AlphaBetaAgent -a depth=0 -l smallClassic -n 10
- (3)Expectimax算法表现:
python pacman.py --frameTime 0 -p ExpectimaxAgent -a depth=0 -l smallClassic -n 10
发现:
- 1.它们表现都不大好。
- 2.ExpectimaxAgent在胜率和均分上表现稍微好一点,但几乎没什么区别。
3、在trappedClass问题中比较:
- Alpha-Beta Pruning Agent在 trappedClassic 10 次胜负情况:
- Expectimax Agent在 trappedClassic10次胜负情况:
发现:
- 1.在 trappedClassic问题中ExpectimaxAgent体现出了巨大的优势。
- 2.Alpha- Beta Pruning Agent总是输,而 Expectimax Agent有相当大概率赢。这是因为, Alpha- Beta Pruning Agent总是认为 ghost会采取最佳对于 ghost策略,即 pacman一定会被 ghost吃掉,于是 pacman干脆就撞向最近的 ghost尽早结束游戏减少时间扣分项,而实际上图中蓝色 ghost有一定几率往右下角走。
五、总结
- Alpha- Beta Pruning Agent能在与 Minimax Agent得出同样结果情况下大大减少了需要展开的结点数,但是两者都假设了对面总做出最好的选择,因此大大限制了自己所能采取的步骤。而 Expectimax Agent能比较好地处理对面不是总 optima1的情况,但是相比Alpha- Beta Pruning Agent所消耗的资源就大得多。除了 Agent本身的设计外评分函数对 Agent的表现起了至关重要的作用,设计评分函数时要考虑“趋利避害”之间的平衡,核心问題是对于玩家说什么是“好的”.遗憾的是我设计的函数比较简单粗暴,因此表现不是很好。
参考博客和网页:
https://www.bilibili.com/read/cv7123775/
https://inst.eecs.berkeley.edu/~cs188/fa21/project2/
https://github.com/DylanCope/CS188-Multi-Agent