proj2 finished
This commit is contained in:
91
proj2/AI_Concepts_Q1-Q5.md
Normal file
91
proj2/AI_Concepts_Q1-Q5.md
Normal file
@ -0,0 +1,91 @@
|
||||
# CS188 Project 2: Multi-Agent Pacman - 人工智能知识点总结
|
||||
|
||||
这份文档总结了在 Berkeley CS188 Project 2 (Multi-Agent Search) 中涉及的核心人工智能概念。通过完成 Q1 到 Q5,我们从简单的反射代理进化到了能够进行对抗性推理和处理不确定性的复杂代理。
|
||||
|
||||
## Q1: Reflex Agent (反射代理)
|
||||
|
||||
### 核心概念
|
||||
* **反射代理 (Reflex Agent)**: 这种代理不进行显式的规划或搜索。它仅根据**当前状态 (Current State)** 的直接感知信息来做出决策。
|
||||
* **状态-动作评估 (State-Action Evaluation)**: 我们评估的是“在当前状态下采取某个动作后会发生什么”。
|
||||
* **理性 (Rationality)**: 即使没有通过搜索看得很远,反射代理通过设计良好的**评估函数 (Evaluation Function)** 也能表现出看似理性的行为(例如吃食物、躲避幽灵)。
|
||||
|
||||
### 关键实现点
|
||||
* **特征提取 (Feature Extraction)**: 我们从游戏状态中提取关键特征,如“距离最近食物的距离”、“距离最近幽灵的距离”、“剩余胶囊数量”等。
|
||||
* **线性组合 (Linear Combination)**: 评估函数通常是这些特征的加权和。
|
||||
* $Value(state, action) = w_1 \cdot f_1 + w_2 \cdot f_2 + \dots$
|
||||
* **局限性**: 反射代理容易陷入**局部最优 (Local Optima)**。例如,为了吃一个豆子而走进死胡同,因为它没有“向前看”几步的能力。
|
||||
|
||||
---
|
||||
|
||||
## Q2: Minimax (极小化极大算法)
|
||||
|
||||
### 核心概念
|
||||
* **对抗搜索 (Adversarial Search)**: 在多智能体环境中,其他代理(幽灵)的目标可能与我们(Pacman)相反。这是一个**零和博弈 (Zero-Sum Game)**。
|
||||
* **博弈树 (Game Tree)**: 游戏的所有可能状态构成的树状结构。
|
||||
* **Minimax 策略**:
|
||||
* **MAX 层 (Pacman)**: 总是选择能让自己收益最大化的动作。
|
||||
* **MIN 层 (Ghosts)**: 假设对手(幽灵)是完美的,总是选择能让 Pacman 收益最小化(即对幽灵最有利)的动作。
|
||||
|
||||
### 算法流程
|
||||
1. **深度优先搜索 (DFS)**: 递归地向下搜索博弈树,直到达到预定的**深度 (Depth)** 或**终止状态 (Terminal State)**(输/赢)。
|
||||
2. **状态评估**: 在叶子节点使用评估函数计算分值。
|
||||
3. **回溯 (Backtracking)**:
|
||||
* 在 MIN 节点,取子节点的最小值传递给父节点。
|
||||
* 在 MAX 节点,取子节点的最大值传递给父节点。
|
||||
|
||||
### 意义
|
||||
Minimax 保证了在对手最优应对的情况下,我们能得到的最好的结果(下限)。它是保守的,假设情况总是最坏的。
|
||||
|
||||
---
|
||||
|
||||
## Q3: Alpha-Beta Pruning (Alpha-Beta 剪枝)
|
||||
|
||||
### 核心概念
|
||||
* **剪枝 (Pruning)**: 在不改变 Minimax 最终决策结果的前提下,忽略(剪掉)那些不需要探索的博弈树分支。
|
||||
* **优化效率**: 剪枝可以极大地减少搜索节点的数量,允许我们在相同的时间内搜索得更深。
|
||||
|
||||
### 关键参数
|
||||
* **$\alpha$ (Alpha)**: 目前为止,MAX 节点(Pacman)在任意路径上发现的**最好**(最大)的选择。即 MAX 的下界。
|
||||
* **$\beta$ (Beta)**: 目前为止,MIN 节点(Ghosts)在任意路径上发现的**最好**(最小)的选择。即 MIN 的上界(对于 MAX 来说是上界)。
|
||||
|
||||
### 剪枝逻辑
|
||||
* **Min 节点剪枝**: 如果某个 Min 节点发现一个值 $v \leq \alpha$,它会立即停止搜索其他子节点。因为 MAX 的父节点已经有一个比 $v$ 更好的选择 $\alpha$,所以 MAX 绝不会选择通向这个 Min 节点的路径。
|
||||
* **Max 节点剪枝**: 如果某个 Max 节点发现一个值 $v \geq \beta$,它会立即停止。因为 MIN 的父节点已经有一个比 $v$ 更让 MAX 难受的选择 $\beta$(更小的值),MIN 绝不会让游戏走到这个 Max 节点产生 $v$ 的情况。
|
||||
|
||||
---
|
||||
|
||||
## Q4: Expectimax (期望最大化算法)
|
||||
|
||||
### 核心概念
|
||||
* **不确定性 (Uncertainty)**: 在现实中,对手并不总是完美的(最优的),或者对手的行为具有随机性。
|
||||
* **概率模型**: 我们不再假设幽灵是“敌人”,而是将其视为环境中的随机因素。
|
||||
* **期望值 (Expected Value)**: 我们计算的是采取某个动作后的**平均收益**,而不是最差情况下的收益。
|
||||
|
||||
### 算法变化
|
||||
* **MAX 节点**: 依然选择最大值(Pacman 仍然想赢)。
|
||||
* **CHANCE (Expectation) 节点**: 替代了 Minimax 中的 MIN 节点。它的值是所有子节点值的**加权平均数**。
|
||||
* $V(s) = \sum_{s'} P(s'|s, a) \cdot V(s')$
|
||||
* 在本实验中,假设幽灵随机行动,即概率是均匀分布的。
|
||||
|
||||
### 意义
|
||||
* Expectimax 能够利用对手的非最优行为(犯错)。
|
||||
* 它愿意承担计算出的风险(Calculated Risk)。例如,Minimax 可能会因为害怕 1% 的死亡概率而放弃大奖,而 Expectimax 如果计算出期望收益够高,就会去冒险。
|
||||
|
||||
---
|
||||
|
||||
## Q5: Evaluation Function (评估函数设计)
|
||||
|
||||
### 核心概念
|
||||
* **启发式 (Heuristics)**: 当搜索树太深无法穷尽时,我们需要在有限深度处截断,并对非终局状态进行估值。
|
||||
* **状态评估 (State Evaluation)**: Q1 评估的是动作,Q5 评估的是静态的状态。
|
||||
|
||||
### 设计要素
|
||||
一个好的评估函数应该包含:
|
||||
1. **当前分数**: 游戏的基础目标。
|
||||
2. **食物距离**: 鼓励 Pacman 吃豆子(通常用距离的倒数, $1/distance$)。
|
||||
3. **胶囊与受惊幽灵**: 鼓励 Pacman 在幽灵受惊时去吃掉它们(高回报)。
|
||||
4. **生存惩罚**: 如果距离非受惊幽灵太近,给予极大的负分。
|
||||
|
||||
### 线性评估函数结构
|
||||
$$ Eval(s) = w_1 \cdot Feature_1(s) + w_2 \cdot Feature_2(s) + \dots + w_n \cdot Feature_n(s) $$
|
||||
通过调整权重 $w$,我们定义了代理的“性格”(例如,是贪婪得分还是极其怕死)。
|
||||
130
proj2/README_CN.md
Normal file
130
proj2/README_CN.md
Normal file
@ -0,0 +1,130 @@
|
||||
# Project 2: Multi-Agent Pacman 实验报告
|
||||
|
||||
本实验旨在通过编写 Pacman 智能体来实践对抗搜索算法(Adversarial Search)。主要涉及 Reflex Agent(反射智能体)、Minimax(极大极小算法)、Alpha-Beta Pruning(Alpha-Beta 剪枝)以及 Expectimax(期望最大算法)和评估函数的设计。
|
||||
|
||||
## 实验环境
|
||||
- **操作系统**: Linux
|
||||
- **语言**: Python 3
|
||||
- **文件**: `multiAgents.py` (主要修改文件)
|
||||
|
||||
---
|
||||
|
||||
## Q1: Reflex Agent (反射智能体)
|
||||
|
||||
### 任务描述
|
||||
编写 `ReflexAgent` 类中的 `evaluationFunction` 方法。该智能体通过评估当前状态及其后续动作的得分来选择最佳动作。不仅要考虑吃豆子,还要避免碰到幽灵。
|
||||
|
||||
### 实现逻辑
|
||||
我们在 `evaluationFunction` 中综合考虑了以下因素:
|
||||
1. **当前分数 (`successorGameState.getScore()`)**: 基础得分。
|
||||
2. **食物距离**: 计算 Pacman 到最近食物的曼哈顿距离。距离越近,得分越高(使用倒数 `1.0 / (distance + 1)`)。
|
||||
3. **幽灵距离**:
|
||||
- 如果幽灵距离过近(小于 2 格)且处于非受惊状态,返回极低分(代表死亡风险)。
|
||||
- 如果幽灵处于受惊状态(Scared),则不用过于担心。
|
||||
|
||||
### 核心代码片段
|
||||
```python
|
||||
# 计算到最近食物的距离
|
||||
minFoodDist = min([util.manhattanDistance(newPos, food) for food in foodList])
|
||||
|
||||
# 避免幽灵
|
||||
for i, ghostState in enumerate(newGhostStates):
|
||||
# ... (省略部分代码)
|
||||
if dist < 2 and newScaredTimes[i] == 0:
|
||||
return -999999
|
||||
|
||||
return successorGameState.getScore() + 1.0 / (minFoodDist + 1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Q2: Minimax (极大极小算法)
|
||||
|
||||
### 任务描述
|
||||
在 `MinimaxAgent` 类中实现 Minimax 算法。该算法假设对手(幽灵)也是最优的,即 Pacman 试图最大化分数,而幽灵试图最小化分数。
|
||||
|
||||
### 知识点讲解
|
||||
Minimax 算法是一种递归算法,用于在两人零和博弈中找到最优策略。
|
||||
- **MAX 层 (Pacman)**: 选择能获得最大评估值的动作。
|
||||
- **MIN 层 (Ghosts)**: 选择会导致最小评估值的动作。
|
||||
- **深度 (Depth)**: 搜索树的深度。本实验中,一层(Ply)包含 Pacman 的一步和所有幽灵的一步。
|
||||
|
||||
### 实现细节
|
||||
我们实现了一个递归函数 `minimax(agentIndex, depth, gameState)`:
|
||||
- **终止条件**: 达到最大深度 `self.depth` 或游戏结束(胜/负)。
|
||||
- **Agent 轮转**: `agentIndex` 从 0 (Pacman) 到 `numAgents - 1`。当 `agentIndex` 回到 0 时,深度 `depth` 加 1。
|
||||
- **状态值传递**: 递归向上层传递最优值(Max 或 Min)。
|
||||
|
||||
---
|
||||
|
||||
## Q3: Alpha-Beta Pruning (Alpha-Beta 剪枝)
|
||||
|
||||
### 任务描述
|
||||
在 `AlphaBetaAgent` 类中实现带有 Alpha-Beta 剪枝的 Minimax 算法,以提高搜索效率。
|
||||
|
||||
### 知识点讲解
|
||||
Minimax 算法会搜索整个博弈树,计算量巨大。Alpha-Beta 剪枝通过维护两个值 `alpha` 和 `beta` 来忽略那些不需要搜索的分支:
|
||||
- **Alpha (α)**: MAX 节点目前找到的最好(最大)值的下界。
|
||||
- **Beta (β)**: MIN 节点目前找到的最好(最小)值的上界。
|
||||
- **剪枝规则**:
|
||||
- 在 MAX 节点,如果发现某分支的值 `v > beta`,则 MIN 父节点绝不会选择该分支(因为父节点已有更小的值 β),故剪枝。
|
||||
- 在 MIN 节点,如果发现 `v < alpha`,则 MAX 父节点绝不会选择该分支,故剪枝。
|
||||
|
||||
### 实现细节
|
||||
在 `alphaBeta` 递归函数中增加 `alpha` 和 `beta` 参数,并在循环中动态更新它们。注意不要在剪枝时改变节点的访问顺序,以通过 Autograder 的严格检查。
|
||||
|
||||
---
|
||||
|
||||
## Q4 (Optional/Part of Logic): Expectimax (期望最大算法)
|
||||
|
||||
*虽然任务主要要求 Q1-Q3,但也实现了 Expectimax 以备不时之需。*
|
||||
|
||||
### 知识点讲解
|
||||
Expectimax 假设对手(幽灵)不一定是最优的,而是随机行动。MIN 节点变为 CHANCE 节点,计算所有可能动作的期望值(平均值)。这更符合随机幽灵的行为模式。
|
||||
|
||||
---
|
||||
|
||||
## Q5: Evaluation Function (评估函数)
|
||||
|
||||
### 任务描述
|
||||
编写 `betterEvaluationFunction`,用于评估非终止状态的好坏。这是实现高性能智能体的关键。
|
||||
|
||||
### 设计思路
|
||||
我们需要根据当前状态特征给出一个数值评分,特征及其权重如下:
|
||||
1. **基础分数**: 游戏自带的得分。
|
||||
2. **食物距离 (权重 +10)**: 鼓励吃掉最近的食物。
|
||||
3. **胶囊距离 (权重 +20)**: 鼓励去吃能量胶囊。
|
||||
4. **受惊幽灵 (权重 +100)**: 如果幽灵被吓坏了,鼓励去吃掉它们(距离越近分越高)。
|
||||
5. **活跃幽灵 (权重 -1000)**: 如果幽灵正常且距离太近,给予极大的惩罚。
|
||||
6. **剩余食物数量 (权重 -4)**: 剩余越少越好。
|
||||
7. **剩余胶囊数量 (权重 -20)**: 剩余越少越好。
|
||||
|
||||
通过线性组合这些特征,Pacman 能够在复杂的环境中表现出色,既能躲避追捕,又能积极得分。
|
||||
|
||||
---
|
||||
|
||||
## 如何运行测试
|
||||
|
||||
可以使用 `autograder.py` 来验证各个问题的实现:
|
||||
|
||||
1. **测试 Q1 (Reflex Agent)**:
|
||||
```bash
|
||||
python autograder.py -q q1
|
||||
```
|
||||
|
||||
2. **测试 Q2 (Minimax)**:
|
||||
```bash
|
||||
python autograder.py -q q2
|
||||
```
|
||||
|
||||
3. **测试 Q3 (Alpha-Beta)**:
|
||||
```bash
|
||||
python autograder.py -q q3
|
||||
```
|
||||
|
||||
4. **测试 Q5 (Evaluation Function)**:
|
||||
```bash
|
||||
python autograder.py -q q5
|
||||
```
|
||||
|
||||
如果不希望显示图形界面(加快测试速度),可以添加 `--no-graphics` 参数。
|
||||
1
proj2/VERSION
Normal file
1
proj2/VERSION
Normal file
@ -0,0 +1 @@
|
||||
v1.004
|
||||
BIN
proj2/__pycache__/autograder.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/autograder.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/game.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/game.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/ghostAgents.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/ghostAgents.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/grading.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/grading.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/graphicsDisplay.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/graphicsDisplay.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/graphicsUtils.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/graphicsUtils.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/keyboardAgents.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/keyboardAgents.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/layout.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/layout.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/multiAgents.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/multiAgents.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/multiagentTestClasses.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/multiagentTestClasses.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/pacman.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/pacman.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/pacmanAgents.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/pacmanAgents.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/projectParams.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/projectParams.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/testClasses.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/testClasses.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/testParser.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/testParser.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/textDisplay.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/textDisplay.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj2/__pycache__/util.cpython-313.pyc
Normal file
BIN
proj2/__pycache__/util.cpython-313.pyc
Normal file
Binary file not shown.
362
proj2/autograder.py
Normal file
362
proj2/autograder.py
Normal file
@ -0,0 +1,362 @@
|
||||
# autograder.py
|
||||
# -------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
# imports from python standard library
|
||||
import grading
|
||||
import importlib.util
|
||||
import optparse
|
||||
import os
|
||||
import pprint
|
||||
import re
|
||||
import sys
|
||||
import projectParams
|
||||
import random
|
||||
random.seed(0)
|
||||
try:
|
||||
from pacman import GameState
|
||||
except:
|
||||
pass
|
||||
|
||||
# register arguments and set default values
|
||||
def readCommand(argv):
|
||||
parser = optparse.OptionParser(
|
||||
description='Run public tests on student code')
|
||||
parser.set_defaults(generateSolutions=False, edxOutput=False, gsOutput=False,
|
||||
muteOutput=False, printTestCase=False, noGraphics=False)
|
||||
parser.add_option('--test-directory',
|
||||
dest='testRoot',
|
||||
default='test_cases',
|
||||
help='Root test directory which contains subdirectories corresponding to each question')
|
||||
parser.add_option('--student-code',
|
||||
dest='studentCode',
|
||||
default=projectParams.STUDENT_CODE_DEFAULT,
|
||||
help='comma separated list of student code files')
|
||||
parser.add_option('--code-directory',
|
||||
dest='codeRoot',
|
||||
default="",
|
||||
help='Root directory containing the student and testClass code')
|
||||
parser.add_option('--test-case-code',
|
||||
dest='testCaseCode',
|
||||
default=projectParams.PROJECT_TEST_CLASSES,
|
||||
help='class containing testClass classes for this project')
|
||||
parser.add_option('--generate-solutions',
|
||||
dest='generateSolutions',
|
||||
action='store_true',
|
||||
help='Write solutions generated to .solution file')
|
||||
parser.add_option('--edx-output',
|
||||
dest='edxOutput',
|
||||
action='store_true',
|
||||
help='Generate edX output files')
|
||||
parser.add_option('--gradescope-output',
|
||||
dest='gsOutput',
|
||||
action='store_true',
|
||||
help='Generate GradeScope output files')
|
||||
parser.add_option('--mute',
|
||||
dest='muteOutput',
|
||||
action='store_true',
|
||||
help='Mute output from executing tests')
|
||||
parser.add_option('--print-tests', '-p',
|
||||
dest='printTestCase',
|
||||
action='store_true',
|
||||
help='Print each test case before running them.')
|
||||
parser.add_option('--test', '-t',
|
||||
dest='runTest',
|
||||
default=None,
|
||||
help='Run one particular test. Relative to test root.')
|
||||
parser.add_option('--question', '-q',
|
||||
dest='gradeQuestion',
|
||||
default=None,
|
||||
help='Grade one particular question.')
|
||||
parser.add_option('--no-graphics',
|
||||
dest='noGraphics',
|
||||
action='store_true',
|
||||
help='No graphics display for pacman games.')
|
||||
(options, args) = parser.parse_args(argv)
|
||||
return options
|
||||
|
||||
|
||||
# confirm we should author solution files
|
||||
def confirmGenerate():
|
||||
print('WARNING: this action will overwrite any solution files.')
|
||||
print('Are you sure you want to proceed? (yes/no)')
|
||||
while True:
|
||||
ans = sys.stdin.readline().strip()
|
||||
if ans == 'yes':
|
||||
break
|
||||
elif ans == 'no':
|
||||
sys.exit(0)
|
||||
else:
|
||||
print('please answer either "yes" or "no"')
|
||||
|
||||
|
||||
# TODO: Fix this so that it tracebacks work correctly
|
||||
# Looking at source of the traceback module, presuming it works
|
||||
# the same as the intepreters, it uses co_filename. This is,
|
||||
# however, a readonly attribute.
|
||||
def setModuleName(module, filename):
|
||||
functionType = type(confirmGenerate)
|
||||
classType = type(optparse.Option)
|
||||
|
||||
for i in dir(module):
|
||||
o = getattr(module, i)
|
||||
if hasattr(o, '__file__'):
|
||||
continue
|
||||
|
||||
if type(o) == functionType:
|
||||
setattr(o, '__file__', filename)
|
||||
elif type(o) == classType:
|
||||
setattr(o, '__file__', filename)
|
||||
# TODO: assign member __file__'s?
|
||||
# print i, type(o)
|
||||
|
||||
|
||||
#from cStringIO import StringIO
|
||||
|
||||
def loadModuleString(moduleSource):
|
||||
# Below broken, imp doesn't believe its being passed a file:
|
||||
# ValueError: load_module arg#2 should be a file or None
|
||||
#
|
||||
#f = StringIO(moduleCodeDict[k])
|
||||
#tmp = imp.load_module(k, f, k, (".py", "r", imp.PY_SOURCE))
|
||||
tmp = imp.new_module(k)
|
||||
exec(moduleCodeDict[k], tmp.__dict__)
|
||||
setModuleName(tmp, k)
|
||||
return tmp
|
||||
|
||||
|
||||
def loadModuleFile(moduleName, filePath):
|
||||
# https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
|
||||
spec = importlib.util.spec_from_file_location(moduleName, filePath)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def readFile(path, root=""):
|
||||
"Read file from disk at specified path and return as string"
|
||||
with open(os.path.join(root, path), 'r') as handle:
|
||||
return handle.read()
|
||||
|
||||
|
||||
#######################################################################
|
||||
# Error Hint Map
|
||||
#######################################################################
|
||||
|
||||
# TODO: use these
|
||||
ERROR_HINT_MAP = {
|
||||
'q1': {
|
||||
"<type 'exceptions.IndexError'>": """
|
||||
We noticed that your project threw an IndexError on q1.
|
||||
While many things may cause this, it may have been from
|
||||
assuming a certain number of successors from a state space
|
||||
or assuming a certain number of actions available from a given
|
||||
state. Try making your code more general (no hardcoded indices)
|
||||
and submit again!
|
||||
"""
|
||||
},
|
||||
'q3': {
|
||||
"<type 'exceptions.AttributeError'>": """
|
||||
We noticed that your project threw an AttributeError on q3.
|
||||
While many things may cause this, it may have been from assuming
|
||||
a certain size or structure to the state space. For example, if you have
|
||||
a line of code assuming that the state is (x, y) and we run your code
|
||||
on a state space with (x, y, z), this error could be thrown. Try
|
||||
making your code more general and submit again!
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def splitStrings(d):
|
||||
d2 = dict(d)
|
||||
for k in d:
|
||||
if k[0:2] == "__":
|
||||
del d2[k]
|
||||
continue
|
||||
if d2[k].find("\n") >= 0:
|
||||
d2[k] = d2[k].split("\n")
|
||||
return d2
|
||||
|
||||
|
||||
def printTest(testDict, solutionDict):
|
||||
pp = pprint.PrettyPrinter(indent=4)
|
||||
print("Test case:")
|
||||
for line in testDict["__raw_lines__"]:
|
||||
print(" |", line)
|
||||
print("Solution:")
|
||||
for line in solutionDict["__raw_lines__"]:
|
||||
print(" |", line)
|
||||
|
||||
|
||||
def runTest(testName, moduleDict, printTestCase=False, display=None):
|
||||
import testParser
|
||||
import testClasses
|
||||
for module in moduleDict:
|
||||
setattr(sys.modules[__name__], module, moduleDict[module])
|
||||
|
||||
testDict = testParser.TestParser(testName + ".test").parse()
|
||||
solutionDict = testParser.TestParser(testName + ".solution").parse()
|
||||
test_out_file = os.path.join('%s.test_output' % testName)
|
||||
testDict['test_out_file'] = test_out_file
|
||||
testClass = getattr(projectTestClasses, testDict['class'])
|
||||
|
||||
questionClass = getattr(testClasses, 'Question')
|
||||
question = questionClass({'max_points': 0}, display)
|
||||
testCase = testClass(question, testDict)
|
||||
|
||||
if printTestCase:
|
||||
printTest(testDict, solutionDict)
|
||||
|
||||
# This is a fragile hack to create a stub grades object
|
||||
grades = grading.Grades(projectParams.PROJECT_NAME, [(None, 0)])
|
||||
testCase.execute(grades, moduleDict, solutionDict)
|
||||
|
||||
|
||||
# returns all the tests you need to run in order to run question
|
||||
def getDepends(testParser, testRoot, question):
|
||||
allDeps = [question]
|
||||
questionDict = testParser.TestParser(
|
||||
os.path.join(testRoot, question, 'CONFIG')).parse()
|
||||
if 'depends' in questionDict:
|
||||
depends = questionDict['depends'].split()
|
||||
for d in depends:
|
||||
# run dependencies first
|
||||
allDeps = getDepends(testParser, testRoot, d) + allDeps
|
||||
return allDeps
|
||||
|
||||
# get list of questions to grade
|
||||
def getTestSubdirs(testParser, testRoot, questionToGrade):
|
||||
problemDict = testParser.TestParser(
|
||||
os.path.join(testRoot, 'CONFIG')).parse()
|
||||
if questionToGrade != None:
|
||||
questions = getDepends(testParser, testRoot, questionToGrade)
|
||||
if len(questions) > 1:
|
||||
print('Note: due to dependencies, the following tests will be run: %s' %
|
||||
' '.join(questions))
|
||||
return questions
|
||||
if 'order' in problemDict:
|
||||
return problemDict['order'].split()
|
||||
return sorted(os.listdir(testRoot))
|
||||
|
||||
|
||||
# evaluate student code
|
||||
def evaluate(generateSolutions, testRoot, moduleDict, exceptionMap=ERROR_HINT_MAP,
|
||||
edxOutput=False, muteOutput=False, gsOutput=False,
|
||||
printTestCase=False, questionToGrade=None, display=None):
|
||||
# imports of testbench code. note that the testClasses import must follow
|
||||
# the import of student code due to dependencies
|
||||
import testParser
|
||||
import testClasses
|
||||
for module in moduleDict:
|
||||
setattr(sys.modules[__name__], module, moduleDict[module])
|
||||
|
||||
questions = []
|
||||
questionDicts = {}
|
||||
test_subdirs = getTestSubdirs(testParser, testRoot, questionToGrade)
|
||||
for q in test_subdirs:
|
||||
subdir_path = os.path.join(testRoot, q)
|
||||
if not os.path.isdir(subdir_path) or q[0] == '.':
|
||||
continue
|
||||
|
||||
# create a question object
|
||||
questionDict = testParser.TestParser(
|
||||
os.path.join(subdir_path, 'CONFIG')).parse()
|
||||
questionClass = getattr(testClasses, questionDict['class'])
|
||||
question = questionClass(questionDict, display)
|
||||
questionDicts[q] = questionDict
|
||||
|
||||
# load test cases into question
|
||||
tests = [t for t in os.listdir(
|
||||
subdir_path) if re.match(r'[^#~.].*\.test\Z', t)]
|
||||
tests = [re.match(r'(.*)\.test\Z', t).group(1) for t in tests]
|
||||
for t in sorted(tests):
|
||||
test_file = os.path.join(subdir_path, '%s.test' % t)
|
||||
solution_file = os.path.join(subdir_path, '%s.solution' % t)
|
||||
test_out_file = os.path.join(subdir_path, '%s.test_output' % t)
|
||||
testDict = testParser.TestParser(test_file).parse()
|
||||
if testDict.get("disabled", "false").lower() == "true":
|
||||
continue
|
||||
testDict['test_out_file'] = test_out_file
|
||||
testClass = getattr(projectTestClasses, testDict['class'])
|
||||
testCase = testClass(question, testDict)
|
||||
|
||||
def makefun(testCase, solution_file):
|
||||
if generateSolutions:
|
||||
# write solution file to disk
|
||||
return lambda grades: testCase.writeSolution(moduleDict, solution_file)
|
||||
else:
|
||||
# read in solution dictionary and pass as an argument
|
||||
testDict = testParser.TestParser(test_file).parse()
|
||||
solutionDict = testParser.TestParser(solution_file).parse()
|
||||
if printTestCase:
|
||||
return lambda grades: printTest(testDict, solutionDict) or testCase.execute(grades, moduleDict, solutionDict)
|
||||
else:
|
||||
return lambda grades: testCase.execute(grades, moduleDict, solutionDict)
|
||||
question.addTestCase(testCase, makefun(testCase, solution_file))
|
||||
|
||||
# Note extra function is necessary for scoping reasons
|
||||
def makefun(question):
|
||||
return lambda grades: question.execute(grades)
|
||||
setattr(sys.modules[__name__], q, makefun(question))
|
||||
questions.append((q, question.getMaxPoints()))
|
||||
|
||||
grades = grading.Grades(projectParams.PROJECT_NAME, questions,
|
||||
gsOutput=gsOutput, edxOutput=edxOutput, muteOutput=muteOutput)
|
||||
if questionToGrade == None:
|
||||
for q in questionDicts:
|
||||
for prereq in questionDicts[q].get('depends', '').split():
|
||||
grades.addPrereq(q, prereq)
|
||||
|
||||
grades.grade(sys.modules[__name__], bonusPic=projectParams.BONUS_PIC)
|
||||
return grades.points
|
||||
|
||||
|
||||
def getDisplay(graphicsByDefault, options=None):
|
||||
graphics = graphicsByDefault
|
||||
if options is not None and options.noGraphics:
|
||||
graphics = False
|
||||
if graphics:
|
||||
try:
|
||||
import graphicsDisplay
|
||||
return graphicsDisplay.PacmanGraphics(1, frameTime=.05)
|
||||
except ImportError:
|
||||
pass
|
||||
import textDisplay
|
||||
return textDisplay.NullGraphics()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
options = readCommand(sys.argv)
|
||||
if options.generateSolutions:
|
||||
confirmGenerate()
|
||||
codePaths = options.studentCode.split(',')
|
||||
|
||||
moduleDict = {}
|
||||
for cp in codePaths:
|
||||
moduleName = re.match(r'.*?([^/]*)\.py', cp).group(1)
|
||||
moduleDict[moduleName] = loadModuleFile(
|
||||
moduleName, os.path.join(options.codeRoot, cp))
|
||||
moduleName = re.match(r'.*?([^/]*)\.py', options.testCaseCode).group(1)
|
||||
moduleDict['projectTestClasses'] = loadModuleFile(
|
||||
moduleName, os.path.join(options.codeRoot, options.testCaseCode))
|
||||
|
||||
if options.runTest != None:
|
||||
runTest(options.runTest, moduleDict, printTestCase=options.printTestCase,
|
||||
display=getDisplay(True, options))
|
||||
else:
|
||||
evaluate(options.generateSolutions, options.testRoot, moduleDict,
|
||||
gsOutput=options.gsOutput,
|
||||
edxOutput=options.edxOutput, muteOutput=options.muteOutput, printTestCase=options.printTestCase,
|
||||
questionToGrade=options.gradeQuestion, display=getDisplay(options.gradeQuestion != None, options))
|
||||
778
proj2/game.py
Normal file
778
proj2/game.py
Normal file
@ -0,0 +1,778 @@
|
||||
# game.py
|
||||
# -------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
# game.py
|
||||
# -------
|
||||
# Licensing Information: Please do not distribute or publish solutions to this
|
||||
# project. You are free to use and extend these projects for educational
|
||||
# purposes. The Pacman AI projects were developed at UC Berkeley, primarily by
|
||||
# John DeNero (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# For more info, see http://inst.eecs.berkeley.edu/~cs188/sp09/pacman.html
|
||||
|
||||
from util import *
|
||||
import time
|
||||
import os
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
#######################
|
||||
# Parts worth reading #
|
||||
#######################
|
||||
|
||||
|
||||
class Agent:
|
||||
"""
|
||||
An agent must define a getAction method, but may also define the
|
||||
following methods which will be called if they exist:
|
||||
|
||||
def registerInitialState(self, state): # inspects the starting state
|
||||
"""
|
||||
|
||||
def __init__(self, index=0):
|
||||
self.index = index
|
||||
|
||||
def getAction(self, state):
|
||||
"""
|
||||
The Agent will receive a GameState (from either {pacman, capture, sonar}.py) and
|
||||
must return an action from Directions.{North, South, East, West, Stop}
|
||||
"""
|
||||
raiseNotDefined()
|
||||
|
||||
|
||||
class Directions:
|
||||
NORTH = 'North'
|
||||
SOUTH = 'South'
|
||||
EAST = 'East'
|
||||
WEST = 'West'
|
||||
STOP = 'Stop'
|
||||
|
||||
LEFT = {NORTH: WEST,
|
||||
SOUTH: EAST,
|
||||
EAST: NORTH,
|
||||
WEST: SOUTH,
|
||||
STOP: STOP}
|
||||
|
||||
RIGHT = dict([(y, x) for x, y in list(LEFT.items())])
|
||||
|
||||
REVERSE = {NORTH: SOUTH,
|
||||
SOUTH: NORTH,
|
||||
EAST: WEST,
|
||||
WEST: EAST,
|
||||
STOP: STOP}
|
||||
|
||||
|
||||
class Configuration:
|
||||
"""
|
||||
A Configuration holds the (x,y) coordinate of a character, along with its
|
||||
traveling direction.
|
||||
|
||||
The convention for positions, like a graph, is that (0,0) is the lower left corner, x increases
|
||||
horizontally and y increases vertically. Therefore, north is the direction of increasing y, or (0,1).
|
||||
"""
|
||||
|
||||
def __init__(self, pos, direction):
|
||||
self.pos = pos
|
||||
self.direction = direction
|
||||
|
||||
def getPosition(self):
|
||||
return (self.pos)
|
||||
|
||||
def getDirection(self):
|
||||
return self.direction
|
||||
|
||||
def isInteger(self):
|
||||
x, y = self.pos
|
||||
return x == int(x) and y == int(y)
|
||||
|
||||
def __eq__(self, other):
|
||||
if other == None:
|
||||
return False
|
||||
return (self.pos == other.pos and self.direction == other.direction)
|
||||
|
||||
def __hash__(self):
|
||||
x = hash(self.pos)
|
||||
y = hash(self.direction)
|
||||
return hash(x + 13 * y)
|
||||
|
||||
def __str__(self):
|
||||
return "(x,y)="+str(self.pos)+", "+str(self.direction)
|
||||
|
||||
def generateSuccessor(self, vector):
|
||||
"""
|
||||
Generates a new configuration reached by translating the current
|
||||
configuration by the action vector. This is a low-level call and does
|
||||
not attempt to respect the legality of the movement.
|
||||
|
||||
Actions are movement vectors.
|
||||
"""
|
||||
x, y = self.pos
|
||||
dx, dy = vector
|
||||
direction = Actions.vectorToDirection(vector)
|
||||
if direction == Directions.STOP:
|
||||
direction = self.direction # There is no stop direction
|
||||
return Configuration((x + dx, y+dy), direction)
|
||||
|
||||
|
||||
class AgentState:
|
||||
"""
|
||||
AgentStates hold the state of an agent (configuration, speed, scared, etc).
|
||||
"""
|
||||
|
||||
def __init__(self, startConfiguration, isPacman):
|
||||
self.start = startConfiguration
|
||||
self.configuration = startConfiguration
|
||||
self.isPacman = isPacman
|
||||
self.scaredTimer = 0
|
||||
# state below potentially used for contest only
|
||||
self.numCarrying = 0
|
||||
self.numReturned = 0
|
||||
|
||||
def __str__(self):
|
||||
if self.isPacman:
|
||||
return "Pacman: " + str(self.configuration)
|
||||
else:
|
||||
return "Ghost: " + str(self.configuration)
|
||||
|
||||
def __eq__(self, other):
|
||||
if other == None:
|
||||
return False
|
||||
return self.configuration == other.configuration and self.scaredTimer == other.scaredTimer
|
||||
|
||||
def __hash__(self):
|
||||
return hash(hash(self.configuration) + 13 * hash(self.scaredTimer))
|
||||
|
||||
def copy(self):
|
||||
state = AgentState(self.start, self.isPacman)
|
||||
state.configuration = self.configuration
|
||||
state.scaredTimer = self.scaredTimer
|
||||
state.numCarrying = self.numCarrying
|
||||
state.numReturned = self.numReturned
|
||||
return state
|
||||
|
||||
def getPosition(self):
|
||||
if self.configuration == None:
|
||||
return None
|
||||
return self.configuration.getPosition()
|
||||
|
||||
def getDirection(self):
|
||||
return self.configuration.getDirection()
|
||||
|
||||
|
||||
class Grid:
|
||||
"""
|
||||
A 2-dimensional array of objects backed by a list of lists. Data is accessed
|
||||
via grid[x][y] where (x,y) are positions on a Pacman map with x horizontal,
|
||||
y vertical and the origin (0,0) in the bottom left corner.
|
||||
|
||||
The __str__ method constructs an output that is oriented like a pacman board.
|
||||
"""
|
||||
|
||||
def __init__(self, width, height, initialValue=False, bitRepresentation=None):
|
||||
if initialValue not in [False, True]:
|
||||
raise Exception('Grids can only contain booleans')
|
||||
self.CELLS_PER_INT = 30
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.data = [[initialValue for y in range(
|
||||
height)] for x in range(width)]
|
||||
if bitRepresentation:
|
||||
self._unpackBits(bitRepresentation)
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self.data[i]
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
self.data[key] = item
|
||||
|
||||
def __str__(self):
|
||||
out = [[str(self.data[x][y])[0] for x in range(self.width)]
|
||||
for y in range(self.height)]
|
||||
out.reverse()
|
||||
return '\n'.join([''.join(x) for x in out])
|
||||
|
||||
def __eq__(self, other):
|
||||
if other == None:
|
||||
return False
|
||||
return self.data == other.data
|
||||
|
||||
def __hash__(self):
|
||||
# return hash(str(self))
|
||||
base = 1
|
||||
h = 0
|
||||
for l in self.data:
|
||||
for i in l:
|
||||
if i:
|
||||
h += base
|
||||
base *= 2
|
||||
return hash(h)
|
||||
|
||||
def copy(self):
|
||||
g = Grid(self.width, self.height)
|
||||
g.data = [x[:] for x in self.data]
|
||||
return g
|
||||
|
||||
def deepCopy(self):
|
||||
return self.copy()
|
||||
|
||||
def shallowCopy(self):
|
||||
g = Grid(self.width, self.height)
|
||||
g.data = self.data
|
||||
return g
|
||||
|
||||
def count(self, item=True):
|
||||
return sum([x.count(item) for x in self.data])
|
||||
|
||||
def asList(self, key=True):
|
||||
list = []
|
||||
for x in range(self.width):
|
||||
for y in range(self.height):
|
||||
if self[x][y] == key:
|
||||
list.append((x, y))
|
||||
return list
|
||||
|
||||
def packBits(self):
|
||||
"""
|
||||
Returns an efficient int list representation
|
||||
|
||||
(width, height, bitPackedInts...)
|
||||
"""
|
||||
bits = [self.width, self.height]
|
||||
currentInt = 0
|
||||
for i in range(self.height * self.width):
|
||||
bit = self.CELLS_PER_INT - (i % self.CELLS_PER_INT) - 1
|
||||
x, y = self._cellIndexToPosition(i)
|
||||
if self[x][y]:
|
||||
currentInt += 2 ** bit
|
||||
if (i + 1) % self.CELLS_PER_INT == 0:
|
||||
bits.append(currentInt)
|
||||
currentInt = 0
|
||||
bits.append(currentInt)
|
||||
return tuple(bits)
|
||||
|
||||
def _cellIndexToPosition(self, index):
|
||||
x = index / self.height
|
||||
y = index % self.height
|
||||
return x, y
|
||||
|
||||
def _unpackBits(self, bits):
|
||||
"""
|
||||
Fills in data from a bit-level representation
|
||||
"""
|
||||
cell = 0
|
||||
for packed in bits:
|
||||
for bit in self._unpackInt(packed, self.CELLS_PER_INT):
|
||||
if cell == self.width * self.height:
|
||||
break
|
||||
x, y = self._cellIndexToPosition(cell)
|
||||
self[x][y] = bit
|
||||
cell += 1
|
||||
|
||||
def _unpackInt(self, packed, size):
|
||||
bools = []
|
||||
if packed < 0:
|
||||
raise ValueError("must be a positive integer")
|
||||
for i in range(size):
|
||||
n = 2 ** (self.CELLS_PER_INT - i - 1)
|
||||
if packed >= n:
|
||||
bools.append(True)
|
||||
packed -= n
|
||||
else:
|
||||
bools.append(False)
|
||||
return bools
|
||||
|
||||
|
||||
def reconstituteGrid(bitRep):
|
||||
if type(bitRep) is not type((1, 2)):
|
||||
return bitRep
|
||||
width, height = bitRep[:2]
|
||||
return Grid(width, height, bitRepresentation=bitRep[2:])
|
||||
|
||||
####################################
|
||||
# Parts you shouldn't have to read #
|
||||
####################################
|
||||
|
||||
|
||||
class Actions:
|
||||
"""
|
||||
A collection of static methods for manipulating move actions.
|
||||
"""
|
||||
# Directions
|
||||
_directions = {Directions.WEST: (-1, 0),
|
||||
Directions.STOP: (0, 0),
|
||||
Directions.EAST: (1, 0),
|
||||
Directions.NORTH: (0, 1),
|
||||
Directions.SOUTH: (0, -1)}
|
||||
|
||||
_directionsAsList = [('West', (-1, 0)), ('Stop', (0, 0)), ('East', (1, 0)), ('North', (0, 1)), ('South', (0, -1))]
|
||||
|
||||
TOLERANCE = .001
|
||||
|
||||
def reverseDirection(action):
|
||||
if action == Directions.NORTH:
|
||||
return Directions.SOUTH
|
||||
if action == Directions.SOUTH:
|
||||
return Directions.NORTH
|
||||
if action == Directions.EAST:
|
||||
return Directions.WEST
|
||||
if action == Directions.WEST:
|
||||
return Directions.EAST
|
||||
return action
|
||||
reverseDirection = staticmethod(reverseDirection)
|
||||
|
||||
def vectorToDirection(vector):
|
||||
dx, dy = vector
|
||||
if dy > 0:
|
||||
return Directions.NORTH
|
||||
if dy < 0:
|
||||
return Directions.SOUTH
|
||||
if dx < 0:
|
||||
return Directions.WEST
|
||||
if dx > 0:
|
||||
return Directions.EAST
|
||||
return Directions.STOP
|
||||
vectorToDirection = staticmethod(vectorToDirection)
|
||||
|
||||
def directionToVector(direction, speed=1.0):
|
||||
dx, dy = Actions._directions[direction]
|
||||
return (dx * speed, dy * speed)
|
||||
directionToVector = staticmethod(directionToVector)
|
||||
|
||||
def getPossibleActions(config, walls):
|
||||
possible = []
|
||||
x, y = config.pos
|
||||
x_int, y_int = int(x + 0.5), int(y + 0.5)
|
||||
|
||||
# In between grid points, all agents must continue straight
|
||||
if (abs(x - x_int) + abs(y - y_int) > Actions.TOLERANCE):
|
||||
return [config.getDirection()]
|
||||
|
||||
for dir, vec in Actions._directionsAsList:
|
||||
dx, dy = vec
|
||||
next_y = y_int + dy
|
||||
next_x = x_int + dx
|
||||
if not walls[next_x][next_y]:
|
||||
possible.append(dir)
|
||||
|
||||
return possible
|
||||
|
||||
getPossibleActions = staticmethod(getPossibleActions)
|
||||
|
||||
def getLegalNeighbors(position, walls):
|
||||
x, y = position
|
||||
x_int, y_int = int(x + 0.5), int(y + 0.5)
|
||||
neighbors = []
|
||||
for dir, vec in Actions._directionsAsList:
|
||||
dx, dy = vec
|
||||
next_x = x_int + dx
|
||||
if next_x < 0 or next_x == walls.width:
|
||||
continue
|
||||
next_y = y_int + dy
|
||||
if next_y < 0 or next_y == walls.height:
|
||||
continue
|
||||
if not walls[next_x][next_y]:
|
||||
neighbors.append((next_x, next_y))
|
||||
return neighbors
|
||||
getLegalNeighbors = staticmethod(getLegalNeighbors)
|
||||
|
||||
def getSuccessor(position, action):
|
||||
dx, dy = Actions.directionToVector(action)
|
||||
x, y = position
|
||||
return (x + dx, y + dy)
|
||||
getSuccessor = staticmethod(getSuccessor)
|
||||
|
||||
|
||||
class GameStateData:
|
||||
|
||||
def __init__(self, prevState=None):
|
||||
"""
|
||||
Generates a new data packet by copying information from its predecessor.
|
||||
"""
|
||||
if prevState != None:
|
||||
self.food = prevState.food.shallowCopy()
|
||||
self.capsules = prevState.capsules[:]
|
||||
self.agentStates = self.copyAgentStates(prevState.agentStates)
|
||||
self.layout = prevState.layout
|
||||
self._eaten = prevState._eaten
|
||||
self.score = prevState.score
|
||||
|
||||
self._foodEaten = None
|
||||
self._foodAdded = None
|
||||
self._capsuleEaten = None
|
||||
self._agentMoved = None
|
||||
self._lose = False
|
||||
self._win = False
|
||||
self.scoreChange = 0
|
||||
|
||||
def deepCopy(self):
|
||||
state = GameStateData(self)
|
||||
state.food = self.food.deepCopy()
|
||||
state.layout = self.layout.deepCopy()
|
||||
state._agentMoved = self._agentMoved
|
||||
state._foodEaten = self._foodEaten
|
||||
state._foodAdded = self._foodAdded
|
||||
state._capsuleEaten = self._capsuleEaten
|
||||
return state
|
||||
|
||||
def copyAgentStates(self, agentStates):
|
||||
copiedStates = []
|
||||
for agentState in agentStates:
|
||||
copiedStates.append(agentState.copy())
|
||||
return copiedStates
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Allows two states to be compared.
|
||||
"""
|
||||
if other == None:
|
||||
return False
|
||||
# TODO Check for type of other
|
||||
if not self.agentStates == other.agentStates:
|
||||
return False
|
||||
if not self.food == other.food:
|
||||
return False
|
||||
if not self.capsules == other.capsules:
|
||||
return False
|
||||
if not self.score == other.score:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __hash__(self):
|
||||
"""
|
||||
Allows states to be keys of dictionaries.
|
||||
"""
|
||||
for i, state in enumerate(self.agentStates):
|
||||
try:
|
||||
int(hash(state))
|
||||
except TypeError as e:
|
||||
print(e)
|
||||
# hash(state)
|
||||
return int((hash(tuple(self.agentStates)) + 13*hash(self.food) + 113 * hash(tuple(self.capsules)) + 7 * hash(self.score)) % 1048575)
|
||||
|
||||
def __str__(self):
|
||||
width, height = self.layout.width, self.layout.height
|
||||
map = Grid(width, height)
|
||||
if type(self.food) == type((1, 2)):
|
||||
self.food = reconstituteGrid(self.food)
|
||||
for x in range(width):
|
||||
for y in range(height):
|
||||
food, walls = self.food, self.layout.walls
|
||||
map[x][y] = self._foodWallStr(food[x][y], walls[x][y])
|
||||
|
||||
for agentState in self.agentStates:
|
||||
if agentState == None:
|
||||
continue
|
||||
if agentState.configuration == None:
|
||||
continue
|
||||
x, y = [int(i) for i in nearestPoint(agentState.configuration.pos)]
|
||||
agent_dir = agentState.configuration.direction
|
||||
if agentState.isPacman:
|
||||
map[x][y] = self._pacStr(agent_dir)
|
||||
else:
|
||||
map[x][y] = self._ghostStr(agent_dir)
|
||||
|
||||
for x, y in self.capsules:
|
||||
map[x][y] = 'o'
|
||||
|
||||
return str(map) + ("\nScore: %d\n" % self.score)
|
||||
|
||||
def _foodWallStr(self, hasFood, hasWall):
|
||||
if hasFood:
|
||||
return '.'
|
||||
elif hasWall:
|
||||
return '%'
|
||||
else:
|
||||
return ' '
|
||||
|
||||
def _pacStr(self, dir):
|
||||
if dir == Directions.NORTH:
|
||||
return 'v'
|
||||
if dir == Directions.SOUTH:
|
||||
return '^'
|
||||
if dir == Directions.WEST:
|
||||
return '>'
|
||||
return '<'
|
||||
|
||||
def _ghostStr(self, dir):
|
||||
return 'G'
|
||||
if dir == Directions.NORTH:
|
||||
return 'M'
|
||||
if dir == Directions.SOUTH:
|
||||
return 'W'
|
||||
if dir == Directions.WEST:
|
||||
return '3'
|
||||
return 'E'
|
||||
|
||||
def initialize(self, layout, numGhostAgents):
|
||||
"""
|
||||
Creates an initial game state from a layout array (see layout.py).
|
||||
"""
|
||||
self.food = layout.food.copy()
|
||||
#self.capsules = []
|
||||
self.capsules = layout.capsules[:]
|
||||
self.layout = layout
|
||||
self.score = 0
|
||||
self.scoreChange = 0
|
||||
|
||||
self.agentStates = []
|
||||
numGhosts = 0
|
||||
for isPacman, pos in layout.agentPositions:
|
||||
if not isPacman:
|
||||
if numGhosts == numGhostAgents:
|
||||
continue # Max ghosts reached already
|
||||
else:
|
||||
numGhosts += 1
|
||||
self.agentStates.append(AgentState(
|
||||
Configuration(pos, Directions.STOP), isPacman))
|
||||
self._eaten = [False for a in self.agentStates]
|
||||
|
||||
|
||||
try:
|
||||
import boinc
|
||||
_BOINC_ENABLED = True
|
||||
except:
|
||||
_BOINC_ENABLED = False
|
||||
|
||||
|
||||
class Game:
|
||||
"""
|
||||
The Game manages the control flow, soliciting actions from agents.
|
||||
"""
|
||||
|
||||
def __init__(self, agents, display, rules, startingIndex=0, muteAgents=False, catchExceptions=False):
|
||||
self.agentCrashed = False
|
||||
self.agents = agents
|
||||
self.display = display
|
||||
self.rules = rules
|
||||
self.startingIndex = startingIndex
|
||||
self.gameOver = False
|
||||
self.muteAgents = muteAgents
|
||||
self.catchExceptions = catchExceptions
|
||||
self.moveHistory = []
|
||||
self.totalAgentTimes = [0 for agent in agents]
|
||||
self.totalAgentTimeWarnings = [0 for agent in agents]
|
||||
self.agentTimeout = False
|
||||
import io
|
||||
self.agentOutput = [io.StringIO() for agent in agents]
|
||||
|
||||
def getProgress(self):
|
||||
if self.gameOver:
|
||||
return 1.0
|
||||
else:
|
||||
return self.rules.getProgress(self)
|
||||
|
||||
def _agentCrash(self, agentIndex, quiet=False):
|
||||
"Helper method for handling agent crashes"
|
||||
if not quiet:
|
||||
traceback.print_exc()
|
||||
self.gameOver = True
|
||||
self.agentCrashed = True
|
||||
self.rules.agentCrash(self, agentIndex)
|
||||
|
||||
OLD_STDOUT = None
|
||||
OLD_STDERR = None
|
||||
|
||||
def mute(self, agentIndex):
|
||||
if not self.muteAgents:
|
||||
return
|
||||
global OLD_STDOUT, OLD_STDERR
|
||||
import io
|
||||
OLD_STDOUT = sys.stdout
|
||||
OLD_STDERR = sys.stderr
|
||||
sys.stdout = self.agentOutput[agentIndex]
|
||||
sys.stderr = self.agentOutput[agentIndex]
|
||||
|
||||
def unmute(self):
|
||||
if not self.muteAgents:
|
||||
return
|
||||
global OLD_STDOUT, OLD_STDERR
|
||||
# Revert stdout/stderr to originals
|
||||
sys.stdout = OLD_STDOUT
|
||||
sys.stderr = OLD_STDERR
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Main control loop for game play.
|
||||
"""
|
||||
self.display.initialize(self.state.data)
|
||||
self.numMoves = 0
|
||||
|
||||
# self.display.initialize(self.state.makeObservation(1).data)
|
||||
# inform learning agents of the game start
|
||||
for i in range(len(self.agents)):
|
||||
agent = self.agents[i]
|
||||
if not agent:
|
||||
self.mute(i)
|
||||
# this is a null agent, meaning it failed to load
|
||||
# the other team wins
|
||||
print("Agent %d failed to load" % i, file=sys.stderr)
|
||||
self.unmute()
|
||||
self._agentCrash(i, quiet=True)
|
||||
return
|
||||
if ("registerInitialState" in dir(agent)):
|
||||
self.mute(i)
|
||||
if self.catchExceptions:
|
||||
try:
|
||||
timed_func = TimeoutFunction(
|
||||
agent.registerInitialState, int(self.rules.getMaxStartupTime(i)))
|
||||
try:
|
||||
start_time = time.time()
|
||||
timed_func(self.state.deepCopy())
|
||||
time_taken = time.time() - start_time
|
||||
self.totalAgentTimes[i] += time_taken
|
||||
except TimeoutFunctionException:
|
||||
print("Agent %d ran out of time on startup!" %
|
||||
i, file=sys.stderr)
|
||||
self.unmute()
|
||||
self.agentTimeout = True
|
||||
self._agentCrash(i, quiet=True)
|
||||
return
|
||||
except Exception as data:
|
||||
self._agentCrash(i, quiet=False)
|
||||
self.unmute()
|
||||
return
|
||||
else:
|
||||
agent.registerInitialState(self.state.deepCopy())
|
||||
# TODO: could this exceed the total time
|
||||
self.unmute()
|
||||
|
||||
agentIndex = self.startingIndex
|
||||
numAgents = len(self.agents)
|
||||
|
||||
while not self.gameOver:
|
||||
# Fetch the next agent
|
||||
agent = self.agents[agentIndex]
|
||||
move_time = 0
|
||||
skip_action = False
|
||||
# Generate an observation of the state
|
||||
if 'observationFunction' in dir(agent):
|
||||
self.mute(agentIndex)
|
||||
if self.catchExceptions:
|
||||
try:
|
||||
timed_func = TimeoutFunction(agent.observationFunction, int(
|
||||
self.rules.getMoveTimeout(agentIndex)))
|
||||
try:
|
||||
start_time = time.time()
|
||||
observation = timed_func(self.state.deepCopy())
|
||||
except TimeoutFunctionException:
|
||||
skip_action = True
|
||||
move_time += time.time() - start_time
|
||||
self.unmute()
|
||||
except Exception as data:
|
||||
self._agentCrash(agentIndex, quiet=False)
|
||||
self.unmute()
|
||||
return
|
||||
else:
|
||||
observation = agent.observationFunction(
|
||||
self.state.deepCopy())
|
||||
self.unmute()
|
||||
else:
|
||||
observation = self.state.deepCopy()
|
||||
|
||||
# Solicit an action
|
||||
action = None
|
||||
self.mute(agentIndex)
|
||||
if self.catchExceptions:
|
||||
try:
|
||||
timed_func = TimeoutFunction(agent.getAction, int(
|
||||
self.rules.getMoveTimeout(agentIndex)) - int(move_time))
|
||||
try:
|
||||
start_time = time.time()
|
||||
if skip_action:
|
||||
raise TimeoutFunctionException()
|
||||
action = timed_func(observation)
|
||||
except TimeoutFunctionException:
|
||||
print("Agent %d timed out on a single move!" %
|
||||
agentIndex, file=sys.stderr)
|
||||
self.agentTimeout = True
|
||||
self._agentCrash(agentIndex, quiet=True)
|
||||
self.unmute()
|
||||
return
|
||||
|
||||
move_time += time.time() - start_time
|
||||
|
||||
if move_time > self.rules.getMoveWarningTime(agentIndex):
|
||||
self.totalAgentTimeWarnings[agentIndex] += 1
|
||||
print("Agent %d took too long to make a move! This is warning %d" % (
|
||||
agentIndex, self.totalAgentTimeWarnings[agentIndex]), file=sys.stderr)
|
||||
if self.totalAgentTimeWarnings[agentIndex] > self.rules.getMaxTimeWarnings(agentIndex):
|
||||
print("Agent %d exceeded the maximum number of warnings: %d" % (
|
||||
agentIndex, self.totalAgentTimeWarnings[agentIndex]), file=sys.stderr)
|
||||
self.agentTimeout = True
|
||||
self._agentCrash(agentIndex, quiet=True)
|
||||
self.unmute()
|
||||
return
|
||||
|
||||
self.totalAgentTimes[agentIndex] += move_time
|
||||
# print "Agent: %d, time: %f, total: %f" % (agentIndex, move_time, self.totalAgentTimes[agentIndex])
|
||||
if self.totalAgentTimes[agentIndex] > self.rules.getMaxTotalTime(agentIndex):
|
||||
print("Agent %d ran out of time! (time: %1.2f)" % (
|
||||
agentIndex, self.totalAgentTimes[agentIndex]), file=sys.stderr)
|
||||
self.agentTimeout = True
|
||||
self._agentCrash(agentIndex, quiet=True)
|
||||
self.unmute()
|
||||
return
|
||||
self.unmute()
|
||||
except Exception as data:
|
||||
self._agentCrash(agentIndex)
|
||||
self.unmute()
|
||||
return
|
||||
else:
|
||||
action = agent.getAction(observation)
|
||||
self.unmute()
|
||||
|
||||
# Execute the action
|
||||
self.moveHistory.append((agentIndex, action))
|
||||
if self.catchExceptions:
|
||||
try:
|
||||
self.state = self.state.generateSuccessor(
|
||||
agentIndex, action)
|
||||
except Exception as data:
|
||||
self.mute(agentIndex)
|
||||
self._agentCrash(agentIndex)
|
||||
self.unmute()
|
||||
return
|
||||
else:
|
||||
self.state = self.state.generateSuccessor(agentIndex, action)
|
||||
|
||||
# Change the display
|
||||
self.display.update(self.state.data)
|
||||
###idx = agentIndex - agentIndex % 2 + 1
|
||||
###self.display.update( self.state.makeObservation(idx).data )
|
||||
|
||||
# Allow for game specific conditions (winning, losing, etc.)
|
||||
self.rules.process(self.state, self)
|
||||
# Track progress
|
||||
if agentIndex == numAgents + 1:
|
||||
self.numMoves += 1
|
||||
# Next agent
|
||||
agentIndex = (agentIndex + 1) % numAgents
|
||||
|
||||
if _BOINC_ENABLED:
|
||||
boinc.set_fraction_done(self.getProgress())
|
||||
|
||||
# inform a learning agent of the game result
|
||||
for agentIndex, agent in enumerate(self.agents):
|
||||
if "final" in dir(agent):
|
||||
try:
|
||||
self.mute(agentIndex)
|
||||
agent.final(self.state)
|
||||
self.unmute()
|
||||
except Exception as data:
|
||||
if not self.catchExceptions:
|
||||
raise
|
||||
self._agentCrash(agentIndex)
|
||||
self.unmute()
|
||||
return
|
||||
self.display.finish()
|
||||
93
proj2/ghostAgents.py
Normal file
93
proj2/ghostAgents.py
Normal file
@ -0,0 +1,93 @@
|
||||
# ghostAgents.py
|
||||
# --------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
from game import Agent
|
||||
from game import Actions
|
||||
from game import Directions
|
||||
import random
|
||||
from util import manhattanDistance
|
||||
import util
|
||||
|
||||
|
||||
class GhostAgent(Agent):
|
||||
def __init__(self, index):
|
||||
self.index = index
|
||||
|
||||
def getAction(self, state):
|
||||
dist = self.getDistribution(state)
|
||||
if len(dist) == 0:
|
||||
return Directions.STOP
|
||||
else:
|
||||
return util.chooseFromDistribution(dist)
|
||||
|
||||
def getDistribution(self, state):
|
||||
"Returns a Counter encoding a distribution over actions from the provided state."
|
||||
util.raiseNotDefined()
|
||||
|
||||
|
||||
class RandomGhost(GhostAgent):
|
||||
"A ghost that chooses a legal action uniformly at random."
|
||||
|
||||
def getDistribution(self, state):
|
||||
dist = util.Counter()
|
||||
for a in state.getLegalActions(self.index):
|
||||
dist[a] = 1.0
|
||||
dist.normalize()
|
||||
return dist
|
||||
|
||||
|
||||
class DirectionalGhost(GhostAgent):
|
||||
"A ghost that prefers to rush Pacman, or flee when scared."
|
||||
|
||||
def __init__(self, index, prob_attack=0.8, prob_scaredFlee=0.8):
|
||||
self.index = index
|
||||
self.prob_attack = prob_attack
|
||||
self.prob_scaredFlee = prob_scaredFlee
|
||||
|
||||
def getDistribution(self, state):
|
||||
# Read variables from state
|
||||
ghostState = state.getGhostState(self.index)
|
||||
legalActions = state.getLegalActions(self.index)
|
||||
pos = state.getGhostPosition(self.index)
|
||||
isScared = ghostState.scaredTimer > 0
|
||||
|
||||
speed = 1
|
||||
if isScared:
|
||||
speed = 0.5
|
||||
|
||||
actionVectors = [Actions.directionToVector(
|
||||
a, speed) for a in legalActions]
|
||||
newPositions = [(pos[0]+a[0], pos[1]+a[1]) for a in actionVectors]
|
||||
pacmanPosition = state.getPacmanPosition()
|
||||
|
||||
# Select best actions given the state
|
||||
distancesToPacman = [manhattanDistance(
|
||||
pos, pacmanPosition) for pos in newPositions]
|
||||
if isScared:
|
||||
bestScore = max(distancesToPacman)
|
||||
bestProb = self.prob_scaredFlee
|
||||
else:
|
||||
bestScore = min(distancesToPacman)
|
||||
bestProb = self.prob_attack
|
||||
bestActions = [action for action, distance in zip(
|
||||
legalActions, distancesToPacman) if distance == bestScore]
|
||||
|
||||
# Construct distribution
|
||||
dist = util.Counter()
|
||||
for a in bestActions:
|
||||
dist[a] = bestProb / len(bestActions)
|
||||
for a in legalActions:
|
||||
dist[a] += (1-bestProb) / len(legalActions)
|
||||
dist.normalize()
|
||||
return dist
|
||||
328
proj2/grading.py
Normal file
328
proj2/grading.py
Normal file
@ -0,0 +1,328 @@
|
||||
# grading.py
|
||||
# ----------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
"Common code for autograders"
|
||||
|
||||
import html
|
||||
import time
|
||||
import sys
|
||||
import json
|
||||
import traceback
|
||||
import pdb
|
||||
from collections import defaultdict
|
||||
import util
|
||||
|
||||
|
||||
class Grades:
|
||||
"A data structure for project grades, along with formatting code to display them"
|
||||
|
||||
def __init__(self, projectName, questionsAndMaxesList,
|
||||
gsOutput=False, edxOutput=False, muteOutput=False):
|
||||
"""
|
||||
Defines the grading scheme for a project
|
||||
projectName: project name
|
||||
questionsAndMaxesDict: a list of (question name, max points per question)
|
||||
"""
|
||||
self.questions = [el[0] for el in questionsAndMaxesList]
|
||||
self.maxes = dict(questionsAndMaxesList)
|
||||
self.points = Counter()
|
||||
self.messages = dict([(q, []) for q in self.questions])
|
||||
self.project = projectName
|
||||
self.start = time.localtime()[1:6]
|
||||
self.sane = True # Sanity checks
|
||||
self.currentQuestion = None # Which question we're grading
|
||||
self.edxOutput = edxOutput
|
||||
self.gsOutput = gsOutput # GradeScope output
|
||||
self.mute = muteOutput
|
||||
self.prereqs = defaultdict(set)
|
||||
|
||||
# print 'Autograder transcript for %s' % self.project
|
||||
print('Starting on %d-%d at %d:%02d:%02d' % self.start)
|
||||
|
||||
def addPrereq(self, question, prereq):
|
||||
self.prereqs[question].add(prereq)
|
||||
|
||||
def grade(self, gradingModule, exceptionMap={}, bonusPic=False):
|
||||
"""
|
||||
Grades each question
|
||||
gradingModule: the module with all the grading functions (pass in with sys.modules[__name__])
|
||||
"""
|
||||
|
||||
completedQuestions = set([])
|
||||
for q in self.questions:
|
||||
print('\nQuestion %s' % q)
|
||||
print('=' * (9 + len(q)))
|
||||
print()
|
||||
self.currentQuestion = q
|
||||
|
||||
incompleted = self.prereqs[q].difference(completedQuestions)
|
||||
if len(incompleted) > 0:
|
||||
prereq = incompleted.pop()
|
||||
print("""*** NOTE: Make sure to complete Question %s before working on Question %s,
|
||||
*** because Question %s builds upon your answer for Question %s.
|
||||
""" % (prereq, q, q, prereq))
|
||||
continue
|
||||
|
||||
if self.mute:
|
||||
util.mutePrint()
|
||||
try:
|
||||
util.TimeoutFunction(getattr(gradingModule, q), 1800)(
|
||||
self) # Call the question's function
|
||||
# TimeoutFunction(getattr(gradingModule, q),1200)(self) # Call the question's function
|
||||
except Exception as inst:
|
||||
self.addExceptionMessage(q, inst, traceback)
|
||||
self.addErrorHints(exceptionMap, inst, q[1])
|
||||
except:
|
||||
self.fail('FAIL: Terminated with a string exception.')
|
||||
finally:
|
||||
if self.mute:
|
||||
util.unmutePrint()
|
||||
|
||||
if self.points[q] >= self.maxes[q]:
|
||||
completedQuestions.add(q)
|
||||
|
||||
print('\n### Question %s: %d/%d ###\n' %
|
||||
(q, self.points[q], self.maxes[q]))
|
||||
|
||||
print('\nFinished at %d:%02d:%02d' % time.localtime()[3:6])
|
||||
print("\nProvisional grades\n==================")
|
||||
|
||||
for q in self.questions:
|
||||
print('Question %s: %d/%d' % (q, self.points[q], self.maxes[q]))
|
||||
print('------------------')
|
||||
print('Total: %d/%d' %
|
||||
(self.points.totalCount(), sum(self.maxes.values())))
|
||||
if bonusPic and self.points.totalCount() == 25:
|
||||
print("""
|
||||
|
||||
ALL HAIL GRANDPAC.
|
||||
LONG LIVE THE GHOSTBUSTING KING.
|
||||
|
||||
--- ---- ---
|
||||
| \ / + \ / |
|
||||
| + \--/ \--/ + |
|
||||
| + + |
|
||||
| + + + |
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
\ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
\ / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
V \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
\ / @@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
V @@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@
|
||||
/\ @@@@@@@@@@@@@@@@@@@@@@
|
||||
/ \ @@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
/\ / @@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
/ \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
/ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@
|
||||
|
||||
""")
|
||||
print("""
|
||||
Your grades are NOT yet registered. To register your grades, make sure
|
||||
to follow your instructor's guidelines to receive credit on your project.
|
||||
""")
|
||||
|
||||
if self.edxOutput:
|
||||
self.produceOutput()
|
||||
if self.gsOutput:
|
||||
self.produceGradeScopeOutput()
|
||||
|
||||
def addExceptionMessage(self, q, inst, traceback):
|
||||
"""
|
||||
Method to format the exception message, this is more complicated because
|
||||
we need to html.escape the traceback but wrap the exception in a <pre> tag
|
||||
"""
|
||||
self.fail('FAIL: Exception raised: %s' % inst)
|
||||
self.addMessage('')
|
||||
for line in traceback.format_exc().split('\n'):
|
||||
self.addMessage(line)
|
||||
|
||||
def addErrorHints(self, exceptionMap, errorInstance, questionNum):
|
||||
typeOf = str(type(errorInstance))
|
||||
questionName = 'q' + questionNum
|
||||
errorHint = ''
|
||||
|
||||
# question specific error hints
|
||||
if exceptionMap.get(questionName):
|
||||
questionMap = exceptionMap.get(questionName)
|
||||
if (questionMap.get(typeOf)):
|
||||
errorHint = questionMap.get(typeOf)
|
||||
# fall back to general error messages if a question specific
|
||||
# one does not exist
|
||||
if (exceptionMap.get(typeOf)):
|
||||
errorHint = exceptionMap.get(typeOf)
|
||||
|
||||
# dont include the HTML if we have no error hint
|
||||
if not errorHint:
|
||||
return ''
|
||||
|
||||
for line in errorHint.split('\n'):
|
||||
self.addMessage(line)
|
||||
|
||||
def produceGradeScopeOutput(self):
|
||||
out_dct = {}
|
||||
|
||||
# total of entire submission
|
||||
total_possible = sum(self.maxes.values())
|
||||
total_score = sum(self.points.values())
|
||||
out_dct['score'] = total_score
|
||||
out_dct['max_score'] = total_possible
|
||||
out_dct['output'] = "Total score (%d / %d)" % (
|
||||
total_score, total_possible)
|
||||
|
||||
# individual tests
|
||||
tests_out = []
|
||||
for name in self.questions:
|
||||
test_out = {}
|
||||
# test name
|
||||
test_out['name'] = name
|
||||
# test score
|
||||
test_out['score'] = self.points[name]
|
||||
test_out['max_score'] = self.maxes[name]
|
||||
# others
|
||||
is_correct = self.points[name] >= self.maxes[name]
|
||||
test_out['output'] = " Question {num} ({points}/{max}) {correct}".format(
|
||||
num=(name[1] if len(name) == 2 else name),
|
||||
points=test_out['score'],
|
||||
max=test_out['max_score'],
|
||||
correct=('X' if not is_correct else ''),
|
||||
)
|
||||
test_out['tags'] = []
|
||||
tests_out.append(test_out)
|
||||
out_dct['tests'] = tests_out
|
||||
|
||||
# file output
|
||||
with open('gradescope_response.json', 'w') as outfile:
|
||||
json.dump(out_dct, outfile)
|
||||
return
|
||||
|
||||
def produceOutput(self):
|
||||
edxOutput = open('edx_response.html', 'w')
|
||||
edxOutput.write("<div>")
|
||||
|
||||
# first sum
|
||||
total_possible = sum(self.maxes.values())
|
||||
total_score = sum(self.points.values())
|
||||
checkOrX = '<span class="incorrect"/>'
|
||||
if (total_score >= total_possible):
|
||||
checkOrX = '<span class="correct"/>'
|
||||
header = """
|
||||
<h3>
|
||||
Total score ({total_score} / {total_possible})
|
||||
</h3>
|
||||
""".format(total_score=total_score,
|
||||
total_possible=total_possible,
|
||||
checkOrX=checkOrX
|
||||
)
|
||||
edxOutput.write(header)
|
||||
|
||||
for q in self.questions:
|
||||
if len(q) == 2:
|
||||
name = q[1]
|
||||
else:
|
||||
name = q
|
||||
checkOrX = '<span class="incorrect"/>'
|
||||
if (self.points[q] >= self.maxes[q]):
|
||||
checkOrX = '<span class="correct"/>'
|
||||
#messages = '\n<br/>\n'.join(self.messages[q])
|
||||
messages = "<pre>%s</pre>" % '\n'.join(self.messages[q])
|
||||
output = """
|
||||
<div class="test">
|
||||
<section>
|
||||
<div class="shortform">
|
||||
Question {q} ({points}/{max}) {checkOrX}
|
||||
</div>
|
||||
<div class="longform">
|
||||
{messages}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
""".format(q=name,
|
||||
max=self.maxes[q],
|
||||
messages=messages,
|
||||
checkOrX=checkOrX,
|
||||
points=self.points[q]
|
||||
)
|
||||
# print "*** output for Question %s " % q[1]
|
||||
# print output
|
||||
edxOutput.write(output)
|
||||
edxOutput.write("</div>")
|
||||
edxOutput.close()
|
||||
edxOutput = open('edx_grade', 'w')
|
||||
edxOutput.write(str(self.points.totalCount()))
|
||||
edxOutput.close()
|
||||
|
||||
def fail(self, message, raw=False):
|
||||
"Sets sanity check bit to false and outputs a message"
|
||||
self.sane = False
|
||||
self.assignZeroCredit()
|
||||
self.addMessage(message, raw)
|
||||
|
||||
def assignZeroCredit(self):
|
||||
self.points[self.currentQuestion] = 0
|
||||
|
||||
def addPoints(self, amt):
|
||||
self.points[self.currentQuestion] += amt
|
||||
|
||||
def deductPoints(self, amt):
|
||||
self.points[self.currentQuestion] -= amt
|
||||
|
||||
def assignFullCredit(self, message="", raw=False):
|
||||
self.points[self.currentQuestion] = self.maxes[self.currentQuestion]
|
||||
if message != "":
|
||||
self.addMessage(message, raw)
|
||||
|
||||
def addMessage(self, message, raw=False):
|
||||
if not raw:
|
||||
# We assume raw messages, formatted for HTML, are printed separately
|
||||
if self.mute:
|
||||
util.unmutePrint()
|
||||
print('*** ' + message)
|
||||
if self.mute:
|
||||
util.mutePrint()
|
||||
message = html.escape(message)
|
||||
self.messages[self.currentQuestion].append(message)
|
||||
|
||||
def addMessageToEmail(self, message):
|
||||
print("WARNING**** addMessageToEmail is deprecated %s" % message)
|
||||
for line in message.split('\n'):
|
||||
pass
|
||||
# print '%%% ' + line + ' %%%'
|
||||
# self.messages[self.currentQuestion].append(line)
|
||||
|
||||
|
||||
class Counter(dict):
|
||||
"""
|
||||
Dict with default 0
|
||||
"""
|
||||
|
||||
def __getitem__(self, idx):
|
||||
try:
|
||||
return dict.__getitem__(self, idx)
|
||||
except KeyError:
|
||||
return 0
|
||||
|
||||
def totalCount(self):
|
||||
"""
|
||||
Returns the sum of counts for all keys.
|
||||
"""
|
||||
return sum(self.values())
|
||||
738
proj2/graphicsDisplay.py
Normal file
738
proj2/graphicsDisplay.py
Normal file
@ -0,0 +1,738 @@
|
||||
# graphicsDisplay.py
|
||||
# ------------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
from graphicsUtils import *
|
||||
import math
|
||||
import time
|
||||
from game import Directions
|
||||
|
||||
###########################
|
||||
# GRAPHICS DISPLAY CODE #
|
||||
###########################
|
||||
|
||||
# Most code by Dan Klein and John Denero written or rewritten for cs188, UC Berkeley.
|
||||
# Some code from a Pacman implementation by LiveWires, and used / modified with permission.
|
||||
|
||||
DEFAULT_GRID_SIZE = 30.0
|
||||
INFO_PANE_HEIGHT = 35
|
||||
BACKGROUND_COLOR = formatColor(0, 0, 0)
|
||||
WALL_COLOR = formatColor(0.0/255.0, 51.0/255.0, 255.0/255.0)
|
||||
INFO_PANE_COLOR = formatColor(.4, .4, 0)
|
||||
SCORE_COLOR = formatColor(.9, .9, .9)
|
||||
PACMAN_OUTLINE_WIDTH = 2
|
||||
PACMAN_CAPTURE_OUTLINE_WIDTH = 4
|
||||
|
||||
GHOST_COLORS = []
|
||||
GHOST_COLORS.append(formatColor(.9, 0, 0)) # Red
|
||||
GHOST_COLORS.append(formatColor(0, .3, .9)) # Blue
|
||||
GHOST_COLORS.append(formatColor(.98, .41, .07)) # Orange
|
||||
GHOST_COLORS.append(formatColor(.1, .75, .7)) # Green
|
||||
GHOST_COLORS.append(formatColor(1.0, 0.6, 0.0)) # Yellow
|
||||
GHOST_COLORS.append(formatColor(.4, 0.13, 0.91)) # Purple
|
||||
|
||||
TEAM_COLORS = GHOST_COLORS[:2]
|
||||
|
||||
GHOST_SHAPE = [
|
||||
(0, 0.3),
|
||||
(0.25, 0.75),
|
||||
(0.5, 0.3),
|
||||
(0.75, 0.75),
|
||||
(0.75, -0.5),
|
||||
(0.5, -0.75),
|
||||
(-0.5, -0.75),
|
||||
(-0.75, -0.5),
|
||||
(-0.75, 0.75),
|
||||
(-0.5, 0.3),
|
||||
(-0.25, 0.75)
|
||||
]
|
||||
GHOST_SIZE = 0.65
|
||||
SCARED_COLOR = formatColor(1, 1, 1)
|
||||
|
||||
GHOST_VEC_COLORS = list(map(colorToVector, GHOST_COLORS))
|
||||
|
||||
PACMAN_COLOR = formatColor(255.0/255.0, 255.0/255.0, 61.0/255)
|
||||
PACMAN_SCALE = 0.5
|
||||
#pacman_speed = 0.25
|
||||
|
||||
# Food
|
||||
FOOD_COLOR = formatColor(1, 1, 1)
|
||||
FOOD_SIZE = 0.1
|
||||
|
||||
# Laser
|
||||
LASER_COLOR = formatColor(1, 0, 0)
|
||||
LASER_SIZE = 0.02
|
||||
|
||||
# Capsule graphics
|
||||
CAPSULE_COLOR = formatColor(1, 1, 1)
|
||||
CAPSULE_SIZE = 0.25
|
||||
|
||||
# Drawing walls
|
||||
WALL_RADIUS = 0.15
|
||||
|
||||
|
||||
class InfoPane:
|
||||
def __init__(self, layout, gridSize):
|
||||
self.gridSize = gridSize
|
||||
self.width = (layout.width) * gridSize
|
||||
self.base = (layout.height + 1) * gridSize
|
||||
self.height = INFO_PANE_HEIGHT
|
||||
self.fontSize = 24
|
||||
self.textColor = PACMAN_COLOR
|
||||
self.drawPane()
|
||||
|
||||
def toScreen(self, pos, y=None):
|
||||
"""
|
||||
Translates a point relative from the bottom left of the info pane.
|
||||
"""
|
||||
if y == None:
|
||||
x, y = pos
|
||||
else:
|
||||
x = pos
|
||||
|
||||
x = self.gridSize + x # Margin
|
||||
y = self.base + y
|
||||
return x, y
|
||||
|
||||
def drawPane(self):
|
||||
self.scoreText = text(self.toScreen(
|
||||
0, 0), self.textColor, "SCORE: 0", "Times", self.fontSize, "bold")
|
||||
|
||||
def initializeGhostDistances(self, distances):
|
||||
self.ghostDistanceText = []
|
||||
|
||||
size = 20
|
||||
if self.width < 240:
|
||||
size = 12
|
||||
if self.width < 160:
|
||||
size = 10
|
||||
|
||||
for i, d in enumerate(distances):
|
||||
t = text(self.toScreen(self.width/2 + self.width/8 * i, 0),
|
||||
GHOST_COLORS[i+1], d, "Times", size, "bold")
|
||||
self.ghostDistanceText.append(t)
|
||||
|
||||
def updateScore(self, score):
|
||||
changeText(self.scoreText, "SCORE: % 4d" % score)
|
||||
|
||||
def setTeam(self, isBlue):
|
||||
text = "RED TEAM"
|
||||
if isBlue:
|
||||
text = "BLUE TEAM"
|
||||
self.teamText = text(self.toScreen(
|
||||
300, 0), self.textColor, text, "Times", self.fontSize, "bold")
|
||||
|
||||
def updateGhostDistances(self, distances):
|
||||
if len(distances) == 0:
|
||||
return
|
||||
if 'ghostDistanceText' not in dir(self):
|
||||
self.initializeGhostDistances(distances)
|
||||
else:
|
||||
for i, d in enumerate(distances):
|
||||
changeText(self.ghostDistanceText[i], d)
|
||||
|
||||
def drawGhost(self):
|
||||
pass
|
||||
|
||||
def drawPacman(self):
|
||||
pass
|
||||
|
||||
def drawWarning(self):
|
||||
pass
|
||||
|
||||
def clearIcon(self):
|
||||
pass
|
||||
|
||||
def updateMessage(self, message):
|
||||
pass
|
||||
|
||||
def clearMessage(self):
|
||||
pass
|
||||
|
||||
|
||||
class PacmanGraphics:
|
||||
def __init__(self, zoom=1.0, frameTime=0.0, capture=False):
|
||||
self.have_window = 0
|
||||
self.currentGhostImages = {}
|
||||
self.pacmanImage = None
|
||||
self.zoom = zoom
|
||||
self.gridSize = DEFAULT_GRID_SIZE * zoom
|
||||
self.capture = capture
|
||||
self.frameTime = frameTime
|
||||
|
||||
def checkNullDisplay(self):
|
||||
return False
|
||||
|
||||
def initialize(self, state, isBlue=False):
|
||||
self.isBlue = isBlue
|
||||
self.startGraphics(state)
|
||||
|
||||
# self.drawDistributions(state)
|
||||
self.distributionImages = None # Initialized lazily
|
||||
self.drawStaticObjects(state)
|
||||
self.drawAgentObjects(state)
|
||||
|
||||
# Information
|
||||
self.previousState = state
|
||||
|
||||
def startGraphics(self, state):
|
||||
self.layout = state.layout
|
||||
layout = self.layout
|
||||
self.width = layout.width
|
||||
self.height = layout.height
|
||||
self.make_window(self.width, self.height)
|
||||
self.infoPane = InfoPane(layout, self.gridSize)
|
||||
self.currentState = layout
|
||||
|
||||
def drawDistributions(self, state):
|
||||
walls = state.layout.walls
|
||||
dist = []
|
||||
for x in range(walls.width):
|
||||
distx = []
|
||||
dist.append(distx)
|
||||
for y in range(walls.height):
|
||||
(screen_x, screen_y) = self.to_screen((x, y))
|
||||
block = square((screen_x, screen_y),
|
||||
0.5 * self.gridSize,
|
||||
color=BACKGROUND_COLOR,
|
||||
filled=1, behind=2)
|
||||
distx.append(block)
|
||||
self.distributionImages = dist
|
||||
|
||||
def drawStaticObjects(self, state):
|
||||
layout = self.layout
|
||||
self.drawWalls(layout.walls)
|
||||
self.food = self.drawFood(layout.food)
|
||||
self.capsules = self.drawCapsules(layout.capsules)
|
||||
refresh()
|
||||
|
||||
def drawAgentObjects(self, state):
|
||||
self.agentImages = [] # (agentState, image)
|
||||
for index, agent in enumerate(state.agentStates):
|
||||
if agent.isPacman:
|
||||
image = self.drawPacman(agent, index)
|
||||
self.agentImages.append((agent, image))
|
||||
else:
|
||||
image = self.drawGhost(agent, index)
|
||||
self.agentImages.append((agent, image))
|
||||
refresh()
|
||||
|
||||
def swapImages(self, agentIndex, newState):
|
||||
"""
|
||||
Changes an image from a ghost to a pacman or vis versa (for capture)
|
||||
"""
|
||||
prevState, prevImage = self.agentImages[agentIndex]
|
||||
for item in prevImage:
|
||||
remove_from_screen(item)
|
||||
if newState.isPacman:
|
||||
image = self.drawPacman(newState, agentIndex)
|
||||
self.agentImages[agentIndex] = (newState, image)
|
||||
else:
|
||||
image = self.drawGhost(newState, agentIndex)
|
||||
self.agentImages[agentIndex] = (newState, image)
|
||||
refresh()
|
||||
|
||||
def update(self, newState):
|
||||
agentIndex = newState._agentMoved
|
||||
agentState = newState.agentStates[agentIndex]
|
||||
|
||||
if self.agentImages[agentIndex][0].isPacman != agentState.isPacman:
|
||||
self.swapImages(agentIndex, agentState)
|
||||
prevState, prevImage = self.agentImages[agentIndex]
|
||||
if agentState.isPacman:
|
||||
self.animatePacman(agentState, prevState, prevImage)
|
||||
else:
|
||||
self.moveGhost(agentState, agentIndex, prevState, prevImage)
|
||||
self.agentImages[agentIndex] = (agentState, prevImage)
|
||||
|
||||
if newState._foodEaten != None:
|
||||
self.removeFood(newState._foodEaten, self.food)
|
||||
if newState._capsuleEaten != None:
|
||||
self.removeCapsule(newState._capsuleEaten, self.capsules)
|
||||
self.infoPane.updateScore(newState.score)
|
||||
if 'ghostDistances' in dir(newState):
|
||||
self.infoPane.updateGhostDistances(newState.ghostDistances)
|
||||
|
||||
def make_window(self, width, height):
|
||||
grid_width = (width-1) * self.gridSize
|
||||
grid_height = (height-1) * self.gridSize
|
||||
screen_width = 2*self.gridSize + grid_width
|
||||
screen_height = 2*self.gridSize + grid_height + INFO_PANE_HEIGHT
|
||||
|
||||
begin_graphics(screen_width,
|
||||
screen_height,
|
||||
BACKGROUND_COLOR,
|
||||
"CS188 Pacman")
|
||||
|
||||
def drawPacman(self, pacman, index):
|
||||
position = self.getPosition(pacman)
|
||||
screen_point = self.to_screen(position)
|
||||
endpoints = self.getEndpoints(self.getDirection(pacman))
|
||||
|
||||
width = PACMAN_OUTLINE_WIDTH
|
||||
outlineColor = PACMAN_COLOR
|
||||
fillColor = PACMAN_COLOR
|
||||
|
||||
if self.capture:
|
||||
outlineColor = TEAM_COLORS[index % 2]
|
||||
fillColor = GHOST_COLORS[index]
|
||||
width = PACMAN_CAPTURE_OUTLINE_WIDTH
|
||||
|
||||
return [circle(screen_point, PACMAN_SCALE * self.gridSize,
|
||||
fillColor=fillColor, outlineColor=outlineColor,
|
||||
endpoints=endpoints,
|
||||
width=width)]
|
||||
|
||||
def getEndpoints(self, direction, position=(0, 0)):
|
||||
x, y = position
|
||||
pos = x - int(x) + y - int(y)
|
||||
width = 30 + 80 * math.sin(math.pi * pos)
|
||||
|
||||
delta = width / 2
|
||||
if (direction == 'West'):
|
||||
endpoints = (180+delta, 180-delta)
|
||||
elif (direction == 'North'):
|
||||
endpoints = (90+delta, 90-delta)
|
||||
elif (direction == 'South'):
|
||||
endpoints = (270+delta, 270-delta)
|
||||
else:
|
||||
endpoints = (0+delta, 0-delta)
|
||||
return endpoints
|
||||
|
||||
def movePacman(self, position, direction, image):
|
||||
screenPosition = self.to_screen(position)
|
||||
endpoints = self.getEndpoints(direction, position)
|
||||
r = PACMAN_SCALE * self.gridSize
|
||||
moveCircle(image[0], screenPosition, r, endpoints)
|
||||
refresh()
|
||||
|
||||
def animatePacman(self, pacman, prevPacman, image):
|
||||
if self.frameTime < 0:
|
||||
print('Press any key to step forward, "q" to play')
|
||||
keys = wait_for_keys()
|
||||
if 'q' in keys:
|
||||
self.frameTime = 0.1
|
||||
if self.frameTime > 0.01 or self.frameTime < 0:
|
||||
start = time.time()
|
||||
fx, fy = self.getPosition(prevPacman)
|
||||
px, py = self.getPosition(pacman)
|
||||
frames = 4.0
|
||||
for i in range(1, int(frames) + 1):
|
||||
pos = px*i/frames + fx * \
|
||||
(frames-i)/frames, py*i/frames + fy*(frames-i)/frames
|
||||
self.movePacman(pos, self.getDirection(pacman), image)
|
||||
refresh()
|
||||
sleep(abs(self.frameTime) / frames)
|
||||
else:
|
||||
self.movePacman(self.getPosition(pacman),
|
||||
self.getDirection(pacman), image)
|
||||
refresh()
|
||||
|
||||
def getGhostColor(self, ghost, ghostIndex):
|
||||
if ghost.scaredTimer > 0:
|
||||
return SCARED_COLOR
|
||||
else:
|
||||
return GHOST_COLORS[ghostIndex]
|
||||
|
||||
def drawGhost(self, ghost, agentIndex):
|
||||
pos = self.getPosition(ghost)
|
||||
dir = self.getDirection(ghost)
|
||||
(screen_x, screen_y) = (self.to_screen(pos))
|
||||
coords = []
|
||||
for (x, y) in GHOST_SHAPE:
|
||||
coords.append((x*self.gridSize*GHOST_SIZE + screen_x,
|
||||
y*self.gridSize*GHOST_SIZE + screen_y))
|
||||
|
||||
colour = self.getGhostColor(ghost, agentIndex)
|
||||
body = polygon(coords, colour, filled=1)
|
||||
WHITE = formatColor(1.0, 1.0, 1.0)
|
||||
BLACK = formatColor(0.0, 0.0, 0.0)
|
||||
|
||||
dx = 0
|
||||
dy = 0
|
||||
if dir == 'North':
|
||||
dy = -0.2
|
||||
if dir == 'South':
|
||||
dy = 0.2
|
||||
if dir == 'East':
|
||||
dx = 0.2
|
||||
if dir == 'West':
|
||||
dx = -0.2
|
||||
leftEye = circle((screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx/1.5), screen_y -
|
||||
self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2, WHITE, WHITE)
|
||||
rightEye = circle((screen_x+self.gridSize*GHOST_SIZE*(0.3+dx/1.5), screen_y -
|
||||
self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2, WHITE, WHITE)
|
||||
leftPupil = circle((screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx), screen_y -
|
||||
self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08, BLACK, BLACK)
|
||||
rightPupil = circle((screen_x+self.gridSize*GHOST_SIZE*(0.3+dx), screen_y -
|
||||
self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08, BLACK, BLACK)
|
||||
ghostImageParts = []
|
||||
ghostImageParts.append(body)
|
||||
ghostImageParts.append(leftEye)
|
||||
ghostImageParts.append(rightEye)
|
||||
ghostImageParts.append(leftPupil)
|
||||
ghostImageParts.append(rightPupil)
|
||||
|
||||
return ghostImageParts
|
||||
|
||||
def moveEyes(self, pos, dir, eyes):
|
||||
(screen_x, screen_y) = (self.to_screen(pos))
|
||||
dx = 0
|
||||
dy = 0
|
||||
if dir == 'North':
|
||||
dy = -0.2
|
||||
if dir == 'South':
|
||||
dy = 0.2
|
||||
if dir == 'East':
|
||||
dx = 0.2
|
||||
if dir == 'West':
|
||||
dx = -0.2
|
||||
moveCircle(eyes[0], (screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx/1.5), screen_y -
|
||||
self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2)
|
||||
moveCircle(eyes[1], (screen_x+self.gridSize*GHOST_SIZE*(0.3+dx/1.5), screen_y -
|
||||
self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2)
|
||||
moveCircle(eyes[2], (screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx), screen_y -
|
||||
self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08)
|
||||
moveCircle(eyes[3], (screen_x+self.gridSize*GHOST_SIZE*(0.3+dx), screen_y -
|
||||
self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08)
|
||||
|
||||
def moveGhost(self, ghost, ghostIndex, prevGhost, ghostImageParts):
|
||||
old_x, old_y = self.to_screen(self.getPosition(prevGhost))
|
||||
new_x, new_y = self.to_screen(self.getPosition(ghost))
|
||||
delta = new_x - old_x, new_y - old_y
|
||||
|
||||
for ghostImagePart in ghostImageParts:
|
||||
move_by(ghostImagePart, delta)
|
||||
refresh()
|
||||
|
||||
if ghost.scaredTimer > 0:
|
||||
color = SCARED_COLOR
|
||||
else:
|
||||
color = GHOST_COLORS[ghostIndex]
|
||||
edit(ghostImageParts[0], ('fill', color), ('outline', color))
|
||||
self.moveEyes(self.getPosition(ghost),
|
||||
self.getDirection(ghost), ghostImageParts[-4:])
|
||||
refresh()
|
||||
|
||||
def getPosition(self, agentState):
|
||||
if agentState.configuration == None:
|
||||
return (-1000, -1000)
|
||||
return agentState.getPosition()
|
||||
|
||||
def getDirection(self, agentState):
|
||||
if agentState.configuration == None:
|
||||
return Directions.STOP
|
||||
return agentState.configuration.getDirection()
|
||||
|
||||
def finish(self):
|
||||
end_graphics()
|
||||
|
||||
def to_screen(self, point):
|
||||
(x, y) = point
|
||||
#y = self.height - y
|
||||
x = (x + 1)*self.gridSize
|
||||
y = (self.height - y)*self.gridSize
|
||||
return (x, y)
|
||||
|
||||
# Fixes some TK issue with off-center circles
|
||||
def to_screen2(self, point):
|
||||
(x, y) = point
|
||||
#y = self.height - y
|
||||
x = (x + 1)*self.gridSize
|
||||
y = (self.height - y)*self.gridSize
|
||||
return (x, y)
|
||||
|
||||
def drawWalls(self, wallMatrix):
|
||||
wallColor = WALL_COLOR
|
||||
for xNum, x in enumerate(wallMatrix):
|
||||
if self.capture and (xNum * 2) < wallMatrix.width:
|
||||
wallColor = TEAM_COLORS[0]
|
||||
if self.capture and (xNum * 2) >= wallMatrix.width:
|
||||
wallColor = TEAM_COLORS[1]
|
||||
|
||||
for yNum, cell in enumerate(x):
|
||||
if cell: # There's a wall here
|
||||
pos = (xNum, yNum)
|
||||
screen = self.to_screen(pos)
|
||||
screen2 = self.to_screen2(pos)
|
||||
|
||||
# draw each quadrant of the square based on adjacent walls
|
||||
wIsWall = self.isWall(xNum-1, yNum, wallMatrix)
|
||||
eIsWall = self.isWall(xNum+1, yNum, wallMatrix)
|
||||
nIsWall = self.isWall(xNum, yNum+1, wallMatrix)
|
||||
sIsWall = self.isWall(xNum, yNum-1, wallMatrix)
|
||||
nwIsWall = self.isWall(xNum-1, yNum+1, wallMatrix)
|
||||
swIsWall = self.isWall(xNum-1, yNum-1, wallMatrix)
|
||||
neIsWall = self.isWall(xNum+1, yNum+1, wallMatrix)
|
||||
seIsWall = self.isWall(xNum+1, yNum-1, wallMatrix)
|
||||
|
||||
# NE quadrant
|
||||
if (not nIsWall) and (not eIsWall):
|
||||
# inner circle
|
||||
circle(screen2, WALL_RADIUS * self.gridSize,
|
||||
wallColor, wallColor, (0, 91), 'arc')
|
||||
if (nIsWall) and (not eIsWall):
|
||||
# vertical line
|
||||
line(add(screen, (self.gridSize*WALL_RADIUS, 0)), add(screen,
|
||||
(self.gridSize*WALL_RADIUS, self.gridSize*(-0.5)-1)), wallColor)
|
||||
if (not nIsWall) and (eIsWall):
|
||||
# horizontal line
|
||||
line(add(screen, (0, self.gridSize*(-1)*WALL_RADIUS)), add(screen,
|
||||
(self.gridSize*0.5+1, self.gridSize*(-1)*WALL_RADIUS)), wallColor)
|
||||
if (nIsWall) and (eIsWall) and (not neIsWall):
|
||||
# outer circle
|
||||
circle(add(screen2, (self.gridSize*2*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS)),
|
||||
WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (180, 271), 'arc')
|
||||
line(add(screen, (self.gridSize*2*WALL_RADIUS-1, self.gridSize*(-1)*WALL_RADIUS)),
|
||||
add(screen, (self.gridSize*0.5+1, self.gridSize*(-1)*WALL_RADIUS)), wallColor)
|
||||
line(add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS+1)),
|
||||
add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-0.5))), wallColor)
|
||||
|
||||
# NW quadrant
|
||||
if (not nIsWall) and (not wIsWall):
|
||||
# inner circle
|
||||
circle(screen2, WALL_RADIUS * self.gridSize,
|
||||
wallColor, wallColor, (90, 181), 'arc')
|
||||
if (nIsWall) and (not wIsWall):
|
||||
# vertical line
|
||||
line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, 0)), add(screen,
|
||||
(self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-0.5)-1)), wallColor)
|
||||
if (not nIsWall) and (wIsWall):
|
||||
# horizontal line
|
||||
line(add(screen, (0, self.gridSize*(-1)*WALL_RADIUS)), add(screen,
|
||||
(self.gridSize*(-0.5)-1, self.gridSize*(-1)*WALL_RADIUS)), wallColor)
|
||||
if (nIsWall) and (wIsWall) and (not nwIsWall):
|
||||
# outer circle
|
||||
circle(add(screen2, (self.gridSize*(-2)*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS)),
|
||||
WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (270, 361), 'arc')
|
||||
line(add(screen, (self.gridSize*(-2)*WALL_RADIUS+1, self.gridSize*(-1)*WALL_RADIUS)),
|
||||
add(screen, (self.gridSize*(-0.5), self.gridSize*(-1)*WALL_RADIUS)), wallColor)
|
||||
line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS+1)),
|
||||
add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-0.5))), wallColor)
|
||||
|
||||
# SE quadrant
|
||||
if (not sIsWall) and (not eIsWall):
|
||||
# inner circle
|
||||
circle(screen2, WALL_RADIUS * self.gridSize,
|
||||
wallColor, wallColor, (270, 361), 'arc')
|
||||
if (sIsWall) and (not eIsWall):
|
||||
# vertical line
|
||||
line(add(screen, (self.gridSize*WALL_RADIUS, 0)), add(screen,
|
||||
(self.gridSize*WALL_RADIUS, self.gridSize*(0.5)+1)), wallColor)
|
||||
if (not sIsWall) and (eIsWall):
|
||||
# horizontal line
|
||||
line(add(screen, (0, self.gridSize*(1)*WALL_RADIUS)), add(screen,
|
||||
(self.gridSize*0.5+1, self.gridSize*(1)*WALL_RADIUS)), wallColor)
|
||||
if (sIsWall) and (eIsWall) and (not seIsWall):
|
||||
# outer circle
|
||||
circle(add(screen2, (self.gridSize*2*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS)),
|
||||
WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (90, 181), 'arc')
|
||||
line(add(screen, (self.gridSize*2*WALL_RADIUS-1, self.gridSize*(1)*WALL_RADIUS)),
|
||||
add(screen, (self.gridSize*0.5, self.gridSize*(1)*WALL_RADIUS)), wallColor)
|
||||
line(add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS-1)),
|
||||
add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(0.5))), wallColor)
|
||||
|
||||
# SW quadrant
|
||||
if (not sIsWall) and (not wIsWall):
|
||||
# inner circle
|
||||
circle(screen2, WALL_RADIUS * self.gridSize,
|
||||
wallColor, wallColor, (180, 271), 'arc')
|
||||
if (sIsWall) and (not wIsWall):
|
||||
# vertical line
|
||||
line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, 0)), add(screen,
|
||||
(self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(0.5)+1)), wallColor)
|
||||
if (not sIsWall) and (wIsWall):
|
||||
# horizontal line
|
||||
line(add(screen, (0, self.gridSize*(1)*WALL_RADIUS)), add(screen,
|
||||
(self.gridSize*(-0.5)-1, self.gridSize*(1)*WALL_RADIUS)), wallColor)
|
||||
if (sIsWall) and (wIsWall) and (not swIsWall):
|
||||
# outer circle
|
||||
circle(add(screen2, (self.gridSize*(-2)*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS)),
|
||||
WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (0, 91), 'arc')
|
||||
line(add(screen, (self.gridSize*(-2)*WALL_RADIUS+1, self.gridSize*(1)*WALL_RADIUS)),
|
||||
add(screen, (self.gridSize*(-0.5), self.gridSize*(1)*WALL_RADIUS)), wallColor)
|
||||
line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS-1)),
|
||||
add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(0.5))), wallColor)
|
||||
|
||||
def isWall(self, x, y, walls):
|
||||
if x < 0 or y < 0:
|
||||
return False
|
||||
if x >= walls.width or y >= walls.height:
|
||||
return False
|
||||
return walls[x][y]
|
||||
|
||||
def drawFood(self, foodMatrix):
|
||||
foodImages = []
|
||||
color = FOOD_COLOR
|
||||
for xNum, x in enumerate(foodMatrix):
|
||||
if self.capture and (xNum * 2) <= foodMatrix.width:
|
||||
color = TEAM_COLORS[0]
|
||||
if self.capture and (xNum * 2) > foodMatrix.width:
|
||||
color = TEAM_COLORS[1]
|
||||
imageRow = []
|
||||
foodImages.append(imageRow)
|
||||
for yNum, cell in enumerate(x):
|
||||
if cell: # There's food here
|
||||
screen = self.to_screen((xNum, yNum))
|
||||
dot = circle(screen,
|
||||
FOOD_SIZE * self.gridSize,
|
||||
outlineColor=color, fillColor=color,
|
||||
width=1)
|
||||
imageRow.append(dot)
|
||||
else:
|
||||
imageRow.append(None)
|
||||
return foodImages
|
||||
|
||||
def drawCapsules(self, capsules):
|
||||
capsuleImages = {}
|
||||
for capsule in capsules:
|
||||
(screen_x, screen_y) = self.to_screen(capsule)
|
||||
dot = circle((screen_x, screen_y),
|
||||
CAPSULE_SIZE * self.gridSize,
|
||||
outlineColor=CAPSULE_COLOR,
|
||||
fillColor=CAPSULE_COLOR,
|
||||
width=1)
|
||||
capsuleImages[capsule] = dot
|
||||
return capsuleImages
|
||||
|
||||
def removeFood(self, cell, foodImages):
|
||||
x, y = cell
|
||||
remove_from_screen(foodImages[x][y])
|
||||
|
||||
def removeCapsule(self, cell, capsuleImages):
|
||||
x, y = cell
|
||||
remove_from_screen(capsuleImages[(x, y)])
|
||||
|
||||
def drawExpandedCells(self, cells):
|
||||
"""
|
||||
Draws an overlay of expanded grid positions for search agents
|
||||
"""
|
||||
n = float(len(cells))
|
||||
baseColor = [1.0, 0.0, 0.0]
|
||||
self.clearExpandedCells()
|
||||
self.expandedCells = []
|
||||
for k, cell in enumerate(cells):
|
||||
screenPos = self.to_screen(cell)
|
||||
cellColor = formatColor(
|
||||
*[(n-k) * c * .5 / n + .25 for c in baseColor])
|
||||
block = square(screenPos,
|
||||
0.5 * self.gridSize,
|
||||
color=cellColor,
|
||||
filled=1, behind=2)
|
||||
self.expandedCells.append(block)
|
||||
if self.frameTime < 0:
|
||||
refresh()
|
||||
|
||||
def clearExpandedCells(self):
|
||||
if 'expandedCells' in dir(self) and len(self.expandedCells) > 0:
|
||||
for cell in self.expandedCells:
|
||||
remove_from_screen(cell)
|
||||
|
||||
def updateDistributions(self, distributions):
|
||||
"Draws an agent's belief distributions"
|
||||
# copy all distributions so we don't change their state
|
||||
distributions = [x.copy() for x in distributions]
|
||||
if self.distributionImages == None:
|
||||
self.drawDistributions(self.previousState)
|
||||
for x in range(len(self.distributionImages)):
|
||||
for y in range(len(self.distributionImages[0])):
|
||||
image = self.distributionImages[x][y]
|
||||
weights = [dist[(x, y)] for dist in distributions]
|
||||
|
||||
if sum(weights) != 0:
|
||||
pass
|
||||
# Fog of war
|
||||
color = [0.0, 0.0, 0.0]
|
||||
colors = GHOST_VEC_COLORS[1:] # With Pacman
|
||||
if self.capture:
|
||||
colors = GHOST_VEC_COLORS
|
||||
for weight, gcolor in zip(weights, colors):
|
||||
color = [min(1.0, c + 0.95 * g * weight ** .3)
|
||||
for c, g in zip(color, gcolor)]
|
||||
changeColor(image, formatColor(*color))
|
||||
refresh()
|
||||
|
||||
|
||||
class FirstPersonPacmanGraphics(PacmanGraphics):
|
||||
def __init__(self, zoom=1.0, showGhosts=True, capture=False, frameTime=0):
|
||||
PacmanGraphics.__init__(self, zoom, frameTime=frameTime)
|
||||
self.showGhosts = showGhosts
|
||||
self.capture = capture
|
||||
|
||||
def initialize(self, state, isBlue=False):
|
||||
|
||||
self.isBlue = isBlue
|
||||
PacmanGraphics.startGraphics(self, state)
|
||||
# Initialize distribution images
|
||||
walls = state.layout.walls
|
||||
dist = []
|
||||
self.layout = state.layout
|
||||
|
||||
# Draw the rest
|
||||
self.distributionImages = None # initialize lazily
|
||||
self.drawStaticObjects(state)
|
||||
self.drawAgentObjects(state)
|
||||
|
||||
# Information
|
||||
self.previousState = state
|
||||
|
||||
def lookAhead(self, config, state):
|
||||
if config.getDirection() == 'Stop':
|
||||
return
|
||||
else:
|
||||
pass
|
||||
# Draw relevant ghosts
|
||||
allGhosts = state.getGhostStates()
|
||||
visibleGhosts = state.getVisibleGhosts()
|
||||
for i, ghost in enumerate(allGhosts):
|
||||
if ghost in visibleGhosts:
|
||||
self.drawGhost(ghost, i)
|
||||
else:
|
||||
self.currentGhostImages[i] = None
|
||||
|
||||
def getGhostColor(self, ghost, ghostIndex):
|
||||
return GHOST_COLORS[ghostIndex]
|
||||
|
||||
def getPosition(self, ghostState):
|
||||
if not self.showGhosts and not ghostState.isPacman and ghostState.getPosition()[1] > 1:
|
||||
return (-1000, -1000)
|
||||
else:
|
||||
return PacmanGraphics.getPosition(self, ghostState)
|
||||
|
||||
|
||||
def add(x, y):
|
||||
return (x[0] + y[0], x[1] + y[1])
|
||||
|
||||
|
||||
# Saving graphical output
|
||||
# -----------------------
|
||||
# Note: to make an animated gif from this postscript output, try the command:
|
||||
# convert -delay 7 -loop 1 -compress lzw -layers optimize frame* out.gif
|
||||
# convert is part of imagemagick (freeware)
|
||||
|
||||
SAVE_POSTSCRIPT = False
|
||||
POSTSCRIPT_OUTPUT_DIR = 'frames'
|
||||
FRAME_NUMBER = 0
|
||||
import os
|
||||
|
||||
|
||||
def saveFrame():
|
||||
"Saves the current graphical output as a postscript file"
|
||||
global SAVE_POSTSCRIPT, FRAME_NUMBER, POSTSCRIPT_OUTPUT_DIR
|
||||
if not SAVE_POSTSCRIPT:
|
||||
return
|
||||
if not os.path.exists(POSTSCRIPT_OUTPUT_DIR):
|
||||
os.mkdir(POSTSCRIPT_OUTPUT_DIR)
|
||||
name = os.path.join(POSTSCRIPT_OUTPUT_DIR, 'frame_%08d.ps' % FRAME_NUMBER)
|
||||
FRAME_NUMBER += 1
|
||||
writePostscript(name) # writes the current canvas
|
||||
451
proj2/graphicsUtils.py
Normal file
451
proj2/graphicsUtils.py
Normal file
@ -0,0 +1,451 @@
|
||||
# graphicsUtils.py
|
||||
# ----------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
import sys
|
||||
import math
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
import types
|
||||
import tkinter
|
||||
import os.path
|
||||
|
||||
_Windows = sys.platform == 'win32' # True if on Win95/98/NT
|
||||
|
||||
_root_window = None # The root window for graphics output
|
||||
_canvas = None # The canvas which holds graphics
|
||||
_canvas_xs = None # Size of canvas object
|
||||
_canvas_ys = None
|
||||
_canvas_x = None # Current position on canvas
|
||||
_canvas_y = None
|
||||
_canvas_col = None # Current colour (set to black below)
|
||||
_canvas_tsize = 12
|
||||
_canvas_tserifs = 0
|
||||
|
||||
|
||||
def formatColor(r, g, b):
|
||||
return '#%02x%02x%02x' % (int(r * 255), int(g * 255), int(b * 255))
|
||||
|
||||
|
||||
def colorToVector(color):
|
||||
return [int(x, 16) / 256.0 for x in [color[1:3], color[3:5], color[5:7]]]
|
||||
|
||||
|
||||
if _Windows:
|
||||
_canvas_tfonts = ['times new roman', 'lucida console']
|
||||
else:
|
||||
_canvas_tfonts = ['times', 'lucidasans-24']
|
||||
pass # XXX need defaults here
|
||||
|
||||
|
||||
def sleep(secs):
|
||||
global _root_window
|
||||
if _root_window == None:
|
||||
time.sleep(secs)
|
||||
else:
|
||||
_root_window.update_idletasks()
|
||||
_root_window.after(int(1000 * secs), _root_window.quit)
|
||||
_root_window.mainloop()
|
||||
|
||||
|
||||
def begin_graphics(width=640, height=480, color=formatColor(0, 0, 0), title=None):
|
||||
|
||||
global _root_window, _canvas, _canvas_x, _canvas_y, _canvas_xs, _canvas_ys, _bg_color
|
||||
|
||||
# Check for duplicate call
|
||||
if _root_window is not None:
|
||||
# Lose the window.
|
||||
_root_window.destroy()
|
||||
|
||||
# Save the canvas size parameters
|
||||
_canvas_xs, _canvas_ys = width - 1, height - 1
|
||||
_canvas_x, _canvas_y = 0, _canvas_ys
|
||||
_bg_color = color
|
||||
|
||||
# Create the root window
|
||||
_root_window = tkinter.Tk()
|
||||
_root_window.protocol('WM_DELETE_WINDOW', _destroy_window)
|
||||
_root_window.title(title or 'Graphics Window')
|
||||
_root_window.resizable(0, 0)
|
||||
|
||||
# Create the canvas object
|
||||
try:
|
||||
_canvas = tkinter.Canvas(_root_window, width=width, height=height)
|
||||
_canvas.pack()
|
||||
draw_background()
|
||||
_canvas.update()
|
||||
except:
|
||||
_root_window = None
|
||||
raise
|
||||
|
||||
# Bind to key-down and key-up events
|
||||
_root_window.bind("<KeyPress>", _keypress)
|
||||
_root_window.bind("<KeyRelease>", _keyrelease)
|
||||
_root_window.bind("<FocusIn>", _clear_keys)
|
||||
_root_window.bind("<FocusOut>", _clear_keys)
|
||||
_root_window.bind("<Button-1>", _leftclick)
|
||||
_root_window.bind("<Button-2>", _rightclick)
|
||||
_root_window.bind("<Button-3>", _rightclick)
|
||||
_root_window.bind("<Control-Button-1>", _ctrl_leftclick)
|
||||
_clear_keys()
|
||||
|
||||
|
||||
_leftclick_loc = None
|
||||
_rightclick_loc = None
|
||||
_ctrl_leftclick_loc = None
|
||||
|
||||
|
||||
def _leftclick(event):
|
||||
global _leftclick_loc
|
||||
_leftclick_loc = (event.x, event.y)
|
||||
|
||||
|
||||
def _rightclick(event):
|
||||
global _rightclick_loc
|
||||
_rightclick_loc = (event.x, event.y)
|
||||
|
||||
|
||||
def _ctrl_leftclick(event):
|
||||
global _ctrl_leftclick_loc
|
||||
_ctrl_leftclick_loc = (event.x, event.y)
|
||||
|
||||
|
||||
def wait_for_click():
|
||||
while True:
|
||||
global _leftclick_loc
|
||||
global _rightclick_loc
|
||||
global _ctrl_leftclick_loc
|
||||
if _leftclick_loc != None:
|
||||
val = _leftclick_loc
|
||||
_leftclick_loc = None
|
||||
return val, 'left'
|
||||
if _rightclick_loc != None:
|
||||
val = _rightclick_loc
|
||||
_rightclick_loc = None
|
||||
return val, 'right'
|
||||
if _ctrl_leftclick_loc != None:
|
||||
val = _ctrl_leftclick_loc
|
||||
_ctrl_leftclick_loc = None
|
||||
return val, 'ctrl_left'
|
||||
sleep(0.05)
|
||||
|
||||
|
||||
def draw_background():
|
||||
corners = [(0, 0), (0, _canvas_ys),
|
||||
(_canvas_xs, _canvas_ys), (_canvas_xs, 0)]
|
||||
polygon(corners, _bg_color, fillColor=_bg_color,
|
||||
filled=True, smoothed=False)
|
||||
|
||||
|
||||
def _destroy_window(event=None):
|
||||
sys.exit(0)
|
||||
# global _root_window
|
||||
# _root_window.destroy()
|
||||
# _root_window = None
|
||||
# print "DESTROY"
|
||||
|
||||
|
||||
def end_graphics():
|
||||
global _root_window, _canvas, _mouse_enabled
|
||||
try:
|
||||
try:
|
||||
sleep(1)
|
||||
if _root_window != None:
|
||||
_root_window.destroy()
|
||||
except SystemExit as e:
|
||||
print('Ending graphics raised an exception:', e)
|
||||
finally:
|
||||
_root_window = None
|
||||
_canvas = None
|
||||
_mouse_enabled = 0
|
||||
_clear_keys()
|
||||
|
||||
|
||||
def clear_screen(background=None):
|
||||
global _canvas_x, _canvas_y
|
||||
_canvas.delete('all')
|
||||
draw_background()
|
||||
_canvas_x, _canvas_y = 0, _canvas_ys
|
||||
|
||||
|
||||
def polygon(coords, outlineColor, fillColor=None, filled=1, smoothed=1, behind=0, width=1):
|
||||
c = []
|
||||
for coord in coords:
|
||||
c.append(coord[0])
|
||||
c.append(coord[1])
|
||||
if fillColor == None:
|
||||
fillColor = outlineColor
|
||||
if filled == 0:
|
||||
fillColor = ""
|
||||
poly = _canvas.create_polygon(
|
||||
c, outline=outlineColor, fill=fillColor, smooth=smoothed, width=width)
|
||||
if behind > 0:
|
||||
_canvas.tag_lower(poly, behind) # Higher should be more visible
|
||||
return poly
|
||||
|
||||
|
||||
def square(pos, r, color, filled=1, behind=0):
|
||||
x, y = pos
|
||||
coords = [(x - r, y - r), (x + r, y - r), (x + r, y + r), (x - r, y + r)]
|
||||
return polygon(coords, color, color, filled, 0, behind=behind)
|
||||
|
||||
|
||||
def circle(pos, r, outlineColor, fillColor, endpoints=None, style='pieslice', width=2):
|
||||
x, y = pos
|
||||
x0, x1 = x - r - 1, x + r
|
||||
y0, y1 = y - r - 1, y + r
|
||||
if endpoints == None:
|
||||
e = [0, 359]
|
||||
else:
|
||||
e = list(endpoints)
|
||||
while e[0] > e[1]:
|
||||
e[1] = e[1] + 360
|
||||
|
||||
return _canvas.create_arc(x0, y0, x1, y1, outline=outlineColor, fill=fillColor,
|
||||
extent=e[1] - e[0], start=e[0], style=style, width=width)
|
||||
|
||||
|
||||
def image(pos, file="../../blueghost.gif"):
|
||||
x, y = pos
|
||||
# img = PhotoImage(file=file)
|
||||
return _canvas.create_image(x, y, image=tkinter.PhotoImage(file=file), anchor=tkinter.NW)
|
||||
|
||||
|
||||
def refresh():
|
||||
_canvas.update_idletasks()
|
||||
|
||||
|
||||
def moveCircle(id, pos, r, endpoints=None):
|
||||
global _canvas_x, _canvas_y
|
||||
|
||||
x, y = pos
|
||||
# x0, x1 = x - r, x + r + 1
|
||||
# y0, y1 = y - r, y + r + 1
|
||||
x0, x1 = x - r - 1, x + r
|
||||
y0, y1 = y - r - 1, y + r
|
||||
if endpoints == None:
|
||||
e = [0, 359]
|
||||
else:
|
||||
e = list(endpoints)
|
||||
while e[0] > e[1]:
|
||||
e[1] = e[1] + 360
|
||||
|
||||
if os.path.isfile('flag'):
|
||||
edit(id, ('extent', e[1] - e[0]))
|
||||
else:
|
||||
edit(id, ('start', e[0]), ('extent', e[1] - e[0]))
|
||||
move_to(id, x0, y0)
|
||||
|
||||
|
||||
def edit(id, *args):
|
||||
_canvas.itemconfigure(id, **dict(args))
|
||||
|
||||
|
||||
def text(pos, color, contents, font='Helvetica', size=12, style='normal', anchor="nw"):
|
||||
global _canvas_x, _canvas_y
|
||||
x, y = pos
|
||||
font = (font, str(size), style)
|
||||
return _canvas.create_text(x, y, fill=color, text=contents, font=font, anchor=anchor)
|
||||
|
||||
|
||||
def changeText(id, newText, font=None, size=12, style='normal'):
|
||||
_canvas.itemconfigure(id, text=newText)
|
||||
if font != None:
|
||||
_canvas.itemconfigure(id, font=(font, '-%d' % size, style))
|
||||
|
||||
|
||||
def changeColor(id, newColor):
|
||||
_canvas.itemconfigure(id, fill=newColor)
|
||||
|
||||
|
||||
def line(here, there, color=formatColor(0, 0, 0), width=2):
|
||||
x0, y0 = here[0], here[1]
|
||||
x1, y1 = there[0], there[1]
|
||||
return _canvas.create_line(x0, y0, x1, y1, fill=color, width=width)
|
||||
|
||||
##############################################################################
|
||||
### Keypress handling ########################################################
|
||||
##############################################################################
|
||||
|
||||
# We bind to key-down and key-up events.
|
||||
|
||||
|
||||
_keysdown = {}
|
||||
_keyswaiting = {}
|
||||
# This holds an unprocessed key release. We delay key releases by up to
|
||||
# one call to keys_pressed() to get round a problem with auto repeat.
|
||||
_got_release = None
|
||||
|
||||
|
||||
def _keypress(event):
|
||||
global _got_release
|
||||
# remap_arrows(event)
|
||||
_keysdown[event.keysym] = 1
|
||||
_keyswaiting[event.keysym] = 1
|
||||
# print event.char, event.keycode
|
||||
_got_release = None
|
||||
|
||||
|
||||
def _keyrelease(event):
|
||||
global _got_release
|
||||
# remap_arrows(event)
|
||||
try:
|
||||
del _keysdown[event.keysym]
|
||||
except:
|
||||
pass
|
||||
_got_release = 1
|
||||
|
||||
|
||||
def remap_arrows(event):
|
||||
# TURN ARROW PRESSES INTO LETTERS (SHOULD BE IN KEYBOARD AGENT)
|
||||
if event.char in ['a', 's', 'd', 'w']:
|
||||
return
|
||||
if event.keycode in [37, 101]: # LEFT ARROW (win / x)
|
||||
event.char = 'a'
|
||||
if event.keycode in [38, 99]: # UP ARROW
|
||||
event.char = 'w'
|
||||
if event.keycode in [39, 102]: # RIGHT ARROW
|
||||
event.char = 'd'
|
||||
if event.keycode in [40, 104]: # DOWN ARROW
|
||||
event.char = 's'
|
||||
|
||||
|
||||
def _clear_keys(event=None):
|
||||
global _keysdown, _got_release, _keyswaiting
|
||||
_keysdown = {}
|
||||
_keyswaiting = {}
|
||||
_got_release = None
|
||||
|
||||
|
||||
def keys_pressed(d_o_e=lambda arg: _root_window.dooneevent(arg),
|
||||
d_w=tkinter._tkinter.DONT_WAIT):
|
||||
d_o_e(d_w)
|
||||
if _got_release:
|
||||
d_o_e(d_w)
|
||||
return list(_keysdown.keys())
|
||||
|
||||
|
||||
def keys_waiting():
|
||||
global _keyswaiting
|
||||
keys = list(_keyswaiting.keys())
|
||||
_keyswaiting = {}
|
||||
return keys
|
||||
|
||||
# Block for a list of keys...
|
||||
|
||||
|
||||
def wait_for_keys():
|
||||
keys = []
|
||||
while keys == []:
|
||||
keys = keys_pressed()
|
||||
sleep(0.05)
|
||||
return keys
|
||||
|
||||
|
||||
def remove_from_screen(x,
|
||||
d_o_e=lambda arg: _root_window.dooneevent(arg),
|
||||
d_w=tkinter._tkinter.DONT_WAIT):
|
||||
_canvas.delete(x)
|
||||
d_o_e(d_w)
|
||||
|
||||
|
||||
def _adjust_coords(coord_list, x, y):
|
||||
for i in range(0, len(coord_list), 2):
|
||||
coord_list[i] = coord_list[i] + x
|
||||
coord_list[i + 1] = coord_list[i + 1] + y
|
||||
return coord_list
|
||||
|
||||
|
||||
def move_to(object, x, y=None,
|
||||
d_o_e=lambda arg: _root_window.dooneevent(arg),
|
||||
d_w=tkinter._tkinter.DONT_WAIT):
|
||||
if y is None:
|
||||
try:
|
||||
x, y = x
|
||||
except:
|
||||
raise Exception('incomprehensible coordinates')
|
||||
|
||||
horiz = True
|
||||
newCoords = []
|
||||
current_x, current_y = _canvas.coords(object)[0:2] # first point
|
||||
for coord in _canvas.coords(object):
|
||||
if horiz:
|
||||
inc = x - current_x
|
||||
else:
|
||||
inc = y - current_y
|
||||
horiz = not horiz
|
||||
|
||||
newCoords.append(coord + inc)
|
||||
|
||||
_canvas.coords(object, *newCoords)
|
||||
d_o_e(d_w)
|
||||
|
||||
|
||||
def move_by(object, x, y=None,
|
||||
d_o_e=lambda arg: _root_window.dooneevent(arg),
|
||||
d_w=tkinter._tkinter.DONT_WAIT, lift=False):
|
||||
if y is None:
|
||||
try:
|
||||
x, y = x
|
||||
except:
|
||||
raise Exception('incomprehensible coordinates')
|
||||
|
||||
horiz = True
|
||||
newCoords = []
|
||||
for coord in _canvas.coords(object):
|
||||
if horiz:
|
||||
inc = x
|
||||
else:
|
||||
inc = y
|
||||
horiz = not horiz
|
||||
|
||||
newCoords.append(coord + inc)
|
||||
|
||||
_canvas.coords(object, *newCoords)
|
||||
d_o_e(d_w)
|
||||
if lift:
|
||||
_canvas.tag_raise(object)
|
||||
|
||||
|
||||
def writePostscript(filename):
|
||||
"Writes the current canvas to a postscript file."
|
||||
psfile = file(filename, 'w')
|
||||
psfile.write(_canvas.postscript(pageanchor='sw',
|
||||
y='0.c',
|
||||
x='0.c'))
|
||||
psfile.close()
|
||||
|
||||
|
||||
ghost_shape = [
|
||||
(0, - 0.5),
|
||||
(0.25, - 0.75),
|
||||
(0.5, - 0.5),
|
||||
(0.75, - 0.75),
|
||||
(0.75, 0.5),
|
||||
(0.5, 0.75),
|
||||
(- 0.5, 0.75),
|
||||
(- 0.75, 0.5),
|
||||
(- 0.75, - 0.75),
|
||||
(- 0.5, - 0.5),
|
||||
(- 0.25, - 0.75)
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
begin_graphics()
|
||||
clear_screen()
|
||||
ghost_shape = [(x * 10 + 20, y * 10 + 20) for x, y in ghost_shape]
|
||||
g = polygon(ghost_shape, formatColor(1, 1, 1))
|
||||
move_to(g, (50, 50))
|
||||
circle((150, 150), 20, formatColor(0.7, 0.3, 0.0), endpoints=[15, - 15])
|
||||
sleep(2)
|
||||
95
proj2/keyboardAgents.py
Normal file
95
proj2/keyboardAgents.py
Normal file
@ -0,0 +1,95 @@
|
||||
# keyboardAgents.py
|
||||
# -----------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
from game import Agent
|
||||
from game import Directions
|
||||
import random
|
||||
|
||||
|
||||
class KeyboardAgent(Agent):
|
||||
"""
|
||||
An agent controlled by the keyboard.
|
||||
"""
|
||||
# NOTE: Arrow keys also work.
|
||||
WEST_KEY = 'a'
|
||||
EAST_KEY = 'd'
|
||||
NORTH_KEY = 'w'
|
||||
SOUTH_KEY = 's'
|
||||
STOP_KEY = 'q'
|
||||
|
||||
def __init__(self, index=0):
|
||||
|
||||
self.lastMove = Directions.STOP
|
||||
self.index = index
|
||||
self.keys = []
|
||||
|
||||
def getAction(self, state):
|
||||
from graphicsUtils import keys_waiting
|
||||
from graphicsUtils import keys_pressed
|
||||
keys = keys_waiting() + keys_pressed()
|
||||
if keys != []:
|
||||
self.keys = keys
|
||||
|
||||
legal = state.getLegalActions(self.index)
|
||||
move = self.getMove(legal)
|
||||
|
||||
if move == Directions.STOP:
|
||||
# Try to move in the same direction as before
|
||||
if self.lastMove in legal:
|
||||
move = self.lastMove
|
||||
|
||||
if (self.STOP_KEY in self.keys) and Directions.STOP in legal:
|
||||
move = Directions.STOP
|
||||
|
||||
if move not in legal:
|
||||
move = random.choice(legal)
|
||||
|
||||
self.lastMove = move
|
||||
return move
|
||||
|
||||
def getMove(self, legal):
|
||||
move = Directions.STOP
|
||||
if (self.WEST_KEY in self.keys or 'Left' in self.keys) and Directions.WEST in legal:
|
||||
move = Directions.WEST
|
||||
if (self.EAST_KEY in self.keys or 'Right' in self.keys) and Directions.EAST in legal:
|
||||
move = Directions.EAST
|
||||
if (self.NORTH_KEY in self.keys or 'Up' in self.keys) and Directions.NORTH in legal:
|
||||
move = Directions.NORTH
|
||||
if (self.SOUTH_KEY in self.keys or 'Down' in self.keys) and Directions.SOUTH in legal:
|
||||
move = Directions.SOUTH
|
||||
return move
|
||||
|
||||
|
||||
class KeyboardAgent2(KeyboardAgent):
|
||||
"""
|
||||
A second agent controlled by the keyboard.
|
||||
"""
|
||||
# NOTE: Arrow keys also work.
|
||||
WEST_KEY = 'j'
|
||||
EAST_KEY = "l"
|
||||
NORTH_KEY = 'i'
|
||||
SOUTH_KEY = 'k'
|
||||
STOP_KEY = 'u'
|
||||
|
||||
def getMove(self, legal):
|
||||
move = Directions.STOP
|
||||
if (self.WEST_KEY in self.keys) and Directions.WEST in legal:
|
||||
move = Directions.WEST
|
||||
if (self.EAST_KEY in self.keys) and Directions.EAST in legal:
|
||||
move = Directions.EAST
|
||||
if (self.NORTH_KEY in self.keys) and Directions.NORTH in legal:
|
||||
move = Directions.NORTH
|
||||
if (self.SOUTH_KEY in self.keys) and Directions.SOUTH in legal:
|
||||
move = Directions.SOUTH
|
||||
return move
|
||||
177
proj2/labtemplate.typ
Executable file
177
proj2/labtemplate.typ
Executable file
@ -0,0 +1,177 @@
|
||||
#let times = "Times LT Pro"
|
||||
#let times = "Times New Roman"
|
||||
#let song = (times, "Noto Serif CJK SC")
|
||||
#let hei = (times, "Noto Sans CJK SC")
|
||||
#let kai = (times, "Noto Serif CJK SC")
|
||||
#let xbsong = (times, "Noto Serif CJK SC")
|
||||
#let fsong = (times, "Noto Serif CJK SC")
|
||||
#let code = (times, "JetBrains Mono")
|
||||
#let nudtlabpaper(title: "",
|
||||
author: "",
|
||||
id: "",
|
||||
training_type:"",
|
||||
grade: "",
|
||||
major: "",
|
||||
department: "",
|
||||
advisor: "",
|
||||
jobtitle: "",
|
||||
lab: "",
|
||||
date: "",
|
||||
header_str: "",
|
||||
simple_cover: "",
|
||||
body) = {
|
||||
// Set the document's basic properties.
|
||||
set document(author: author, title: title)
|
||||
set page(
|
||||
|
||||
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
|
||||
)
|
||||
|
||||
// Title row.
|
||||
v(158pt)
|
||||
align(center)[
|
||||
#block(text(weight: 700, size: 30pt, font: hei, tracking: 15pt, "人工智能实验报告"))
|
||||
]
|
||||
// align(center)[
|
||||
// #block(text(weight: 700, size: 30pt, font: song, tracking: 15pt, "本科实验报告"))
|
||||
// ]
|
||||
|
||||
v(83pt)
|
||||
pad(
|
||||
left: 1em,
|
||||
right: 1em,
|
||||
grid(
|
||||
columns: (80pt, 1fr),
|
||||
rows: (17pt, auto),
|
||||
text(weight: 700, size: 16pt, font: song, "实验名称:"),
|
||||
align(center, text(weight: "regular", size: 16pt, font: song, title)),
|
||||
text(""),
|
||||
line(length: 100%)
|
||||
)
|
||||
// #block(text(weight: 700, 1.75em, title))
|
||||
// underline(text(weight: 700, size: 16pt, font: song, title))
|
||||
)
|
||||
|
||||
// Author information.
|
||||
|
||||
v(82.5pt)
|
||||
|
||||
if simple_cover {
|
||||
pad(
|
||||
left: 10%,
|
||||
right: 10%,
|
||||
grid(
|
||||
columns: (100pt, 1fr),
|
||||
rows: (15pt, 8pt, 15pt, 8pt, 15pt, 8pt),
|
||||
text(size: 14pt, font: song, "学员姓名:"),
|
||||
align(center, text(size: 14pt, font: song, author)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(size: 14pt, font: song, "学 号:"),
|
||||
align(center, text(size: 14pt, font: times, id)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(size: 14pt, font: song, "实验日期:"),
|
||||
align(center, text(size: 14pt, font: song, date)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
grid(
|
||||
columns: (0.25fr, 0.25fr, 0.25fr, 0.25fr),
|
||||
rows: (15pt, 8pt, 15pt, 8pt, 15pt, 8pt, 15pt, 8pt, 15pt),
|
||||
text(size: 14pt, font: song, tracking: 10pt, "学员姓名"),
|
||||
align(center, text(size: 14pt, font: song, author)),
|
||||
text(size: 14pt, font: song, tracking: 54pt, "学号"),
|
||||
align(center, text(size: 14pt, font: times, id)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(size: 14pt, font: song, tracking: 9pt, "培养类型"),
|
||||
align(center, text(size: 14pt, font: song, training_type)),
|
||||
text(size: 14pt, font: song, tracking: 54pt, "年级"),
|
||||
align(center, text(size: 14pt, font: times, grade)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(size: 14pt, font: song, tracking: 54pt, "专业"),
|
||||
align(center, text(size: 14pt, font: song, major)),
|
||||
text(size: 14pt, font: song, tracking: 9pt, "所属学院"),
|
||||
align(center, text(size: 14pt, font: song, department)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(size: 14pt, font: song, tracking: 9pt, "指导教员"),
|
||||
align(center, text(size: 14pt, font: song, advisor)),
|
||||
text(size: 14pt, font: song, tracking: 54pt, "职称"),
|
||||
align(center, text(size: 14pt, font: song, jobtitle)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(size: 14pt, font: song, tracking: 20pt, "实验室"),
|
||||
align(center, text(size: 14pt, font: song, lab)),
|
||||
text(size: 14pt, font: song, tracking: 9pt, "实验时间"),
|
||||
align(center, text(size: 14pt, font: song, date)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
)
|
||||
}
|
||||
|
||||
v(50.5pt)
|
||||
align(center, text(font: hei, size: 15pt, "国防科技大学教育训练部制"))
|
||||
|
||||
pagebreak()
|
||||
|
||||
set page(
|
||||
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
|
||||
numbering: "i",
|
||||
number-align: center,
|
||||
)
|
||||
|
||||
v(14pt)
|
||||
align(center)[
|
||||
#block(text(font: hei, size: 14pt, "《本科实验报告》填写说明"))
|
||||
]
|
||||
|
||||
v(14pt)
|
||||
text("")
|
||||
par(first-line-indent: 2em, text(font: song, size: 12pt, "实验报告内容编排应符合以下要求:"))
|
||||
|
||||
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "(1)采用A4(21cm×29.7cm)白色复印纸,单面黑字。上下左右各侧的页边距均为3cm;缺省文档网格:字号为小4号,中文为宋体,英文和阿拉伯数字为Times New Roman,每页30行,每行36字;页脚距边界为2.5cm,页码置于页脚、居中,采用小5号阿拉伯数字从1开始连续编排,封面不编页码。"))
|
||||
|
||||
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "(2)报告正文最多可设四级标题,字体均为黑体,第一级标题字号为4号,其余各级标题为小4号;标题序号第一级用“一、”、“二、”……,第二级用“(一)”、“(二)” ……,第三级用“1.”、“2.” ……,第四级用“(1)”、“(2)” ……,分别按序连续编排。"))
|
||||
|
||||
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "(3)正文插图、表格中的文字字号均为5号。"))
|
||||
|
||||
pagebreak()
|
||||
|
||||
set page(
|
||||
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
|
||||
numbering: "1",
|
||||
number-align: center,
|
||||
)
|
||||
|
||||
set heading(numbering: "1.1")
|
||||
// set text(font: hei, lang: "zh")
|
||||
|
||||
show heading: it => box(width: 100%)[
|
||||
#v(0.50em)
|
||||
#set text(font: hei)
|
||||
#counter(heading).display()
|
||||
// #h(0.5em)
|
||||
#it.body
|
||||
]
|
||||
// Main body.
|
||||
set par(justify: true)
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
#let para(t) = par(first-line-indent: 2em, text(font: song, size: 10.5pt, t))
|
||||
164
proj2/layout.py
Normal file
164
proj2/layout.py
Normal file
@ -0,0 +1,164 @@
|
||||
# layout.py
|
||||
# ---------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
from util import manhattanDistance
|
||||
from game import Grid
|
||||
import os
|
||||
import random
|
||||
from functools import reduce
|
||||
|
||||
VISIBILITY_MATRIX_CACHE = {}
|
||||
|
||||
|
||||
class Layout:
|
||||
"""
|
||||
A Layout manages the static information about the game board.
|
||||
"""
|
||||
|
||||
def __init__(self, layoutText):
|
||||
self.width = len(layoutText[0])
|
||||
self.height = len(layoutText)
|
||||
self.walls = Grid(self.width, self.height, False)
|
||||
self.food = Grid(self.width, self.height, False)
|
||||
self.capsules = []
|
||||
self.agentPositions = []
|
||||
self.numGhosts = 0
|
||||
self.processLayoutText(layoutText)
|
||||
self.layoutText = layoutText
|
||||
self.totalFood = len(self.food.asList())
|
||||
# self.initializeVisibilityMatrix()
|
||||
|
||||
def getNumGhosts(self):
|
||||
return self.numGhosts
|
||||
|
||||
def initializeVisibilityMatrix(self):
|
||||
global VISIBILITY_MATRIX_CACHE
|
||||
if reduce(str.__add__, self.layoutText) not in VISIBILITY_MATRIX_CACHE:
|
||||
from game import Directions
|
||||
vecs = [(-0.5, 0), (0.5, 0), (0, -0.5), (0, 0.5)]
|
||||
dirs = [Directions.NORTH, Directions.SOUTH,
|
||||
Directions.WEST, Directions.EAST]
|
||||
vis = Grid(self.width, self.height, {Directions.NORTH: set(), Directions.SOUTH: set(
|
||||
), Directions.EAST: set(), Directions.WEST: set(), Directions.STOP: set()})
|
||||
for x in range(self.width):
|
||||
for y in range(self.height):
|
||||
if self.walls[x][y] == False:
|
||||
for vec, direction in zip(vecs, dirs):
|
||||
dx, dy = vec
|
||||
nextx, nexty = x + dx, y + dy
|
||||
while (nextx + nexty) != int(nextx) + int(nexty) or not self.walls[int(nextx)][int(nexty)]:
|
||||
vis[x][y][direction].add((nextx, nexty))
|
||||
nextx, nexty = x + dx, y + dy
|
||||
self.visibility = vis
|
||||
VISIBILITY_MATRIX_CACHE[reduce(str.__add__, self.layoutText)] = vis
|
||||
else:
|
||||
self.visibility = VISIBILITY_MATRIX_CACHE[reduce(
|
||||
str.__add__, self.layoutText)]
|
||||
|
||||
def isWall(self, pos):
|
||||
x, col = pos
|
||||
return self.walls[x][col]
|
||||
|
||||
def getRandomLegalPosition(self):
|
||||
x = random.choice(list(range(self.width)))
|
||||
y = random.choice(list(range(self.height)))
|
||||
while self.isWall((x, y)):
|
||||
x = random.choice(list(range(self.width)))
|
||||
y = random.choice(list(range(self.height)))
|
||||
return (x, y)
|
||||
|
||||
def getRandomCorner(self):
|
||||
poses = [(1, 1), (1, self.height - 2), (self.width - 2, 1),
|
||||
(self.width - 2, self.height - 2)]
|
||||
return random.choice(poses)
|
||||
|
||||
def getFurthestCorner(self, pacPos):
|
||||
poses = [(1, 1), (1, self.height - 2), (self.width - 2, 1),
|
||||
(self.width - 2, self.height - 2)]
|
||||
dist, pos = max([(manhattanDistance(p, pacPos), p) for p in poses])
|
||||
return pos
|
||||
|
||||
def isVisibleFrom(self, ghostPos, pacPos, pacDirection):
|
||||
row, col = [int(x) for x in pacPos]
|
||||
return ghostPos in self.visibility[row][col][pacDirection]
|
||||
|
||||
def __str__(self):
|
||||
return "\n".join(self.layoutText)
|
||||
|
||||
def deepCopy(self):
|
||||
return Layout(self.layoutText[:])
|
||||
|
||||
def processLayoutText(self, layoutText):
|
||||
"""
|
||||
Coordinates are flipped from the input format to the (x,y) convention here
|
||||
|
||||
The shape of the maze. Each character
|
||||
represents a different type of object.
|
||||
% - Wall
|
||||
. - Food
|
||||
o - Capsule
|
||||
G - Ghost
|
||||
P - Pacman
|
||||
Other characters are ignored.
|
||||
"""
|
||||
maxY = self.height - 1
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
layoutChar = layoutText[maxY - y][x]
|
||||
self.processLayoutChar(x, y, layoutChar)
|
||||
self.agentPositions.sort()
|
||||
self.agentPositions = [(i == 0, pos) for i, pos in self.agentPositions]
|
||||
|
||||
def processLayoutChar(self, x, y, layoutChar):
|
||||
if layoutChar == '%':
|
||||
self.walls[x][y] = True
|
||||
elif layoutChar == '.':
|
||||
self.food[x][y] = True
|
||||
elif layoutChar == 'o':
|
||||
self.capsules.append((x, y))
|
||||
elif layoutChar == 'P':
|
||||
self.agentPositions.append((0, (x, y)))
|
||||
elif layoutChar in ['G']:
|
||||
self.agentPositions.append((1, (x, y)))
|
||||
self.numGhosts += 1
|
||||
elif layoutChar in ['1', '2', '3', '4']:
|
||||
self.agentPositions.append((int(layoutChar), (x, y)))
|
||||
self.numGhosts += 1
|
||||
|
||||
|
||||
def getLayout(name, back=2):
|
||||
if name.endswith('.lay'):
|
||||
layout = tryToLoad('layouts/' + name)
|
||||
if layout == None:
|
||||
layout = tryToLoad(name)
|
||||
else:
|
||||
layout = tryToLoad('layouts/' + name + '.lay')
|
||||
if layout == None:
|
||||
layout = tryToLoad(name + '.lay')
|
||||
if layout == None and back >= 0:
|
||||
curdir = os.path.abspath('.')
|
||||
os.chdir('..')
|
||||
layout = getLayout(name, back - 1)
|
||||
os.chdir(curdir)
|
||||
return layout
|
||||
|
||||
|
||||
def tryToLoad(fullname):
|
||||
if(not os.path.exists(fullname)):
|
||||
return None
|
||||
f = open(fullname)
|
||||
try:
|
||||
return Layout([line.strip() for line in f])
|
||||
finally:
|
||||
f.close()
|
||||
7
proj2/layouts/capsuleClassic.lay
Normal file
7
proj2/layouts/capsuleClassic.lay
Normal file
@ -0,0 +1,7 @@
|
||||
%%%%%%%%%%%%%%%%%%%
|
||||
%G. G ....%
|
||||
%.% % %%%%%% %.%%.%
|
||||
%.%o% % o% %.o%.%
|
||||
%.%%%.% %%% %..%.%
|
||||
%..... P %..%G%
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
9
proj2/layouts/contestClassic.lay
Normal file
9
proj2/layouts/contestClassic.lay
Normal file
@ -0,0 +1,9 @@
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
%o...%........%...o%
|
||||
%.%%.%.%%..%%.%.%%.%
|
||||
%...... G GG%......%
|
||||
%.%.%%.%% %%%.%%.%.%
|
||||
%.%....% ooo%.%..%.%
|
||||
%.%.%%.% %% %.%.%%.%
|
||||
%o%......P....%....%
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
11
proj2/layouts/mediumClassic.lay
Normal file
11
proj2/layouts/mediumClassic.lay
Normal file
@ -0,0 +1,11 @@
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
%o...%........%....%
|
||||
%.%%.%.%%%%%%.%.%%.%
|
||||
%.%..............%.%
|
||||
%.%.%%.%% %%.%%.%.%
|
||||
%......%G G%......%
|
||||
%.%.%%.%%%%%%.%%.%.%
|
||||
%.%..............%.%
|
||||
%.%%.%.%%%%%%.%.%%.%
|
||||
%....%...P....%...o%
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
5
proj2/layouts/minimaxClassic.lay
Normal file
5
proj2/layouts/minimaxClassic.lay
Normal file
@ -0,0 +1,5 @@
|
||||
%%%%%%%%%
|
||||
%.P G%
|
||||
% %.%G%%%
|
||||
%G %%%
|
||||
%%%%%%%%%
|
||||
9
proj2/layouts/openClassic.lay
Normal file
9
proj2/layouts/openClassic.lay
Normal file
@ -0,0 +1,9 @@
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%.. P .... .... %
|
||||
%.. ... ... ... ... %
|
||||
%.. ... ... ... ... %
|
||||
%.. .... .... G %
|
||||
%.. ... ... ... ... %
|
||||
%.. ... ... ... ... %
|
||||
%.. .... .... o%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
27
proj2/layouts/originalClassic.lay
Normal file
27
proj2/layouts/originalClassic.lay
Normal file
@ -0,0 +1,27 @@
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%............%%............%
|
||||
%.%%%%.%%%%%.%%.%%%%%.%%%%.%
|
||||
%o%%%%.%%%%%.%%.%%%%%.%%%%o%
|
||||
%.%%%%.%%%%%.%%.%%%%%.%%%%.%
|
||||
%..........................%
|
||||
%.%%%%.%%.%%%%%%%%.%%.%%%%.%
|
||||
%.%%%%.%%.%%%%%%%%.%%.%%%%.%
|
||||
%......%%....%%....%%......%
|
||||
%%%%%%.%%%%% %% %%%%%.%%%%%%
|
||||
%%%%%%.%%%%% %% %%%%%.%%%%%%
|
||||
%%%%%%.% %.%%%%%%
|
||||
%%%%%%.% %%%% %%%% %.%%%%%%
|
||||
% . %G GG G% . %
|
||||
%%%%%%.% %%%%%%%%%% %.%%%%%%
|
||||
%%%%%%.% %.%%%%%%
|
||||
%%%%%%.% %%%%%%%%%% %.%%%%%%
|
||||
%............%%............%
|
||||
%.%%%%.%%%%%.%%.%%%%%.%%%%.%
|
||||
%.%%%%.%%%%%.%%.%%%%%.%%%%.%
|
||||
%o..%%....... .......%%..o%
|
||||
%%%.%%.%%.%%%%%%%%.%%.%%.%%%
|
||||
%%%.%%.%%.%%%%%%%%.%%.%%.%%%
|
||||
%......%%....%%....%%......%
|
||||
%.%%%%%%%%%%.%%.%%%%%%%%%%.%
|
||||
%.............P............%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
7
proj2/layouts/powerClassic.lay
Normal file
7
proj2/layouts/powerClassic.lay
Normal file
@ -0,0 +1,7 @@
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
%o....o%GGGG%o....o%
|
||||
%..%...%% %%...%..%
|
||||
%.%o.%........%.o%.%
|
||||
%.o%.%.%%%%%%.%.%o.%
|
||||
%........P.........%
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
7
proj2/layouts/smallClassic.lay
Normal file
7
proj2/layouts/smallClassic.lay
Normal file
@ -0,0 +1,7 @@
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
%......%G G%......%
|
||||
%.%%...%% %%...%%.%
|
||||
%.%o.%........%.o%.%
|
||||
%.%%.%.%%%%%%.%.%%.%
|
||||
%........P.........%
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
10
proj2/layouts/testClassic.lay
Normal file
10
proj2/layouts/testClassic.lay
Normal file
@ -0,0 +1,10 @@
|
||||
%%%%%
|
||||
% . %
|
||||
%.G.%
|
||||
% . %
|
||||
%. .%
|
||||
% %
|
||||
% .%
|
||||
% %
|
||||
%P .%
|
||||
%%%%%
|
||||
5
proj2/layouts/trappedClassic.lay
Normal file
5
proj2/layouts/trappedClassic.lay
Normal file
@ -0,0 +1,5 @@
|
||||
%%%%%%%%
|
||||
% P G%
|
||||
%G%%%%%%
|
||||
%.... %
|
||||
%%%%%%%%
|
||||
13
proj2/layouts/trickyClassic.lay
Normal file
13
proj2/layouts/trickyClassic.lay
Normal file
@ -0,0 +1,13 @@
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
%o...%........%...o%
|
||||
%.%%.%.%%..%%.%.%%.%
|
||||
%.%.....%..%.....%.%
|
||||
%.%.%%.%% %%.%%.%.%
|
||||
%...... GGGG%.%....%
|
||||
%.%....%%%%%%.%..%.%
|
||||
%.%....% oo%.%..%.%
|
||||
%.%....% %%%%.%..%.%
|
||||
%.%...........%..%.%
|
||||
%.%%.%.%%%%%%.%.%%.%
|
||||
%o...%...P....%...o%
|
||||
%%%%%%%%%%%%%%%%%%%%
|
||||
266
proj2/main.typ
Executable file
266
proj2/main.typ
Executable file
@ -0,0 +1,266 @@
|
||||
#import "labtemplate.typ": *
|
||||
#show: nudtlabpaper.with(
|
||||
author: "程景愉",
|
||||
id: "202302723005",
|
||||
title: "博弈搜索",
|
||||
training_type: "普通本科生",
|
||||
grade: "2023级",
|
||||
major: "网络工程",
|
||||
department: "计算机学院",
|
||||
advisor: "胡罡",
|
||||
jobtitle: "教授",
|
||||
lab: "306-707",
|
||||
date: "2025.12.02",
|
||||
header_str: "博弈搜索实验报告",
|
||||
simple_cover: true,
|
||||
)
|
||||
|
||||
#set page(header: [
|
||||
#set par(spacing: 6pt)
|
||||
#align(center)[#text(size: 11pt)[人工智能实验报告]]
|
||||
#v(-0.3em)
|
||||
#line(length: 100%, stroke: (thickness: 1pt))
|
||||
],)
|
||||
|
||||
#show heading: it => if it.level == 1 [
|
||||
#v(1em)
|
||||
#set text(font: hei, size: 16pt)
|
||||
#counter(heading).display()
|
||||
#h(0.5em)
|
||||
#it.body
|
||||
#v(0.5em)
|
||||
] else [
|
||||
#v(0.8em)
|
||||
#set text(font: "New Computer Modern", weight: "bold", style: "italic")
|
||||
#counter(heading).display(it.numbering)
|
||||
#h(0.5em)
|
||||
#it.body
|
||||
#v(0.5em)
|
||||
]
|
||||
|
||||
#outline(title: "目录",depth: 2, indent: 1em)
|
||||
|
||||
#set enum(indent: 0.5em,body-indent: 0.5em,)
|
||||
#pagebreak()
|
||||
|
||||
= 实验介绍
|
||||
#para[
|
||||
本项目是UC Berkeley CS188人工智能课程的第二个项目,重点在于对抗搜索(Adversarial Search)。在此项目中,我们将编写控制Pacman的智能体,使其能够在不仅有静止障碍物和食物,还有主动追捕Pacman的幽灵(Ghosts)的环境中生存并获胜。实验涉及的核心算法包括Reflex Agent(反射智能体)、Minimax(极大极小算法)、Alpha-Beta剪枝以及Expectimax(期望最大算法)。此外,还需要设计高效的评估函数来指导智能体在有限深度的搜索中做出最优决策。通过本实验,我们将深入理解博弈树搜索、剪枝优化以及非完全理性对手建模等关键概念。
|
||||
]
|
||||
|
||||
= 实验内容
|
||||
#para[
|
||||
本次实验内容涵盖了5个核心编程任务,具体如下:
|
||||
]
|
||||
+ *Q1: Reflex Agent*:实现一个能够根据当前状态特征(如食物距离、幽灵位置)做出即时反应的反射智能体。
|
||||
+ *Q2: Minimax*:实现通用的Minimax算法,使Pacman能够假设幽灵采取最优策略(即尽力让Pacman得分最低)的情况下,规划出最优路径。
|
||||
+ *Q3: Alpha-Beta Pruning*:在Minimax的基础上引入Alpha-Beta剪枝,以减少不必要的节点扩展,提高搜索效率,从而支持更深层次的搜索。
|
||||
+ *Q4: Expectimax*:实现Expectimax算法,不再假设幽灵是最优对手,而是将其建模为随机行动的对手,通过计算期望得分来最大化收益。
|
||||
+ *Q5: Evaluation Function*:设计一个更强大的评估函数,综合考虑多种游戏状态特征,使智能体在搜索深度有限时仍能准确评估局面好坏。
|
||||
|
||||
= 实验要求
|
||||
#para[
|
||||
本项目要求在 `multiAgents.py` 文件中根据指引补全代码,核心要求如下:
|
||||
]
|
||||
+ *文件修改*:仅允许修改 `multiAgents.py`,其他文件(如 `pacman.py`, `game.py`)不得修改,以确保与自动评分器的兼容性。
|
||||
+ *算法通用性*:实现的Minimax和Alpha-Beta算法必须支持任意数量的幽灵(Min层)和任意指定的搜索深度。
|
||||
+ *剪枝正确性*:在Q3中,必须正确实现Alpha-Beta剪枝逻辑,不能改变节点的遍历顺序(必须按照 `getLegalActions` 返回的顺序),且不能进行基于等值的剪枝,以完全匹配自动评分器的扩展节点数检查。
|
||||
+ *性能指标*:对于Q1和Q5,设计的评估函数必须使Pacman在测试关卡中达到一定的胜率(如Q1需赢得5次以上,Q5需在smallClassic上赢得全部10次且均分超过1000),且运行时间需在规定范围内。
|
||||
|
||||
= 实验步骤与实现
|
||||
|
||||
== Q1: Reflex Agent (反射智能体)
|
||||
*实现思路*:Reflex Agent 不进行树搜索,而是基于当前状态的直接评估来选择动作。我们在 `evaluationFunction` 中设计了一个简单的线性组合评分系统。主要考虑两个因素:一是离食物越近越好(使用距离倒数作为激励);二是如果幽灵太近且未受惊,则必须避开(给予极大的惩罚)。
|
||||
|
||||
*核心代码* (`multiAgents.py`):
|
||||
```python
|
||||
def evaluationFunction(self, currentGameState: GameState, action):
|
||||
successorGameState = currentGameState.generatePacmanSuccessor(action)
|
||||
newPos = successorGameState.getPacmanPosition()
|
||||
newFood = successorGameState.getFood()
|
||||
newGhostStates = successorGameState.getGhostStates()
|
||||
newScaredTimes = [ghostState.scaredTimer for ghostState in newGhostStates]
|
||||
|
||||
# 计算到最近食物的距离
|
||||
foodList = newFood.asList()
|
||||
if not foodList:
|
||||
return 999999
|
||||
minFoodDist = min([util.manhattanDistance(newPos, food) for food in foodList])
|
||||
|
||||
# 避免危险幽灵
|
||||
for i, ghostState in enumerate(newGhostStates):
|
||||
dist = util.manhattanDistance(newPos, ghostState.getPosition())
|
||||
if dist < 2 and newScaredTimes[i] == 0:
|
||||
return -999999
|
||||
|
||||
# 综合评分:基础分 + 食物激励
|
||||
return successorGameState.getScore() + 1.0 / (minFoodDist + 1)
|
||||
```
|
||||
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q1
|
||||
python pacman.py -p ReflexAgent -l testClassic
|
||||
```
|
||||
|
||||
== Q2: Minimax (极大极小算法)
|
||||
*实现思路*:Minimax算法构建一棵博弈树,Pacman作为MAX层试图最大化得分,而所有幽灵作为MIN层试图最小化Pacman的得分。每一层递归代表一个Agent的行动。当轮到Pacman(Agent 0)时,深度加1(实际上是所有Agent行动一轮算一层深度,这里实现时通常在回到Agent 0时增加深度计数)。
|
||||
|
||||
*核心代码* (`multiAgents.py`):
|
||||
```python
|
||||
def minimax(agentIndex, depth, gameState):
|
||||
if gameState.isWin() or gameState.isLose() or depth == self.depth:
|
||||
return self.evaluationFunction(gameState)
|
||||
|
||||
# MAX Agent (Pacman)
|
||||
if agentIndex == 0:
|
||||
bestValue = float("-inf")
|
||||
for action in gameState.getLegalActions(agentIndex):
|
||||
succ = gameState.generateSuccessor(agentIndex, action)
|
||||
bestValue = max(bestValue, minimax(agentIndex + 1, depth, succ))
|
||||
return bestValue
|
||||
# MIN Agents (Ghosts)
|
||||
else:
|
||||
bestValue = float("inf")
|
||||
for action in gameState.getLegalActions(agentIndex):
|
||||
succ = gameState.generateSuccessor(agentIndex, action)
|
||||
if agentIndex == gameState.getNumAgents() - 1:
|
||||
bestValue = min(bestValue, minimax(0, depth + 1, succ))
|
||||
else:
|
||||
bestValue = min(bestValue, minimax(agentIndex + 1, depth, succ))
|
||||
return bestValue
|
||||
```
|
||||
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q2
|
||||
python pacman.py -p MinimaxAgent -l minimaxClassic -a depth=4
|
||||
```
|
||||
|
||||
== Q3: Alpha-Beta Pruning (Alpha-Beta 剪枝)
|
||||
*实现思路*:在Minimax的基础上维护两个变量 $alpha$ (MAX节点的下界) 和 $beta$ (MIN节点的上界)。在MAX节点,如果发现某分支的值大于 $\beta$,则剪枝(因为MIN父节点不会选它);在MIN节点,如果某分支值小于 $alpha$,则剪枝(MAX父节点不会选它)。
|
||||
|
||||
*核心代码* (`multiAgents.py`):
|
||||
```python
|
||||
def maxValue(agentIndex, depth, gameState, alpha, beta):
|
||||
v = float("-inf")
|
||||
for action in gameState.getLegalActions(agentIndex):
|
||||
succ = gameState.generateSuccessor(agentIndex, action)
|
||||
v = max(v, alphaBeta(agentIndex + 1, depth, succ, alpha, beta))
|
||||
if v > beta: return v # Pruning
|
||||
alpha = max(alpha, v)
|
||||
return v
|
||||
|
||||
def minValue(agentIndex, depth, gameState, alpha, beta):
|
||||
v = float("inf")
|
||||
for action in gameState.getLegalActions(agentIndex):
|
||||
succ = gameState.generateSuccessor(agentIndex, action)
|
||||
if agentIndex == gameState.getNumAgents() - 1:
|
||||
v = min(v, alphaBeta(0, depth + 1, succ, alpha, beta))
|
||||
else:
|
||||
v = min(v, alphaBeta(agentIndex + 1, depth, succ, alpha, beta))
|
||||
if v < alpha: return v # Pruning
|
||||
beta = min(beta, v)
|
||||
return v
|
||||
```
|
||||
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q3
|
||||
python pacman.py -p AlphaBetaAgent -a depth=3 -l smallClassic
|
||||
```
|
||||
|
||||
== Q4: Expectimax (期望最大算法)
|
||||
*实现思路*:Expectimax不再假设幽灵是最优对手,而是假设它们随机行动(Uniform Random)。因此,Min层变成了Chance层,计算所有后续状态得分的平均值(期望值)。这使得Pacman在面对非最优幽灵时敢于承担一定风险去获取更高收益。
|
||||
|
||||
*核心代码* (`multiAgents.py`):
|
||||
```python
|
||||
def expValue(agentIndex, depth, gameState):
|
||||
v = 0
|
||||
actions = gameState.getLegalActions(agentIndex)
|
||||
prob = 1.0 / len(actions)
|
||||
for action in actions:
|
||||
succ = gameState.generateSuccessor(agentIndex, action)
|
||||
if agentIndex == gameState.getNumAgents() - 1:
|
||||
v += prob * expectimax(0, depth + 1, succ)
|
||||
else:
|
||||
v += prob * expectimax(agentIndex + 1, depth, succ)
|
||||
return v
|
||||
```
|
||||
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q4
|
||||
python pacman.py -p ExpectimaxAgent -l minimaxClassic -a depth=3
|
||||
```
|
||||
|
||||
== Q5: Evaluation Function (评估函数)
|
||||
*实现思路*:为了在有限深度搜索中获得更好表现,`betterEvaluationFunction` 考虑了更多特征。除了基础分数,我们增加了对最近食物的奖励(权重+10),对胶囊的奖励(权重+20),以及对受惊幽灵的追捕奖励(权重+100)。同时,对靠近非受惊幽灵给予重罚(权重-1000),并根据剩余食物和胶囊的数量给予惩罚,以激励Pacman尽快清空地图。
|
||||
|
||||
*核心代码* (`multiAgents.py`):
|
||||
```python
|
||||
def betterEvaluationFunction(currentGameState: GameState):
|
||||
pos = currentGameState.getPacmanPosition()
|
||||
score = currentGameState.getScore()
|
||||
|
||||
# 距离特征
|
||||
foodDist = min([util.manhattanDistance(pos, f) for f in foodList]) if foodList else 0
|
||||
score += 10.0 / (foodDist + 1)
|
||||
|
||||
# 幽灵特征
|
||||
for i, ghost in enumerate(ghostStates):
|
||||
dist = util.manhattanDistance(pos, ghost.getPosition())
|
||||
if scaredTimes[i] > 0:
|
||||
score += 100.0 / (dist + 1) # 鼓励吃受惊幽灵
|
||||
elif dist < 2:
|
||||
score -= 1000.0 # 极力避免接触
|
||||
|
||||
# 数量特征(越少越好,故减去)
|
||||
score -= len(foodList) * 4
|
||||
score -= len(capsules) * 20
|
||||
|
||||
return score
|
||||
```
|
||||
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q5
|
||||
python autograder.py -q q5 --no-graphics
|
||||
```
|
||||
|
||||
= 实验结果
|
||||
#para[
|
||||
本项目的所有5个任务均已成功实现,并通过了自动评分器(autograder)的所有测试用例,最终取得了 *25/25* 的满分成绩。
|
||||
]
|
||||
|
||||
```
|
||||
Provisional grades
|
||||
==================
|
||||
Question q1: 4/4
|
||||
Question q2: 5/5
|
||||
Question q3: 5/5
|
||||
Question q4: 5/5
|
||||
Question q5: 6/6
|
||||
------------------
|
||||
Total: 25/25
|
||||
```
|
||||
|
||||
#para[
|
||||
主要测试结果分析:
|
||||
]
|
||||
+ *Q1 (Reflex)*: 智能体能够有效避开幽灵并吃到食物,在 `testClassic` 上表现稳定,平均分超过1000。
|
||||
+ *Q2 (Minimax)*: 成功通过了所有深度和幽灵数量的博弈树测试,扩展节点数与标准答案完全一致,证明了逻辑的正确性。
|
||||
+ *Q3 (Alpha-Beta)*: 在保持结果与Minimax一致的前提下,显著减少了扩展的节点数量。在 `smallClassic` 上,深度为3的搜索能够在极短时间内完成。
|
||||
+ *Q5 (Eval)*: 设计的评估函数表现优异,Pacman在 `smallClassic` 的10次随机测试中全部获胜,且平均得分远超1000分的要求,证明了特征选择和权重分配的合理性。
|
||||
|
||||
#pagebreak()
|
||||
= 实验总结
|
||||
#para[
|
||||
通过本次实验,我深入掌握了对抗搜索算法的核心思想及其在多智能体环境中的应用。
|
||||
]
|
||||
#para[
|
||||
首先,我实现了Minimax算法,理解了零和博弈中MAX与MIN节点的相互制约关系。随后,通过实现Alpha-Beta剪枝,我深刻体会到了剪枝技术对于提升搜索效率的重要性——它能够在不改变最终决策的前提下,指数级地减少搜索空间,使得更深层的搜索成为可能。
|
||||
]
|
||||
#para[
|
||||
其次,Expectimax算法的实现让我认识到,在面对随机或非最优对手时,概率模型往往比悲观的Minimax模型更具优势。最后,也是最具挑战性的部分,是设计评估函数。我学会了如何将游戏状态抽象为特征向量(如食物距离、幽灵状态等),并通过线性组合这些特征来量化状态的优劣。这不仅考验了代码实现能力,更考验了对问题本质的理解和特征工程的能力。
|
||||
]
|
||||
391
proj2/multiAgents.py
Normal file
391
proj2/multiAgents.py
Normal file
@ -0,0 +1,391 @@
|
||||
# multiAgents.py
|
||||
# --------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
from util import manhattanDistance
|
||||
from game import Directions
|
||||
import random, util
|
||||
|
||||
from game import Agent
|
||||
from pacman import GameState
|
||||
|
||||
class ReflexAgent(Agent):
|
||||
"""
|
||||
A reflex agent chooses an action at each choice point by examining
|
||||
its alternatives via a state evaluation function.
|
||||
|
||||
The code below is provided as a guide. You are welcome to change
|
||||
it in any way you see fit, so long as you don't touch our method
|
||||
headers.
|
||||
"""
|
||||
|
||||
|
||||
def getAction(self, gameState: GameState):
|
||||
"""
|
||||
You do not need to change this method, but you're welcome to.
|
||||
|
||||
getAction chooses among the best options according to the evaluation function.
|
||||
|
||||
Just like in the previous project, getAction takes a GameState and returns
|
||||
some Directions.X for some X in the set {NORTH, SOUTH, WEST, EAST, STOP}
|
||||
"""
|
||||
# Collect legal moves and successor states
|
||||
legalMoves = gameState.getLegalActions()
|
||||
|
||||
# Choose one of the best actions
|
||||
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) # Pick randomly among the best
|
||||
|
||||
"Add more of your code here if you want to"
|
||||
|
||||
return legalMoves[chosenIndex]
|
||||
|
||||
def evaluationFunction(self, currentGameState: GameState, action):
|
||||
"""
|
||||
Design a better evaluation function here.
|
||||
|
||||
The evaluation function takes in the current and proposed successor
|
||||
GameStates (pacman.py) and returns a number, where higher numbers are better.
|
||||
|
||||
The code below extracts some useful information from the state, like the
|
||||
remaining food (newFood) and Pacman position after moving (newPos).
|
||||
newScaredTimes holds the number of moves that each ghost will remain
|
||||
scared because of Pacman having eaten a power pellet.
|
||||
|
||||
Print out these variables to see what you're getting, then combine them
|
||||
to create a masterful evaluation function.
|
||||
"""
|
||||
# Useful information you can extract from a GameState (pacman.py)
|
||||
successorGameState = currentGameState.generatePacmanSuccessor(action)
|
||||
newPos = successorGameState.getPacmanPosition()
|
||||
newFood = successorGameState.getFood()
|
||||
newGhostStates = successorGameState.getGhostStates()
|
||||
newScaredTimes = [ghostState.scaredTimer for ghostState in newGhostStates]
|
||||
|
||||
"*** YOUR CODE HERE ***"
|
||||
# 计算到最近食物的距离
|
||||
# Calculate distance to the nearest food
|
||||
foodList = newFood.asList()
|
||||
if not foodList:
|
||||
return 999999
|
||||
|
||||
minFoodDist = min([util.manhattanDistance(newPos, food) for food in foodList])
|
||||
|
||||
# 计算到幽灵的距离
|
||||
# Calculate distance to ghosts
|
||||
for i, ghostState in enumerate(newGhostStates):
|
||||
ghostPos = ghostState.getPosition()
|
||||
dist = util.manhattanDistance(newPos, ghostPos)
|
||||
# 如果幽灵太近且不处于惊吓状态,逃跑!
|
||||
# If ghost is too close and not scared, run away!
|
||||
if dist < 2 and newScaredTimes[i] == 0:
|
||||
return -999999
|
||||
|
||||
# 结合分数和食物距离
|
||||
# 我们优先考虑生存(上面已处理),然后是分数,然后是靠近食物
|
||||
# Combine score and food distance
|
||||
# We prioritize survival (handled above) then score, then getting closer to food
|
||||
return successorGameState.getScore() + 1.0 / (minFoodDist + 1)
|
||||
|
||||
def scoreEvaluationFunction(currentGameState: GameState):
|
||||
"""
|
||||
This default evaluation function just returns the score of the state.
|
||||
The score is the same one displayed in the Pacman GUI.
|
||||
|
||||
This evaluation function is meant for use with adversarial search agents
|
||||
(not reflex agents).
|
||||
"""
|
||||
return currentGameState.getScore()
|
||||
|
||||
class MultiAgentSearchAgent(Agent):
|
||||
"""
|
||||
This class provides some common elements to all of your
|
||||
multi-agent searchers. Any methods defined here will be available
|
||||
to the MinimaxPacmanAgent, AlphaBetaPacmanAgent & ExpectimaxPacmanAgent.
|
||||
|
||||
You *do not* need to make any changes here, but you can if you want to
|
||||
add functionality to all your adversarial search agents. Please do not
|
||||
remove anything, however.
|
||||
|
||||
Note: this is an abstract class: one that should not be instantiated. It's
|
||||
only partially specified, and designed to be extended. Agent (game.py)
|
||||
is another abstract class.
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
class MinimaxAgent(MultiAgentSearchAgent):
|
||||
"""
|
||||
Your minimax agent (question 2)
|
||||
"""
|
||||
|
||||
def getAction(self, gameState: 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 ***"
|
||||
# 核心 minimax 算法函数
|
||||
def minimax(agentIndex, depth, gameState):
|
||||
# 终止条件:达到指定深度,或者游戏胜利/失败
|
||||
if gameState.isWin() or gameState.isLose() or depth == self.depth:
|
||||
return self.evaluationFunction(gameState)
|
||||
|
||||
# 也就是 Pacman (MAX agent)
|
||||
if agentIndex == 0:
|
||||
return maxLevel(agentIndex, depth, gameState)
|
||||
# 也就是 Ghosts (MIN agents)
|
||||
else:
|
||||
return minLevel(agentIndex, depth, gameState)
|
||||
|
||||
# Max 层 (Pacman)
|
||||
def maxLevel(agentIndex, depth, gameState):
|
||||
bestValue = float("-inf")
|
||||
# 遍历 Pacman 的所有合法动作
|
||||
for action in gameState.getLegalActions(agentIndex):
|
||||
successorGameState = gameState.generateSuccessor(agentIndex, action)
|
||||
# 下一个是第一个幽灵 (agentIndex + 1)
|
||||
value = minimax(agentIndex + 1, depth, successorGameState)
|
||||
bestValue = max(bestValue, value)
|
||||
return bestValue
|
||||
|
||||
# Min 层 (Ghosts)
|
||||
def minLevel(agentIndex, depth, gameState):
|
||||
bestValue = float("inf")
|
||||
# 遍历幽灵的所有合法动作
|
||||
for action in gameState.getLegalActions(agentIndex):
|
||||
successorGameState = gameState.generateSuccessor(agentIndex, action)
|
||||
# 如果是最后一个幽灵,下一个是 Pacman,且深度加 1
|
||||
if agentIndex == gameState.getNumAgents() - 1:
|
||||
value = minimax(0, depth + 1, successorGameState)
|
||||
# 否则是下一个幽灵
|
||||
else:
|
||||
value = minimax(agentIndex + 1, depth, successorGameState)
|
||||
bestValue = min(bestValue, value)
|
||||
return bestValue
|
||||
|
||||
# getAction 主逻辑
|
||||
# 针对根节点 (Pacman at current depth) 选择最佳动作
|
||||
bestAction = None
|
||||
bestValue = float("-inf")
|
||||
|
||||
for action in gameState.getLegalActions(0):
|
||||
successorGameState = gameState.generateSuccessor(0, action)
|
||||
# 从第一个幽灵开始计算
|
||||
value = minimax(1, 0, successorGameState)
|
||||
if value > bestValue:
|
||||
bestValue = value
|
||||
bestAction = action
|
||||
|
||||
return bestAction
|
||||
|
||||
class AlphaBetaAgent(MultiAgentSearchAgent):
|
||||
"""
|
||||
Your minimax agent with alpha-beta pruning (question 3)
|
||||
"""
|
||||
|
||||
def getAction(self, gameState: GameState):
|
||||
"""
|
||||
Returns the minimax action using self.depth and self.evaluationFunction
|
||||
"""
|
||||
"*** YOUR CODE HERE ***"
|
||||
# 核心 alpha-beta 算法函数
|
||||
def alphaBeta(agentIndex, depth, gameState, alpha, beta):
|
||||
# 终止条件:达到指定深度,或者游戏胜利/失败
|
||||
if gameState.isWin() or gameState.isLose() or depth == self.depth:
|
||||
return self.evaluationFunction(gameState)
|
||||
|
||||
if agentIndex == 0:
|
||||
return maxValue(agentIndex, depth, gameState, alpha, beta)
|
||||
else:
|
||||
return minValue(agentIndex, depth, gameState, alpha, beta)
|
||||
|
||||
# Max 值计算 (Pacman)
|
||||
def maxValue(agentIndex, depth, gameState, alpha, beta):
|
||||
v = float("-inf")
|
||||
for action in gameState.getLegalActions(agentIndex):
|
||||
successorGameState = gameState.generateSuccessor(agentIndex, action)
|
||||
v = max(v, alphaBeta(agentIndex + 1, depth, successorGameState, alpha, beta))
|
||||
# Pruning / 剪枝
|
||||
if v > beta:
|
||||
return v
|
||||
alpha = max(alpha, v)
|
||||
return v
|
||||
|
||||
# Min 值计算 (Ghosts)
|
||||
def minValue(agentIndex, depth, gameState, alpha, beta):
|
||||
v = float("inf")
|
||||
for action in gameState.getLegalActions(agentIndex):
|
||||
successorGameState = gameState.generateSuccessor(agentIndex, action)
|
||||
if agentIndex == gameState.getNumAgents() - 1:
|
||||
v = min(v, alphaBeta(0, depth + 1, successorGameState, alpha, beta))
|
||||
else:
|
||||
v = min(v, alphaBeta(agentIndex + 1, depth, successorGameState, alpha, beta))
|
||||
# Pruning / 剪枝
|
||||
if v < alpha:
|
||||
return v
|
||||
beta = min(beta, v)
|
||||
return v
|
||||
|
||||
# getAction 主逻辑
|
||||
bestAction = None
|
||||
v = float("-inf")
|
||||
alpha = float("-inf")
|
||||
beta = float("inf")
|
||||
|
||||
for action in gameState.getLegalActions(0):
|
||||
successorGameState = gameState.generateSuccessor(0, action)
|
||||
score = alphaBeta(1, 0, successorGameState, alpha, beta)
|
||||
|
||||
if score > v:
|
||||
v = score
|
||||
bestAction = action
|
||||
|
||||
# 根节点的 alpha 更新
|
||||
if v > beta:
|
||||
return bestAction # 理论上根节点不会在这里剪枝,但保持逻辑一致
|
||||
alpha = max(alpha, v)
|
||||
|
||||
return bestAction
|
||||
|
||||
class ExpectimaxAgent(MultiAgentSearchAgent):
|
||||
"""
|
||||
Your expectimax agent (question 4)
|
||||
"""
|
||||
|
||||
def getAction(self, gameState: 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 ***"
|
||||
# 核心 expectimax 算法函数
|
||||
def expectimax(agentIndex, depth, gameState):
|
||||
if gameState.isWin() or gameState.isLose() or depth == self.depth:
|
||||
return self.evaluationFunction(gameState)
|
||||
|
||||
if agentIndex == 0:
|
||||
return maxValue(agentIndex, depth, gameState)
|
||||
else:
|
||||
return expValue(agentIndex, depth, gameState)
|
||||
|
||||
def maxValue(agentIndex, depth, gameState):
|
||||
v = float("-inf")
|
||||
for action in gameState.getLegalActions(agentIndex):
|
||||
successorGameState = gameState.generateSuccessor(agentIndex, action)
|
||||
v = max(v, expectimax(agentIndex + 1, depth, successorGameState))
|
||||
return v
|
||||
|
||||
def expValue(agentIndex, depth, gameState):
|
||||
v = 0
|
||||
actions = gameState.getLegalActions(agentIndex)
|
||||
if not actions:
|
||||
return 0
|
||||
prob = 1.0 / len(actions)
|
||||
for action in actions:
|
||||
successorGameState = gameState.generateSuccessor(agentIndex, action)
|
||||
if agentIndex == gameState.getNumAgents() - 1:
|
||||
v += prob * expectimax(0, depth + 1, successorGameState)
|
||||
else:
|
||||
v += prob * expectimax(agentIndex + 1, depth, successorGameState)
|
||||
return v
|
||||
|
||||
bestAction = None
|
||||
v = float("-inf")
|
||||
for action in gameState.getLegalActions(0):
|
||||
successorGameState = gameState.generateSuccessor(0, action)
|
||||
score = expectimax(1, 0, successorGameState)
|
||||
if score > v:
|
||||
v = score
|
||||
bestAction = action
|
||||
return bestAction
|
||||
|
||||
def betterEvaluationFunction(currentGameState: GameState):
|
||||
"""
|
||||
Your extreme ghost-hunting, pellet-nabbing, food-gobbling, unstoppable
|
||||
evaluation function (question 5).
|
||||
|
||||
DESCRIPTION: <write something here so we know what you did>
|
||||
"""
|
||||
"*** YOUR CODE HERE ***"
|
||||
# 获取有用的状态信息
|
||||
# Get useful information from the state
|
||||
newPos = currentGameState.getPacmanPosition()
|
||||
newFood = currentGameState.getFood()
|
||||
newGhostStates = currentGameState.getGhostStates()
|
||||
newScaredTimes = [ghostState.scaredTimer for ghostState in newGhostStates]
|
||||
newCapsules = currentGameState.getCapsules()
|
||||
|
||||
# 基础分数
|
||||
score = currentGameState.getScore()
|
||||
|
||||
# 食物距离评分
|
||||
foodList = newFood.asList()
|
||||
if foodList:
|
||||
minFoodDist = min([util.manhattanDistance(newPos, food) for food in foodList])
|
||||
score += 10.0 / (minFoodDist + 1) # 距离越近分数越高
|
||||
|
||||
# 胶囊距离评分
|
||||
if newCapsules:
|
||||
minCapDist = min([util.manhattanDistance(newPos, cap) for cap in newCapsules])
|
||||
score += 20.0 / (minCapDist + 1)
|
||||
|
||||
# 幽灵距离评分
|
||||
for i, ghostState in enumerate(newGhostStates):
|
||||
ghostPos = ghostState.getPosition()
|
||||
dist = util.manhattanDistance(newPos, ghostPos)
|
||||
if newScaredTimes[i] > 0:
|
||||
# 如果幽灵被吓坏了,我们要去吃它(靠近它)
|
||||
score += 100.0 / (dist + 1)
|
||||
else:
|
||||
# 否则,如果太近了,要扣分(远离它)
|
||||
if dist < 2:
|
||||
score -= 1000.0
|
||||
else:
|
||||
score -= 10.0 / (dist + 1) # 稍微远离一点
|
||||
|
||||
# 剩余食物越少越好
|
||||
score -= len(foodList) * 4
|
||||
|
||||
# 剩余胶囊越少越好
|
||||
score -= len(newCapsules) * 20
|
||||
|
||||
return score
|
||||
|
||||
# Abbreviation
|
||||
better = betterEvaluationFunction
|
||||
578
proj2/multiagentTestClasses.py
Normal file
578
proj2/multiagentTestClasses.py
Normal file
@ -0,0 +1,578 @@
|
||||
# multiagentTestClasses.py
|
||||
# ------------------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
# A minimax tree which interfaces like gameState
|
||||
# state.getNumAgents()
|
||||
# state.isWin()
|
||||
# state.isLose()
|
||||
# state.generateSuccessor(agentIndex, action)
|
||||
# state.getScore()
|
||||
# used by multiAgents.scoreEvaluationFunction, which is the default
|
||||
#
|
||||
import testClasses
|
||||
import json
|
||||
|
||||
from collections import defaultdict
|
||||
from pprint import PrettyPrinter
|
||||
pp = PrettyPrinter()
|
||||
|
||||
from game import Agent
|
||||
from pacman import GameState
|
||||
from ghostAgents import RandomGhost, DirectionalGhost
|
||||
import random
|
||||
import math
|
||||
import traceback
|
||||
import sys
|
||||
import os
|
||||
import layout
|
||||
import pacman
|
||||
import autograder
|
||||
# import grading
|
||||
|
||||
VERBOSE = False
|
||||
|
||||
|
||||
class MultiagentTreeState(object):
|
||||
def __init__(self, problem, state):
|
||||
self.problem = problem
|
||||
self.state = state
|
||||
|
||||
def generateSuccessor(self, agentIndex, action):
|
||||
if VERBOSE:
|
||||
print("generateSuccessor(%s, %s, %s) -> %s" % (self.state, agentIndex,
|
||||
action, self.problem.stateToSuccessorMap[self.state][action]))
|
||||
successor = self.problem.stateToSuccessorMap[self.state][action]
|
||||
self.problem.generatedStates.add(successor)
|
||||
return MultiagentTreeState(self.problem, successor)
|
||||
|
||||
def getScore(self):
|
||||
if VERBOSE:
|
||||
print("getScore(%s) -> %s" %
|
||||
(self.state, self.problem.evaluation[self.state]))
|
||||
if self.state not in self.problem.evaluation:
|
||||
raise Exception(
|
||||
'getScore() called on non-terminal state or before maximum depth achieved.')
|
||||
return float(self.problem.evaluation[self.state])
|
||||
|
||||
def getLegalActions(self, agentIndex=0):
|
||||
if VERBOSE:
|
||||
print("getLegalActions(%s) -> %s" %
|
||||
(self.state, self.problem.stateToActions[self.state]))
|
||||
# if len(self.problem.stateToActions[self.state]) == 0:
|
||||
# print "WARNING: getLegalActions called on leaf state %s" % (self.state,)
|
||||
return list(self.problem.stateToActions[self.state])
|
||||
|
||||
def isWin(self):
|
||||
if VERBOSE:
|
||||
print("isWin(%s) -> %s" %
|
||||
(self.state, self.state in self.problem.winStates))
|
||||
return self.state in self.problem.winStates
|
||||
|
||||
def isLose(self):
|
||||
if VERBOSE:
|
||||
print("isLose(%s) -> %s" %
|
||||
(self.state, self.state in self.problem.loseStates))
|
||||
return self.state in self.problem.loseStates
|
||||
|
||||
def getNumAgents(self):
|
||||
if VERBOSE:
|
||||
print("getNumAgents(%s) -> %s" %
|
||||
(self.state, self.problem.numAgents))
|
||||
return self.problem.numAgents
|
||||
|
||||
|
||||
class MultiagentTreeProblem(object):
|
||||
def __init__(self, numAgents, startState, winStates, loseStates, successors, evaluation):
|
||||
self.startState = MultiagentTreeState(self, startState)
|
||||
|
||||
self.numAgents = numAgents
|
||||
self.winStates = winStates
|
||||
self.loseStates = loseStates
|
||||
self.evaluation = evaluation
|
||||
self.successors = successors
|
||||
|
||||
self.reset()
|
||||
|
||||
self.stateToSuccessorMap = defaultdict(dict)
|
||||
self.stateToActions = defaultdict(list)
|
||||
for state, action, nextState in successors:
|
||||
self.stateToActions[state].append(action)
|
||||
self.stateToSuccessorMap[state][action] = nextState
|
||||
|
||||
def reset(self):
|
||||
self.generatedStates = set([self.startState.state])
|
||||
|
||||
|
||||
def parseTreeProblem(testDict):
|
||||
numAgents = int(testDict["num_agents"])
|
||||
startState = testDict["start_state"]
|
||||
winStates = set(testDict["win_states"].split(" "))
|
||||
loseStates = set(testDict["lose_states"].split(" "))
|
||||
successors = []
|
||||
|
||||
evaluation = {}
|
||||
for line in testDict["evaluation"].split('\n'):
|
||||
tokens = line.split()
|
||||
if len(tokens) == 2:
|
||||
state, value = tokens
|
||||
evaluation[state] = float(value)
|
||||
else:
|
||||
raise Exception("[parseTree] Bad evaluation line: |%s|" % (line,))
|
||||
|
||||
for line in testDict["successors"].split('\n'):
|
||||
tokens = line.split()
|
||||
if len(tokens) == 3:
|
||||
state, action, nextState = tokens
|
||||
successors.append((state, action, nextState))
|
||||
else:
|
||||
raise Exception("[parseTree] Bad successor line: |%s|" % (line,))
|
||||
|
||||
return MultiagentTreeProblem(numAgents, startState, winStates, loseStates, successors, evaluation)
|
||||
|
||||
|
||||
def run(lay, layName, pac, ghosts, disp, nGames=1, name='games'):
|
||||
"""
|
||||
Runs a few games and outputs their statistics.
|
||||
"""
|
||||
starttime = time.time()
|
||||
print('*** Running %s on' % name, layName, '%d time(s).' % nGames)
|
||||
games = pacman.runGames(lay, pac, ghosts, disp,
|
||||
nGames, False, catchExceptions=True, timeout=120)
|
||||
print('*** Finished running %s on' % name, layName,
|
||||
'after %d seconds.' % (time.time() - starttime))
|
||||
stats = {'time': time.time() - starttime, 'wins': [g.state.isWin() for g in games].count(True), 'games': games, 'scores': [g.state.getScore() for g in games],
|
||||
'timeouts': [g.agentTimeout for g in games].count(True), 'crashes': [g.agentCrashed for g in games].count(True)}
|
||||
print('*** Won %d out of %d games. Average score: %f ***' %
|
||||
(stats['wins'], len(games), sum(stats['scores']) * 1.0 / len(games)))
|
||||
return stats
|
||||
|
||||
|
||||
class GradingAgent(Agent):
|
||||
def __init__(self, seed, studentAgent, optimalActions, altDepthActions, partialPlyBugActions):
|
||||
# save student agent and actions of refernce agents
|
||||
self.studentAgent = studentAgent
|
||||
self.optimalActions = optimalActions
|
||||
self.altDepthActions = altDepthActions
|
||||
self.partialPlyBugActions = partialPlyBugActions
|
||||
# create fields for storing specific wrong actions
|
||||
self.suboptimalMoves = []
|
||||
self.wrongStatesExplored = -1
|
||||
# boolean vectors represent types of implementation the student could have
|
||||
self.actionsConsistentWithOptimal = [
|
||||
True for i in range(len(optimalActions[0]))]
|
||||
self.actionsConsistentWithAlternativeDepth = [
|
||||
True for i in range(len(altDepthActions[0]))]
|
||||
self.actionsConsistentWithPartialPlyBug = [
|
||||
True for i in range(len(partialPlyBugActions[0]))]
|
||||
# keep track of elapsed moves
|
||||
self.stepCount = 0
|
||||
self.seed = seed
|
||||
|
||||
def registerInitialState(self, state):
|
||||
if 'registerInitialState' in dir(self.studentAgent):
|
||||
self.studentAgent.registerInitialState(state)
|
||||
random.seed(self.seed)
|
||||
|
||||
def getAction(self, state):
|
||||
GameState.getAndResetExplored()
|
||||
studentAction = (self.studentAgent.getAction(state),
|
||||
len(GameState.getAndResetExplored()))
|
||||
optimalActions = self.optimalActions[self.stepCount]
|
||||
altDepthActions = self.altDepthActions[self.stepCount]
|
||||
partialPlyBugActions = self.partialPlyBugActions[self.stepCount]
|
||||
studentOptimalAction = False
|
||||
curRightStatesExplored = False
|
||||
for i in range(len(optimalActions)):
|
||||
if studentAction[0] in optimalActions[i][0]:
|
||||
studentOptimalAction = True
|
||||
else:
|
||||
self.actionsConsistentWithOptimal[i] = False
|
||||
if studentAction[1] == int(optimalActions[i][1]):
|
||||
curRightStatesExplored = True
|
||||
if not curRightStatesExplored and self.wrongStatesExplored < 0:
|
||||
self.wrongStatesExplored = 1
|
||||
for i in range(len(altDepthActions)):
|
||||
if studentAction[0] not in altDepthActions[i]:
|
||||
self.actionsConsistentWithAlternativeDepth[i] = False
|
||||
for i in range(len(partialPlyBugActions)):
|
||||
if studentAction[0] not in partialPlyBugActions[i]:
|
||||
self.actionsConsistentWithPartialPlyBug[i] = False
|
||||
if not studentOptimalAction:
|
||||
self.suboptimalMoves.append(
|
||||
(state, studentAction[0], optimalActions[0][0][0]))
|
||||
self.stepCount += 1
|
||||
random.seed(self.seed + self.stepCount)
|
||||
return optimalActions[0][0][0]
|
||||
|
||||
def getSuboptimalMoves(self):
|
||||
return self.suboptimalMoves
|
||||
|
||||
def getWrongStatesExplored(self):
|
||||
return self.wrongStatesExplored
|
||||
|
||||
def checkFailure(self):
|
||||
"""
|
||||
Return +n if have n suboptimal moves.
|
||||
Return -1 if have only off by one depth moves.
|
||||
Return 0 otherwise.
|
||||
"""
|
||||
if self.wrongStatesExplored > 0:
|
||||
return -3
|
||||
if self.actionsConsistentWithOptimal.count(True) > 0:
|
||||
return 0
|
||||
elif self.actionsConsistentWithPartialPlyBug.count(True) > 0:
|
||||
return -2
|
||||
elif self.actionsConsistentWithAlternativeDepth.count(True) > 0:
|
||||
return -1
|
||||
else:
|
||||
return len(self.suboptimalMoves)
|
||||
|
||||
|
||||
class PolyAgent(Agent):
|
||||
def __init__(self, seed, multiAgents, ourPacOptions, depth):
|
||||
# prepare our pacman agents
|
||||
solutionAgents, alternativeDepthAgents, partialPlyBugAgents = self.construct_our_pacs(
|
||||
multiAgents, ourPacOptions)
|
||||
for p in solutionAgents:
|
||||
p.depth = depth
|
||||
for p in partialPlyBugAgents:
|
||||
p.depth = depth
|
||||
for p in alternativeDepthAgents[:2]:
|
||||
p.depth = max(1, depth - 1)
|
||||
for p in alternativeDepthAgents[2:]:
|
||||
p.depth = depth + 1
|
||||
self.solutionAgents = solutionAgents
|
||||
self.alternativeDepthAgents = alternativeDepthAgents
|
||||
self.partialPlyBugAgents = partialPlyBugAgents
|
||||
# prepare fields for storing the results
|
||||
self.optimalActionLists = []
|
||||
self.alternativeDepthLists = []
|
||||
self.partialPlyBugLists = []
|
||||
self.seed = seed
|
||||
self.stepCount = 0
|
||||
|
||||
def select(self, list, indices):
|
||||
"""
|
||||
Return a sublist of elements given by indices in list.
|
||||
"""
|
||||
return [list[i] for i in indices]
|
||||
|
||||
def construct_our_pacs(self, multiAgents, keyword_dict):
|
||||
pacs_without_stop = [multiAgents.StaffMultiAgentSearchAgent(
|
||||
**keyword_dict) for i in range(3)]
|
||||
keyword_dict['keepStop'] = 'True'
|
||||
pacs_with_stop = [multiAgents.StaffMultiAgentSearchAgent(
|
||||
**keyword_dict) for i in range(3)]
|
||||
keyword_dict['usePartialPlyBug'] = 'True'
|
||||
partial_ply_bug_pacs = [
|
||||
multiAgents.StaffMultiAgentSearchAgent(**keyword_dict)]
|
||||
keyword_dict['keepStop'] = 'False'
|
||||
partial_ply_bug_pacs = partial_ply_bug_pacs + \
|
||||
[multiAgents.StaffMultiAgentSearchAgent(**keyword_dict)]
|
||||
for pac in pacs_with_stop + pacs_without_stop + partial_ply_bug_pacs:
|
||||
pac.verbose = False
|
||||
ourpac = [pacs_with_stop[0], pacs_without_stop[0]]
|
||||
alternative_depth_pacs = self.select(
|
||||
pacs_with_stop + pacs_without_stop, [1, 4, 2, 5])
|
||||
return (ourpac, alternative_depth_pacs, partial_ply_bug_pacs)
|
||||
|
||||
def registerInitialState(self, state):
|
||||
for agent in self.solutionAgents + self.alternativeDepthAgents:
|
||||
if 'registerInitialState' in dir(agent):
|
||||
agent.registerInitialState(state)
|
||||
random.seed(self.seed)
|
||||
|
||||
def getAction(self, state):
|
||||
# survey agents
|
||||
GameState.getAndResetExplored()
|
||||
optimalActionLists = []
|
||||
for agent in self.solutionAgents:
|
||||
optimalActionLists.append((agent.getBestPacmanActions(
|
||||
state)[0], len(GameState.getAndResetExplored())))
|
||||
alternativeDepthLists = [agent.getBestPacmanActions(
|
||||
state)[0] for agent in self.alternativeDepthAgents]
|
||||
partialPlyBugLists = [agent.getBestPacmanActions(
|
||||
state)[0] for agent in self.partialPlyBugAgents]
|
||||
# record responses
|
||||
self.optimalActionLists.append(optimalActionLists)
|
||||
self.alternativeDepthLists.append(alternativeDepthLists)
|
||||
self.partialPlyBugLists.append(partialPlyBugLists)
|
||||
self.stepCount += 1
|
||||
random.seed(self.seed + self.stepCount)
|
||||
return optimalActionLists[0][0][0]
|
||||
|
||||
def getTraces(self):
|
||||
# return traces from individual agents
|
||||
return (self.optimalActionLists, self.alternativeDepthLists, self.partialPlyBugLists)
|
||||
|
||||
|
||||
class PacmanGameTreeTest(testClasses.TestCase):
|
||||
|
||||
def __init__(self, question, testDict):
|
||||
super(PacmanGameTreeTest, self).__init__(question, testDict)
|
||||
self.seed = int(self.testDict['seed'])
|
||||
self.alg = self.testDict['alg']
|
||||
self.layout_text = self.testDict['layout']
|
||||
self.layout_name = self.testDict['layoutName']
|
||||
self.depth = int(self.testDict['depth'])
|
||||
self.max_points = int(self.testDict['max_points'])
|
||||
|
||||
def execute(self, grades, moduleDict, solutionDict):
|
||||
# load student code and staff code solutions
|
||||
multiAgents = moduleDict['multiAgents']
|
||||
studentAgent = getattr(multiAgents, self.alg)(depth=self.depth)
|
||||
allActions = [json.loads(x)
|
||||
for x in solutionDict['optimalActions'].split('\n')]
|
||||
altDepthActions = [json.loads(
|
||||
x) for x in solutionDict['altDepthActions'].split('\n')]
|
||||
partialPlyBugActions = [json.loads(
|
||||
x) for x in solutionDict['partialPlyBugActions'].split('\n')]
|
||||
# set up game state and play a game
|
||||
random.seed(self.seed)
|
||||
lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')])
|
||||
pac = GradingAgent(self.seed, studentAgent, allActions,
|
||||
altDepthActions, partialPlyBugActions)
|
||||
# check return codes and assign grades
|
||||
disp = self.question.getDisplay()
|
||||
stats = run(lay, self.layout_name, pac, [DirectionalGhost(
|
||||
i + 1) for i in range(2)], disp, name=self.alg)
|
||||
if stats['timeouts'] > 0:
|
||||
self.addMessage('Agent timed out on smallClassic. No credit')
|
||||
return self.testFail(grades)
|
||||
if stats['crashes'] > 0:
|
||||
self.addMessage('Agent crashed on smallClassic. No credit')
|
||||
return self.testFail(grades)
|
||||
code = pac.checkFailure()
|
||||
if code == 0:
|
||||
return self.testPass(grades)
|
||||
elif code == -3:
|
||||
if pac.getWrongStatesExplored() >= 0:
|
||||
self.addMessage('Bug: Wrong number of states expanded.')
|
||||
return self.testFail(grades)
|
||||
else:
|
||||
return self.testPass(grades)
|
||||
elif code == -2:
|
||||
self.addMessage('Bug: Partial Ply Bug')
|
||||
return self.testFail(grades)
|
||||
elif code == -1:
|
||||
self.addMessage('Bug: Search depth off by 1')
|
||||
return self.testFail(grades)
|
||||
elif code > 0:
|
||||
moves = pac.getSuboptimalMoves()
|
||||
state, studentMove, optMove = random.choice(moves)
|
||||
self.addMessage('Bug: Suboptimal moves')
|
||||
self.addMessage('State:%s\nStudent Move:%s\nOptimal Move:%s' % (
|
||||
state, studentMove, optMove))
|
||||
return self.testFail(grades)
|
||||
|
||||
def writeList(self, handle, name, list):
|
||||
handle.write('%s: """\n' % name)
|
||||
for l in list:
|
||||
handle.write('%s\n' % json.dumps(l))
|
||||
handle.write('"""\n')
|
||||
|
||||
def writeSolution(self, moduleDict, filePath):
|
||||
# load module, set seed, create ghosts and macman, run game
|
||||
multiAgents = moduleDict['multiAgents']
|
||||
random.seed(self.seed)
|
||||
lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')])
|
||||
if self.alg == 'ExpectimaxAgent':
|
||||
ourPacOptions = {'expectimax': 'True'}
|
||||
elif self.alg == 'AlphaBetaAgent':
|
||||
ourPacOptions = {'alphabeta': 'True'}
|
||||
else:
|
||||
ourPacOptions = {}
|
||||
pac = PolyAgent(self.seed, multiAgents, ourPacOptions, self.depth)
|
||||
disp = self.question.getDisplay()
|
||||
run(lay, self.layout_name, pac, [DirectionalGhost(
|
||||
i + 1) for i in range(2)], disp, name=self.alg)
|
||||
(optimalActions, altDepthActions, partialPlyBugActions) = pac.getTraces()
|
||||
# recover traces and record to file
|
||||
handle = open(filePath, 'w')
|
||||
self.writeList(handle, 'optimalActions', optimalActions)
|
||||
self.writeList(handle, 'altDepthActions', altDepthActions)
|
||||
self.writeList(handle, 'partialPlyBugActions', partialPlyBugActions)
|
||||
handle.close()
|
||||
|
||||
|
||||
class GraphGameTreeTest(testClasses.TestCase):
|
||||
|
||||
def __init__(self, question, testDict):
|
||||
super(GraphGameTreeTest, self).__init__(question, testDict)
|
||||
self.problem = parseTreeProblem(testDict)
|
||||
self.alg = self.testDict['alg']
|
||||
self.diagram = self.testDict['diagram'].split('\n')
|
||||
self.depth = int(self.testDict['depth'])
|
||||
|
||||
def solveProblem(self, multiAgents):
|
||||
self.problem.reset()
|
||||
studentAgent = getattr(multiAgents, self.alg)(depth=self.depth)
|
||||
action = studentAgent.getAction(self.problem.startState)
|
||||
generated = self.problem.generatedStates
|
||||
return action, " ".join([str(s) for s in sorted(generated)])
|
||||
|
||||
def addDiagram(self):
|
||||
self.addMessage('Tree:')
|
||||
for line in self.diagram:
|
||||
self.addMessage(line)
|
||||
|
||||
def execute(self, grades, moduleDict, solutionDict):
|
||||
multiAgents = moduleDict['multiAgents']
|
||||
goldAction = solutionDict['action']
|
||||
goldGenerated = solutionDict['generated']
|
||||
action, generated = self.solveProblem(multiAgents)
|
||||
|
||||
fail = False
|
||||
if action != goldAction:
|
||||
self.addMessage('Incorrect move for depth=%s' % (self.depth,))
|
||||
self.addMessage(
|
||||
' Student move: %s\n Optimal move: %s' % (action, goldAction))
|
||||
fail = True
|
||||
|
||||
if generated != goldGenerated:
|
||||
self.addMessage(
|
||||
'Incorrect generated nodes for depth=%s' % (self.depth,))
|
||||
self.addMessage(' Student generated nodes: %s\n Correct generated nodes: %s' % (
|
||||
generated, goldGenerated))
|
||||
fail = True
|
||||
|
||||
if fail:
|
||||
self.addDiagram()
|
||||
return self.testFail(grades)
|
||||
else:
|
||||
return self.testPass(grades)
|
||||
|
||||
def writeSolution(self, moduleDict, filePath):
|
||||
multiAgents = moduleDict['multiAgents']
|
||||
action, generated = self.solveProblem(multiAgents)
|
||||
with open(filePath, 'w') as handle:
|
||||
handle.write('# This is the solution file for %s.\n' % self.path)
|
||||
handle.write('action: "%s"\n' % (action,))
|
||||
handle.write('generated: "%s"\n' % (generated,))
|
||||
return True
|
||||
|
||||
|
||||
import time
|
||||
from util import TimeoutFunction
|
||||
|
||||
|
||||
class EvalAgentTest(testClasses.TestCase):
|
||||
|
||||
def __init__(self, question, testDict):
|
||||
super(EvalAgentTest, self).__init__(question, testDict)
|
||||
self.layoutName = testDict['layoutName']
|
||||
self.agentName = testDict['agentName']
|
||||
self.ghosts = eval(testDict['ghosts'])
|
||||
self.maxTime = int(testDict['maxTime'])
|
||||
self.seed = int(testDict['randomSeed'])
|
||||
self.numGames = int(testDict['numGames'])
|
||||
|
||||
self.scoreMinimum = int(
|
||||
testDict['scoreMinimum']) if 'scoreMinimum' in testDict else None
|
||||
self.nonTimeoutMinimum = int(
|
||||
testDict['nonTimeoutMinimum']) if 'nonTimeoutMinimum' in testDict else None
|
||||
self.winsMinimum = int(
|
||||
testDict['winsMinimum']) if 'winsMinimum' in testDict else None
|
||||
|
||||
self.scoreThresholds = [int(s) for s in testDict.get(
|
||||
'scoreThresholds', '').split()]
|
||||
self.nonTimeoutThresholds = [int(s) for s in testDict.get(
|
||||
'nonTimeoutThresholds', '').split()]
|
||||
self.winsThresholds = [int(s) for s in testDict.get(
|
||||
'winsThresholds', '').split()]
|
||||
|
||||
self.maxPoints = sum([len(t) for t in [
|
||||
self.scoreThresholds, self.nonTimeoutThresholds, self.winsThresholds]])
|
||||
self.agentArgs = testDict.get('agentArgs', '')
|
||||
|
||||
def execute(self, grades, moduleDict, solutionDict):
|
||||
startTime = time.time()
|
||||
|
||||
agentType = getattr(moduleDict['multiAgents'], self.agentName)
|
||||
agentOpts = pacman.parseAgentArgs(
|
||||
self.agentArgs) if self.agentArgs != '' else {}
|
||||
agent = agentType(**agentOpts)
|
||||
|
||||
lay = layout.getLayout(self.layoutName, 3)
|
||||
|
||||
disp = self.question.getDisplay()
|
||||
|
||||
random.seed(self.seed)
|
||||
games = pacman.runGames(lay, agent, self.ghosts, disp, self.numGames,
|
||||
False, catchExceptions=True, timeout=self.maxTime)
|
||||
totalTime = time.time() - startTime
|
||||
|
||||
stats = {'time': totalTime, 'wins': [g.state.isWin() for g in games].count(True),
|
||||
'games': games, 'scores': [g.state.getScore() for g in games],
|
||||
'timeouts': [g.agentTimeout for g in games].count(True), 'crashes': [g.agentCrashed for g in games].count(True)}
|
||||
|
||||
averageScore = sum(stats['scores']) / float(len(stats['scores']))
|
||||
nonTimeouts = self.numGames - stats['timeouts']
|
||||
wins = stats['wins']
|
||||
|
||||
def gradeThreshold(value, minimum, thresholds, name):
|
||||
points = 0
|
||||
passed = (minimum == None) or (value >= minimum)
|
||||
if passed:
|
||||
for t in thresholds:
|
||||
if value >= t:
|
||||
points += 1
|
||||
return (passed, points, value, minimum, thresholds, name)
|
||||
|
||||
results = [gradeThreshold(averageScore, self.scoreMinimum, self.scoreThresholds, "average score"),
|
||||
gradeThreshold(nonTimeouts, self.nonTimeoutMinimum,
|
||||
self.nonTimeoutThresholds, "games not timed out"),
|
||||
gradeThreshold(wins, self.winsMinimum, self.winsThresholds, "wins")]
|
||||
|
||||
totalPoints = 0
|
||||
for passed, points, value, minimum, thresholds, name in results:
|
||||
if minimum == None and len(thresholds) == 0:
|
||||
continue
|
||||
|
||||
# print passed, points, value, minimum, thresholds, name
|
||||
totalPoints += points
|
||||
if not passed:
|
||||
assert points == 0
|
||||
self.addMessage(
|
||||
"%s %s (fail: below minimum value %s)" % (value, name, minimum))
|
||||
else:
|
||||
self.addMessage("%s %s (%s of %s points)" %
|
||||
(value, name, points, len(thresholds)))
|
||||
|
||||
if minimum != None:
|
||||
self.addMessage(" Grading scheme:")
|
||||
self.addMessage(" < %s: fail" % (minimum,))
|
||||
if len(thresholds) == 0 or minimum != thresholds[0]:
|
||||
self.addMessage(" >= %s: 0 points" % (minimum,))
|
||||
for idx, threshold in enumerate(thresholds):
|
||||
self.addMessage(" >= %s: %s points" %
|
||||
(threshold, idx+1))
|
||||
elif len(thresholds) > 0:
|
||||
self.addMessage(" Grading scheme:")
|
||||
self.addMessage(" < %s: 0 points" % (thresholds[0],))
|
||||
for idx, threshold in enumerate(thresholds):
|
||||
self.addMessage(" >= %s: %s points" %
|
||||
(threshold, idx+1))
|
||||
|
||||
if any([not passed for passed, _, _, _, _, _ in results]):
|
||||
totalPoints = 0
|
||||
|
||||
return self.testPartial(grades, totalPoints, self.maxPoints)
|
||||
|
||||
def writeSolution(self, moduleDict, filePath):
|
||||
handle = open(filePath, 'w')
|
||||
handle.write('# This is the solution file for %s.\n' % self.path)
|
||||
handle.write('# File intentionally blank.\n')
|
||||
handle.close()
|
||||
return True
|
||||
738
proj2/pacman.py
Normal file
738
proj2/pacman.py
Normal file
@ -0,0 +1,738 @@
|
||||
# pacman.py
|
||||
# ---------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
"""
|
||||
Pacman.py holds the logic for the classic pacman game along with the main
|
||||
code to run a game. This file is divided into three sections:
|
||||
|
||||
(i) Your interface to the pacman world:
|
||||
Pacman is a complex environment. You probably don't want to
|
||||
read through all of the code we wrote to make the game runs
|
||||
correctly. This section contains the parts of the code
|
||||
that you will need to understand in order to complete the
|
||||
project. There is also some code in game.py that you should
|
||||
understand.
|
||||
|
||||
(ii) The hidden secrets of pacman:
|
||||
This section contains all of the logic code that the pacman
|
||||
environment uses to decide who can move where, who dies when
|
||||
things collide, etc. You shouldn't need to read this section
|
||||
of code, but you can if you want.
|
||||
|
||||
(iii) Framework to start a game:
|
||||
The final section contains the code for reading the command
|
||||
you use to set up the game, then starting up a new game, along with
|
||||
linking in all the external parts (agent functions, graphics).
|
||||
Check this section out to see all the options available to you.
|
||||
|
||||
To play your first game, type 'python pacman.py' from the command line.
|
||||
The keys are 'a', 's', 'd', and 'w' to move (or arrow keys). Have fun!
|
||||
"""
|
||||
from game import GameStateData
|
||||
from game import Game
|
||||
from game import Directions
|
||||
from game import Actions
|
||||
from util import nearestPoint
|
||||
from util import manhattanDistance
|
||||
import util
|
||||
import layout
|
||||
import sys
|
||||
import types
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
|
||||
###################################################
|
||||
# YOUR INTERFACE TO THE PACMAN WORLD: A GameState #
|
||||
###################################################
|
||||
|
||||
|
||||
class GameState:
|
||||
"""
|
||||
A GameState specifies the full game state, including the food, capsules,
|
||||
agent configurations and score changes.
|
||||
|
||||
GameStates are used by the Game object to capture the actual state of the game and
|
||||
can be used by agents to reason about the game.
|
||||
|
||||
Much of the information in a GameState is stored in a GameStateData object. We
|
||||
strongly suggest that you access that data via the accessor methods below rather
|
||||
than referring to the GameStateData object directly.
|
||||
|
||||
Note that in classic Pacman, Pacman is always agent 0.
|
||||
"""
|
||||
|
||||
####################################################
|
||||
# Accessor methods: use these to access state data #
|
||||
####################################################
|
||||
|
||||
# static variable keeps track of which states have had getLegalActions called
|
||||
explored = set()
|
||||
|
||||
def getAndResetExplored():
|
||||
tmp = GameState.explored.copy()
|
||||
GameState.explored = set()
|
||||
return tmp
|
||||
getAndResetExplored = staticmethod(getAndResetExplored)
|
||||
|
||||
def getLegalActions(self, agentIndex=0):
|
||||
"""
|
||||
Returns the legal actions for the agent specified.
|
||||
"""
|
||||
# GameState.explored.add(self)
|
||||
if self.isWin() or self.isLose():
|
||||
return []
|
||||
|
||||
if agentIndex == 0: # Pacman is moving
|
||||
return PacmanRules.getLegalActions(self)
|
||||
else:
|
||||
return GhostRules.getLegalActions(self, agentIndex)
|
||||
|
||||
def generateSuccessor(self, agentIndex, action):
|
||||
"""
|
||||
Returns the successor state after the specified agent takes the action.
|
||||
"""
|
||||
# Check that successors exist
|
||||
if self.isWin() or self.isLose():
|
||||
raise Exception('Can\'t generate a successor of a terminal state.')
|
||||
|
||||
# Copy current state
|
||||
state = GameState(self)
|
||||
|
||||
# Let agent's logic deal with its action's effects on the board
|
||||
if agentIndex == 0: # Pacman is moving
|
||||
state.data._eaten = [False for i in range(state.getNumAgents())]
|
||||
PacmanRules.applyAction(state, action)
|
||||
else: # A ghost is moving
|
||||
GhostRules.applyAction(state, action, agentIndex)
|
||||
|
||||
# Time passes
|
||||
if agentIndex == 0:
|
||||
state.data.scoreChange += -TIME_PENALTY # Penalty for waiting around
|
||||
else:
|
||||
GhostRules.decrementTimer(state.data.agentStates[agentIndex])
|
||||
|
||||
# Resolve multi-agent effects
|
||||
GhostRules.checkDeath(state, agentIndex)
|
||||
|
||||
# Book keeping
|
||||
state.data._agentMoved = agentIndex
|
||||
state.data.score += state.data.scoreChange
|
||||
GameState.explored.add(self)
|
||||
GameState.explored.add(state)
|
||||
return state
|
||||
|
||||
def getLegalPacmanActions(self):
|
||||
return self.getLegalActions(0)
|
||||
|
||||
def generatePacmanSuccessor(self, action):
|
||||
"""
|
||||
Generates the successor state after the specified pacman move
|
||||
"""
|
||||
return self.generateSuccessor(0, action)
|
||||
|
||||
def getPacmanState(self):
|
||||
"""
|
||||
Returns an AgentState object for pacman (in game.py)
|
||||
|
||||
state.pos gives the current position
|
||||
state.direction gives the travel vector
|
||||
"""
|
||||
return self.data.agentStates[0].copy()
|
||||
|
||||
def getPacmanPosition(self):
|
||||
return self.data.agentStates[0].getPosition()
|
||||
|
||||
def getGhostStates(self):
|
||||
return self.data.agentStates[1:]
|
||||
|
||||
def getGhostState(self, agentIndex):
|
||||
if agentIndex == 0 or agentIndex >= self.getNumAgents():
|
||||
raise Exception("Invalid index passed to getGhostState")
|
||||
return self.data.agentStates[agentIndex]
|
||||
|
||||
def getGhostPosition(self, agentIndex):
|
||||
if agentIndex == 0:
|
||||
raise Exception("Pacman's index passed to getGhostPosition")
|
||||
return self.data.agentStates[agentIndex].getPosition()
|
||||
|
||||
def getGhostPositions(self):
|
||||
return [s.getPosition() for s in self.getGhostStates()]
|
||||
|
||||
def getNumAgents(self):
|
||||
return len(self.data.agentStates)
|
||||
|
||||
def getScore(self):
|
||||
return float(self.data.score)
|
||||
|
||||
def getCapsules(self):
|
||||
"""
|
||||
Returns a list of positions (x,y) of the remaining capsules.
|
||||
"""
|
||||
return self.data.capsules
|
||||
|
||||
def getNumFood(self):
|
||||
return self.data.food.count()
|
||||
|
||||
def getFood(self):
|
||||
"""
|
||||
Returns a Grid of boolean food indicator variables.
|
||||
|
||||
Grids can be accessed via list notation, so to check
|
||||
if there is food at (x,y), just call
|
||||
|
||||
currentFood = state.getFood()
|
||||
if currentFood[x][y] == True: ...
|
||||
"""
|
||||
return self.data.food
|
||||
|
||||
def getWalls(self):
|
||||
"""
|
||||
Returns a Grid of boolean wall indicator variables.
|
||||
|
||||
Grids can be accessed via list notation, so to check
|
||||
if there is a wall at (x,y), just call
|
||||
|
||||
walls = state.getWalls()
|
||||
if walls[x][y] == True: ...
|
||||
"""
|
||||
return self.data.layout.walls
|
||||
|
||||
def hasFood(self, x, y):
|
||||
return self.data.food[x][y]
|
||||
|
||||
def hasWall(self, x, y):
|
||||
return self.data.layout.walls[x][y]
|
||||
|
||||
def isLose(self):
|
||||
return self.data._lose
|
||||
|
||||
def isWin(self):
|
||||
return self.data._win
|
||||
|
||||
#############################################
|
||||
# Helper methods: #
|
||||
# You shouldn't need to call these directly #
|
||||
#############################################
|
||||
|
||||
def __init__(self, prevState=None):
|
||||
"""
|
||||
Generates a new state by copying information from its predecessor.
|
||||
"""
|
||||
if prevState != None: # Initial state
|
||||
self.data = GameStateData(prevState.data)
|
||||
else:
|
||||
self.data = GameStateData()
|
||||
|
||||
def deepCopy(self):
|
||||
state = GameState(self)
|
||||
state.data = self.data.deepCopy()
|
||||
return state
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Allows two states to be compared.
|
||||
"""
|
||||
return hasattr(other, 'data') and self.data == other.data
|
||||
|
||||
def __hash__(self):
|
||||
"""
|
||||
Allows states to be keys of dictionaries.
|
||||
"""
|
||||
return hash(self.data)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return str(self.data)
|
||||
|
||||
def initialize(self, layout, numGhostAgents=1000):
|
||||
"""
|
||||
Creates an initial game state from a layout array (see layout.py).
|
||||
"""
|
||||
self.data.initialize(layout, numGhostAgents)
|
||||
|
||||
############################################################################
|
||||
# THE HIDDEN SECRETS OF PACMAN #
|
||||
# #
|
||||
# You shouldn't need to look through the code in this section of the file. #
|
||||
############################################################################
|
||||
|
||||
|
||||
SCARED_TIME = 40 # Moves ghosts are scared
|
||||
COLLISION_TOLERANCE = 0.7 # How close ghosts must be to Pacman to kill
|
||||
TIME_PENALTY = 1 # Number of points lost each round
|
||||
|
||||
|
||||
class ClassicGameRules:
|
||||
"""
|
||||
These game rules manage the control flow of a game, deciding when
|
||||
and how the game starts and ends.
|
||||
"""
|
||||
|
||||
def __init__(self, timeout=30):
|
||||
self.timeout = timeout
|
||||
|
||||
def newGame(self, layout, pacmanAgent, ghostAgents, display, quiet=False, catchExceptions=False):
|
||||
agents = [pacmanAgent] + ghostAgents[:layout.getNumGhosts()]
|
||||
initState = GameState()
|
||||
initState.initialize(layout, len(ghostAgents))
|
||||
game = Game(agents, display, self, catchExceptions=catchExceptions)
|
||||
game.state = initState
|
||||
self.initialState = initState.deepCopy()
|
||||
self.quiet = quiet
|
||||
return game
|
||||
|
||||
def process(self, state, game):
|
||||
"""
|
||||
Checks to see whether it is time to end the game.
|
||||
"""
|
||||
if state.isWin():
|
||||
self.win(state, game)
|
||||
if state.isLose():
|
||||
self.lose(state, game)
|
||||
|
||||
def win(self, state, game):
|
||||
if not self.quiet:
|
||||
print("Pacman emerges victorious! Score: %d" % state.data.score)
|
||||
game.gameOver = True
|
||||
|
||||
def lose(self, state, game):
|
||||
if not self.quiet:
|
||||
print("Pacman died! Score: %d" % state.data.score)
|
||||
game.gameOver = True
|
||||
|
||||
def getProgress(self, game):
|
||||
return float(game.state.getNumFood()) / self.initialState.getNumFood()
|
||||
|
||||
def agentCrash(self, game, agentIndex):
|
||||
if agentIndex == 0:
|
||||
print("Pacman crashed")
|
||||
else:
|
||||
print("A ghost crashed")
|
||||
|
||||
def getMaxTotalTime(self, agentIndex):
|
||||
return self.timeout
|
||||
|
||||
def getMaxStartupTime(self, agentIndex):
|
||||
return self.timeout
|
||||
|
||||
def getMoveWarningTime(self, agentIndex):
|
||||
return self.timeout
|
||||
|
||||
def getMoveTimeout(self, agentIndex):
|
||||
return self.timeout
|
||||
|
||||
def getMaxTimeWarnings(self, agentIndex):
|
||||
return 0
|
||||
|
||||
|
||||
class PacmanRules:
|
||||
"""
|
||||
These functions govern how pacman interacts with his environment under
|
||||
the classic game rules.
|
||||
"""
|
||||
PACMAN_SPEED = 1
|
||||
|
||||
def getLegalActions(state):
|
||||
"""
|
||||
Returns a list of possible actions.
|
||||
"""
|
||||
return Actions.getPossibleActions(state.getPacmanState().configuration, state.data.layout.walls)
|
||||
getLegalActions = staticmethod(getLegalActions)
|
||||
|
||||
def applyAction(state, action):
|
||||
"""
|
||||
Edits the state to reflect the results of the action.
|
||||
"""
|
||||
legal = PacmanRules.getLegalActions(state)
|
||||
if action not in legal:
|
||||
raise Exception("Illegal action " + str(action))
|
||||
|
||||
pacmanState = state.data.agentStates[0]
|
||||
|
||||
# Update Configuration
|
||||
vector = Actions.directionToVector(action, PacmanRules.PACMAN_SPEED)
|
||||
pacmanState.configuration = pacmanState.configuration.generateSuccessor(
|
||||
vector)
|
||||
|
||||
# Eat
|
||||
next = pacmanState.configuration.getPosition()
|
||||
nearest = nearestPoint(next)
|
||||
if manhattanDistance(nearest, next) <= 0.5:
|
||||
# Remove food
|
||||
PacmanRules.consume(nearest, state)
|
||||
applyAction = staticmethod(applyAction)
|
||||
|
||||
def consume(position, state):
|
||||
x, y = position
|
||||
# Eat food
|
||||
if state.data.food[x][y]:
|
||||
state.data.scoreChange += 10
|
||||
state.data.food = state.data.food.copy()
|
||||
state.data.food[x][y] = False
|
||||
state.data._foodEaten = position
|
||||
# TODO: cache numFood?
|
||||
numFood = state.getNumFood()
|
||||
if numFood == 0 and not state.data._lose:
|
||||
state.data.scoreChange += 500
|
||||
state.data._win = True
|
||||
# Eat capsule
|
||||
if(position in state.getCapsules()):
|
||||
state.data.capsules.remove(position)
|
||||
state.data._capsuleEaten = position
|
||||
# Reset all ghosts' scared timers
|
||||
for index in range(1, len(state.data.agentStates)):
|
||||
state.data.agentStates[index].scaredTimer = SCARED_TIME
|
||||
consume = staticmethod(consume)
|
||||
|
||||
|
||||
class GhostRules:
|
||||
"""
|
||||
These functions dictate how ghosts interact with their environment.
|
||||
"""
|
||||
GHOST_SPEED = 1.0
|
||||
|
||||
def getLegalActions(state, ghostIndex):
|
||||
"""
|
||||
Ghosts cannot stop, and cannot turn around unless they
|
||||
reach a dead end, but can turn 90 degrees at intersections.
|
||||
"""
|
||||
conf = state.getGhostState(ghostIndex).configuration
|
||||
possibleActions = Actions.getPossibleActions(
|
||||
conf, state.data.layout.walls)
|
||||
reverse = Actions.reverseDirection(conf.direction)
|
||||
if Directions.STOP in possibleActions:
|
||||
possibleActions.remove(Directions.STOP)
|
||||
if reverse in possibleActions and len(possibleActions) > 1:
|
||||
possibleActions.remove(reverse)
|
||||
return possibleActions
|
||||
getLegalActions = staticmethod(getLegalActions)
|
||||
|
||||
def applyAction(state, action, ghostIndex):
|
||||
|
||||
legal = GhostRules.getLegalActions(state, ghostIndex)
|
||||
if action not in legal:
|
||||
raise Exception("Illegal ghost action " + str(action))
|
||||
|
||||
ghostState = state.data.agentStates[ghostIndex]
|
||||
speed = GhostRules.GHOST_SPEED
|
||||
if ghostState.scaredTimer > 0:
|
||||
speed /= 2.0
|
||||
vector = Actions.directionToVector(action, speed)
|
||||
ghostState.configuration = ghostState.configuration.generateSuccessor(
|
||||
vector)
|
||||
applyAction = staticmethod(applyAction)
|
||||
|
||||
def decrementTimer(ghostState):
|
||||
timer = ghostState.scaredTimer
|
||||
if timer == 1:
|
||||
ghostState.configuration.pos = nearestPoint(
|
||||
ghostState.configuration.pos)
|
||||
ghostState.scaredTimer = max(0, timer - 1)
|
||||
decrementTimer = staticmethod(decrementTimer)
|
||||
|
||||
def checkDeath(state, agentIndex):
|
||||
pacmanPosition = state.getPacmanPosition()
|
||||
if agentIndex == 0: # Pacman just moved; Anyone can kill him
|
||||
for index in range(1, len(state.data.agentStates)):
|
||||
ghostState = state.data.agentStates[index]
|
||||
ghostPosition = ghostState.configuration.getPosition()
|
||||
if GhostRules.canKill(pacmanPosition, ghostPosition):
|
||||
GhostRules.collide(state, ghostState, index)
|
||||
else:
|
||||
ghostState = state.data.agentStates[agentIndex]
|
||||
ghostPosition = ghostState.configuration.getPosition()
|
||||
if GhostRules.canKill(pacmanPosition, ghostPosition):
|
||||
GhostRules.collide(state, ghostState, agentIndex)
|
||||
checkDeath = staticmethod(checkDeath)
|
||||
|
||||
def collide(state, ghostState, agentIndex):
|
||||
if ghostState.scaredTimer > 0:
|
||||
state.data.scoreChange += 200
|
||||
GhostRules.placeGhost(state, ghostState)
|
||||
ghostState.scaredTimer = 0
|
||||
# Added for first-person
|
||||
state.data._eaten[agentIndex] = True
|
||||
else:
|
||||
if not state.data._win:
|
||||
state.data.scoreChange -= 500
|
||||
state.data._lose = True
|
||||
collide = staticmethod(collide)
|
||||
|
||||
def canKill(pacmanPosition, ghostPosition):
|
||||
return manhattanDistance(ghostPosition, pacmanPosition) <= COLLISION_TOLERANCE
|
||||
canKill = staticmethod(canKill)
|
||||
|
||||
def placeGhost(state, ghostState):
|
||||
ghostState.configuration = ghostState.start
|
||||
placeGhost = staticmethod(placeGhost)
|
||||
|
||||
#############################
|
||||
# FRAMEWORK TO START A GAME #
|
||||
#############################
|
||||
|
||||
|
||||
def default(str):
|
||||
return str + ' [Default: %default]'
|
||||
|
||||
|
||||
def parseAgentArgs(str):
|
||||
if str == None:
|
||||
return {}
|
||||
pieces = str.split(',')
|
||||
opts = {}
|
||||
for p in pieces:
|
||||
if '=' in p:
|
||||
key, val = p.split('=')
|
||||
else:
|
||||
key, val = p, 1
|
||||
opts[key] = val
|
||||
return opts
|
||||
|
||||
|
||||
def readCommand(argv):
|
||||
"""
|
||||
Processes the command used to run pacman from the command line.
|
||||
"""
|
||||
from optparse import OptionParser
|
||||
usageStr = """
|
||||
USAGE: python pacman.py <options>
|
||||
EXAMPLES: (1) python pacman.py
|
||||
- starts an interactive game
|
||||
(2) python pacman.py --layout smallClassic --zoom 2
|
||||
OR python pacman.py -l smallClassic -z 2
|
||||
- starts an interactive game on a smaller board, zoomed in
|
||||
"""
|
||||
parser = OptionParser(usageStr)
|
||||
|
||||
parser.add_option('-n', '--numGames', dest='numGames', type='int',
|
||||
help=default('the number of GAMES to play'), metavar='GAMES', default=1)
|
||||
parser.add_option('-l', '--layout', dest='layout',
|
||||
help=default(
|
||||
'the LAYOUT_FILE from which to load the map layout'),
|
||||
metavar='LAYOUT_FILE', default='mediumClassic')
|
||||
parser.add_option('-p', '--pacman', dest='pacman',
|
||||
help=default(
|
||||
'the agent TYPE in the pacmanAgents module to use'),
|
||||
metavar='TYPE', default='KeyboardAgent')
|
||||
parser.add_option('-t', '--textGraphics', action='store_true', dest='textGraphics',
|
||||
help='Display output as text only', default=False)
|
||||
parser.add_option('-q', '--quietTextGraphics', action='store_true', dest='quietGraphics',
|
||||
help='Generate minimal output and no graphics', default=False)
|
||||
parser.add_option('-g', '--ghosts', dest='ghost',
|
||||
help=default(
|
||||
'the ghost agent TYPE in the ghostAgents module to use'),
|
||||
metavar='TYPE', default='RandomGhost')
|
||||
parser.add_option('-k', '--numghosts', type='int', dest='numGhosts',
|
||||
help=default('The maximum number of ghosts to use'), default=4)
|
||||
parser.add_option('-z', '--zoom', type='float', dest='zoom',
|
||||
help=default('Zoom the size of the graphics window'), default=1.0)
|
||||
parser.add_option('-f', '--fixRandomSeed', action='store_true', dest='fixRandomSeed',
|
||||
help='Fixes the random seed to always play the same game', default=False)
|
||||
parser.add_option('-r', '--recordActions', action='store_true', dest='record',
|
||||
help='Writes game histories to a file (named by the time they were played)', default=False)
|
||||
parser.add_option('--replay', dest='gameToReplay',
|
||||
help='A recorded game file (pickle) to replay', default=None)
|
||||
parser.add_option('-a', '--agentArgs', dest='agentArgs',
|
||||
help='Comma separated values sent to agent. e.g. "opt1=val1,opt2,opt3=val3"')
|
||||
parser.add_option('-x', '--numTraining', dest='numTraining', type='int',
|
||||
help=default('How many episodes are training (suppresses output)'), default=0)
|
||||
parser.add_option('--frameTime', dest='frameTime', type='float',
|
||||
help=default('Time to delay between frames; <0 means keyboard'), default=0.1)
|
||||
parser.add_option('-c', '--catchExceptions', action='store_true', dest='catchExceptions',
|
||||
help='Turns on exception handling and timeouts during games', default=False)
|
||||
parser.add_option('--timeout', dest='timeout', type='int',
|
||||
help=default('Maximum length of time an agent can spend computing in a single game'), default=30)
|
||||
|
||||
options, otherjunk = parser.parse_args(argv)
|
||||
if len(otherjunk) != 0:
|
||||
raise Exception('Command line input not understood: ' + str(otherjunk))
|
||||
args = dict()
|
||||
|
||||
# Fix the random seed
|
||||
if options.fixRandomSeed:
|
||||
random.seed('cs188')
|
||||
|
||||
# Choose a layout
|
||||
args['layout'] = layout.getLayout(options.layout)
|
||||
if args['layout'] == None:
|
||||
raise Exception("The layout " + options.layout + " cannot be found")
|
||||
|
||||
# Choose a Pacman agent
|
||||
noKeyboard = options.gameToReplay == None and (
|
||||
options.textGraphics or options.quietGraphics)
|
||||
pacmanType = loadAgent(options.pacman, noKeyboard)
|
||||
agentOpts = parseAgentArgs(options.agentArgs)
|
||||
if options.numTraining > 0:
|
||||
args['numTraining'] = options.numTraining
|
||||
if 'numTraining' not in agentOpts:
|
||||
agentOpts['numTraining'] = options.numTraining
|
||||
pacman = pacmanType(**agentOpts) # Instantiate Pacman with agentArgs
|
||||
args['pacman'] = pacman
|
||||
|
||||
# Don't display training games
|
||||
if 'numTrain' in agentOpts:
|
||||
options.numQuiet = int(agentOpts['numTrain'])
|
||||
options.numIgnore = int(agentOpts['numTrain'])
|
||||
|
||||
# Choose a ghost agent
|
||||
ghostType = loadAgent(options.ghost, noKeyboard)
|
||||
args['ghosts'] = [ghostType(i+1) for i in range(options.numGhosts)]
|
||||
|
||||
# Choose a display format
|
||||
if options.quietGraphics:
|
||||
import textDisplay
|
||||
args['display'] = textDisplay.NullGraphics()
|
||||
elif options.textGraphics:
|
||||
import textDisplay
|
||||
textDisplay.SLEEP_TIME = options.frameTime
|
||||
args['display'] = textDisplay.PacmanGraphics()
|
||||
else:
|
||||
import graphicsDisplay
|
||||
args['display'] = graphicsDisplay.PacmanGraphics(
|
||||
options.zoom, frameTime=options.frameTime)
|
||||
args['numGames'] = options.numGames
|
||||
args['record'] = options.record
|
||||
args['catchExceptions'] = options.catchExceptions
|
||||
args['timeout'] = options.timeout
|
||||
|
||||
# Special case: recorded games don't use the runGames method or args structure
|
||||
if options.gameToReplay != None:
|
||||
print('Replaying recorded game %s.' % options.gameToReplay)
|
||||
import pickle
|
||||
f = open(options.gameToReplay)
|
||||
try:
|
||||
recorded = pickle.load(f)
|
||||
finally:
|
||||
f.close()
|
||||
recorded['display'] = args['display']
|
||||
replayGame(**recorded)
|
||||
sys.exit(0)
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def loadAgent(pacman, nographics):
|
||||
# Looks through all pythonPath Directories for the right module,
|
||||
pythonPathStr = os.path.expandvars("$PYTHONPATH")
|
||||
if pythonPathStr.find(';') == -1:
|
||||
pythonPathDirs = pythonPathStr.split(':')
|
||||
else:
|
||||
pythonPathDirs = pythonPathStr.split(';')
|
||||
pythonPathDirs.append('.')
|
||||
|
||||
for moduleDir in pythonPathDirs:
|
||||
if not os.path.isdir(moduleDir):
|
||||
continue
|
||||
moduleNames = [f for f in os.listdir(
|
||||
moduleDir) if f.endswith('gents.py')]
|
||||
for modulename in moduleNames:
|
||||
try:
|
||||
module = __import__(modulename[:-3])
|
||||
except ImportError:
|
||||
continue
|
||||
if pacman in dir(module):
|
||||
if nographics and modulename == 'keyboardAgents.py':
|
||||
raise Exception(
|
||||
'Using the keyboard requires graphics (not text display)')
|
||||
return getattr(module, pacman)
|
||||
raise Exception('The agent ' + pacman +
|
||||
' is not specified in any *Agents.py.')
|
||||
|
||||
|
||||
def replayGame(layout, actions, display):
|
||||
import pacmanAgents
|
||||
import ghostAgents
|
||||
rules = ClassicGameRules()
|
||||
agents = [pacmanAgents.GreedyAgent()] + [ghostAgents.RandomGhost(i+1)
|
||||
for i in range(layout.getNumGhosts())]
|
||||
game = rules.newGame(layout, agents[0], agents[1:], display)
|
||||
state = game.state
|
||||
display.initialize(state.data)
|
||||
|
||||
for action in actions:
|
||||
# Execute the action
|
||||
state = state.generateSuccessor(*action)
|
||||
# Change the display
|
||||
display.update(state.data)
|
||||
# Allow for game specific conditions (winning, losing, etc.)
|
||||
rules.process(state, game)
|
||||
|
||||
display.finish()
|
||||
|
||||
|
||||
def runGames(layout, pacman, ghosts, display, numGames, record, numTraining=0, catchExceptions=False, timeout=30):
|
||||
import __main__
|
||||
__main__.__dict__['_display'] = display
|
||||
|
||||
rules = ClassicGameRules(timeout)
|
||||
games = []
|
||||
|
||||
for i in range(numGames):
|
||||
beQuiet = i < numTraining
|
||||
if beQuiet:
|
||||
# Suppress output and graphics
|
||||
import textDisplay
|
||||
gameDisplay = textDisplay.NullGraphics()
|
||||
rules.quiet = True
|
||||
else:
|
||||
gameDisplay = display
|
||||
rules.quiet = False
|
||||
game = rules.newGame(layout, pacman, ghosts,
|
||||
gameDisplay, beQuiet, catchExceptions)
|
||||
game.run()
|
||||
if not beQuiet:
|
||||
games.append(game)
|
||||
|
||||
if record:
|
||||
import time
|
||||
import pickle
|
||||
fname = ('recorded-game-%d' % (i + 1)) + \
|
||||
'-'.join([str(t) for t in time.localtime()[1:6]])
|
||||
f = file(fname, 'w')
|
||||
components = {'layout': layout, 'actions': game.moveHistory}
|
||||
pickle.dump(components, f)
|
||||
f.close()
|
||||
|
||||
if (numGames-numTraining) > 0:
|
||||
scores = [game.state.getScore() for game in games]
|
||||
wins = [game.state.isWin() for game in games]
|
||||
winRate = wins.count(True) / float(len(wins))
|
||||
print('Average Score:', sum(scores) / float(len(scores)))
|
||||
print('Scores: ', ', '.join([str(score) for score in scores]))
|
||||
print('Win Rate: %d/%d (%.2f)' %
|
||||
(wins.count(True), len(wins), winRate))
|
||||
print('Record: ', ', '.join(
|
||||
[['Loss', 'Win'][int(w)] for w in wins]))
|
||||
|
||||
return games
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
The main function called when pacman.py is run
|
||||
from the command line:
|
||||
|
||||
> python pacman.py
|
||||
|
||||
See the usage string for more details.
|
||||
|
||||
> python pacman.py --help
|
||||
"""
|
||||
args = readCommand(sys.argv[1:]) # Get game components based on input
|
||||
runGames(**args)
|
||||
|
||||
# import cProfile
|
||||
# cProfile.run("runGames( **args )")
|
||||
pass
|
||||
63
proj2/pacmanAgents.py
Normal file
63
proj2/pacmanAgents.py
Normal file
@ -0,0 +1,63 @@
|
||||
# pacmanAgents.py
|
||||
# ---------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
from pacman import Directions
|
||||
from game import Agent
|
||||
import random
|
||||
import game
|
||||
import util
|
||||
|
||||
|
||||
class LeftTurnAgent(game.Agent):
|
||||
"An agent that turns left at every opportunity"
|
||||
|
||||
def getAction(self, state):
|
||||
legal = state.getLegalPacmanActions()
|
||||
current = state.getPacmanState().configuration.direction
|
||||
if current == Directions.STOP:
|
||||
current = Directions.NORTH
|
||||
left = Directions.LEFT[current]
|
||||
if left in legal:
|
||||
return left
|
||||
if current in legal:
|
||||
return current
|
||||
if Directions.RIGHT[current] in legal:
|
||||
return Directions.RIGHT[current]
|
||||
if Directions.LEFT[left] in legal:
|
||||
return Directions.LEFT[left]
|
||||
return Directions.STOP
|
||||
|
||||
|
||||
class GreedyAgent(Agent):
|
||||
def __init__(self, evalFn="scoreEvaluation"):
|
||||
self.evaluationFunction = util.lookup(evalFn, globals())
|
||||
assert self.evaluationFunction != None
|
||||
|
||||
def getAction(self, state):
|
||||
# Generate candidate actions
|
||||
legal = state.getLegalPacmanActions()
|
||||
if Directions.STOP in legal:
|
||||
legal.remove(Directions.STOP)
|
||||
|
||||
successors = [(state.generateSuccessor(0, action), action)
|
||||
for action in legal]
|
||||
scored = [(self.evaluationFunction(state), action)
|
||||
for state, action in successors]
|
||||
bestScore = max(scored)[0]
|
||||
bestActions = [pair[1] for pair in scored if pair[0] == bestScore]
|
||||
return random.choice(bestActions)
|
||||
|
||||
|
||||
def scoreEvaluation(state):
|
||||
return state.getScore()
|
||||
18
proj2/projectParams.py
Normal file
18
proj2/projectParams.py
Normal file
@ -0,0 +1,18 @@
|
||||
# projectParams.py
|
||||
# ----------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
STUDENT_CODE_DEFAULT = 'multiAgents.py'
|
||||
PROJECT_TEST_CLASSES = 'multiagentTestClasses.py'
|
||||
PROJECT_NAME = 'Project 2: Multiagent search'
|
||||
BONUS_PIC = False
|
||||
430
proj2/sample.typ
Executable file
430
proj2/sample.typ
Executable file
@ -0,0 +1,430 @@
|
||||
#import "labtemplate.typ": *
|
||||
#show: nudtlabpaper.with(
|
||||
author: "程景愉",
|
||||
id: "202302723005",
|
||||
title: "经典搜索",
|
||||
training_type: "普通本科生",
|
||||
grade: "2023级",
|
||||
major: "网络工程",
|
||||
department: "计算机学院",
|
||||
advisor: "胡罡",
|
||||
jobtitle: "教授",
|
||||
lab: "306-707",
|
||||
date: "2025.12.02",
|
||||
header_str: "经典搜索实验报告",
|
||||
simple_cover: true,
|
||||
)
|
||||
|
||||
#set page(header: [
|
||||
#set par(spacing: 6pt)
|
||||
#align(center)[#text(size: 11pt)[人工智能实验报告]]
|
||||
#v(-0.3em)
|
||||
#line(length: 100%, stroke: (thickness: 1pt))
|
||||
],)
|
||||
|
||||
#show heading: it => if it.level == 1 [
|
||||
#v(1em)
|
||||
#set text(font: hei, size: 16pt)
|
||||
#counter(heading).display()
|
||||
#h(0.5em)
|
||||
#it.body
|
||||
#v(0.5em)
|
||||
] else [
|
||||
#v(0.8em)
|
||||
#set text(font: "New Computer Modern", weight: "bold", style: "italic")
|
||||
#counter(heading).display(it.numbering)
|
||||
#h(0.5em)
|
||||
#it.body
|
||||
#v(0.5em)
|
||||
]
|
||||
|
||||
#outline(title: "目录",depth: 2, indent: 1em)
|
||||
|
||||
#set enum(indent: 0.5em,body-indent: 0.5em,)
|
||||
#pagebreak()
|
||||
|
||||
= 实验介绍
|
||||
#para[
|
||||
本项目是UC Berkeley CS188人工智能课程的第一个项目,主要任务是实现多种通用搜索算法,并将其应用于解决经典游戏吃豆人(Pacman)中的路径规划问题。项目涵盖了从基础的无信息搜索算法(如DFS、BFS)到有信息搜索算法(如UCS、A\*),以及针对特定问题(如访问所有角落、吃掉所有食物)的状态表示和启发式函数设计。通过本项目,我们将深入理解搜索算法的原理,并学会如何利用它们来解决实际问题。
|
||||
]
|
||||
|
||||
= 实验内容
|
||||
#para[
|
||||
本次实验内容涵盖了8个递进的编程任务,具体如下:
|
||||
]
|
||||
+ *Q1: 深度优先搜索 (DFS)*:利用深度优先搜索算法寻找迷宫中的固定食物点。
|
||||
+ *Q2: 广度优先搜索 (BFS)*:利用广度优先搜索算法,找到到达目标所需行动次数最少的路径。
|
||||
+ *Q3: 一致代价搜索 (UCS)*:实现一致代价搜索,以处理具有不同行动代价的路径,找到总代价最小的路径。
|
||||
+ *Q4: A\* 搜索*:实现A\*搜索算法,该算法结合了路径的实际代价和启发式评估,并使用曼哈顿距离作为启发式函数进行测试。
|
||||
+ *Q5: 角落问题*:设计一个新的搜索问题,要求吃豆人以最短路径访问迷宫的全部四个角落。这需要定义一个合适的状态空间。
|
||||
+ *Q6: 角落启发式*:为角落问题设计一个一致且可接受的启发式函数,以加速A\*搜索过程。
|
||||
+ *Q7: 食物启发式*:为“吃完所有食物”这一复杂问题设计一个高效的启发式函数,以在可接受的时间内找到优良路径。
|
||||
+ *Q8: 寻找最近点路径*:实现一个次优的贪心策略,让吃豆人总是前往最近的食物点,作为一种快速解决复杂问题的近似方法。
|
||||
|
||||
= 实验要求
|
||||
#para[
|
||||
本项目要求在 `search.py` 和 `searchAgents.py` 两个文件中根据指引补全代码,核心要求如下:
|
||||
]
|
||||
+ *通用搜索算法*:在 `search.py` 中实现 `depthFirstSearch`, `breadthFirstSearch`, `uniformCostSearch`, 和 `aStarSearch` 四个核心搜索算法。所有搜索函数都需要返回一个动作(action)列表,该列表能够引导吃豆人从起点到达目标。
|
||||
+ *数据结构*:必须使用项目框架中 `util.py` 提供的 `Stack`, `Queue` 和 `PriorityQueue` 数据结构,以确保与自动评分器的兼容性。
|
||||
+ *问题建模*:在 `searchAgents.py` 中,需要为角落问题(`CornersProblem`)选择并实现一个不包含冗余信息的高效状态表示。
|
||||
+ *启发式函数设计*:为角落问题和食物问题设计的启发式函数(`cornersHeuristic` 和 `foodHeuristic`)必须是无不足道的(non-trivial)、非负且一致的(consistent)。启发式函数的性能将根据其在A\*搜索中扩展的节点数量进行评分。
|
||||
+ *目标测试*:为 `AnyFoodSearchProblem` 补全目标测试函数 `isGoalState`,使其能够正确判断是否到达任意一个食物所在的位置。
|
||||
|
||||
= 实验步骤与实现
|
||||
== Q1: 深度优先搜索 (DFS)
|
||||
*实现思路*:深度优先搜索(DFS)优先探索最深的节点。我们使用图搜索版本,即记录已访问的节点以避免冗余搜索和无限循环。数据结构上,DFS采用栈(Stack)来实现“后进先出”(LIFO)的节点扩展顺序。
|
||||
|
||||
*核心代码* (`search.py`):
|
||||
```python
|
||||
def depthFirstSearch(problem: SearchProblem):
|
||||
fringe = util.Stack()
|
||||
visited = set()
|
||||
startState = problem.getStartState()
|
||||
fringe.push((startState, []))
|
||||
|
||||
while not fringe.isEmpty():
|
||||
currentState, actions = fringe.pop()
|
||||
|
||||
if currentState in visited:
|
||||
continue
|
||||
|
||||
visited.add(currentState)
|
||||
|
||||
if problem.isGoalState(currentState):
|
||||
return actions
|
||||
|
||||
successors = problem.getSuccessors(currentState)
|
||||
|
||||
for successor, action, cost in successors:
|
||||
if successor not in visited:
|
||||
newActions = actions + [action]
|
||||
fringe.push((successor, newActions))
|
||||
|
||||
return []
|
||||
```
|
||||
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q1
|
||||
python pacman.py -l mediumMaze -p SearchAgent -a fn=dfs
|
||||
```
|
||||
|
||||
== Q2: 广度优先搜索 (BFS)
|
||||
*实现思路*:广度优先搜索(BFS)逐层扩展节点,确保找到行动次数最少的路径。同样采用图搜索版本。数据结构上,BFS采用队列(Queue)来实现“先进先出”(FIFO)的节点扩展顺序。
|
||||
|
||||
*核心代码* (`search.py`):
|
||||
```python
|
||||
def breadthFirstSearch(problem: SearchProblem):
|
||||
fringe = util.Queue()
|
||||
visited = set()
|
||||
startState = problem.getStartState()
|
||||
fringe.push((startState, []))
|
||||
|
||||
while not fringe.isEmpty():
|
||||
currentState, actions = fringe.pop()
|
||||
|
||||
if currentState in visited:
|
||||
continue
|
||||
|
||||
visited.add(currentState)
|
||||
|
||||
if problem.isGoalState(currentState):
|
||||
return actions
|
||||
|
||||
successors = problem.getSuccessors(currentState)
|
||||
|
||||
for successor, action, cost in successors:
|
||||
if successor not in visited:
|
||||
newActions = actions + [action]
|
||||
fringe.push((successor, newActions))
|
||||
|
||||
return []
|
||||
```
|
||||
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q2
|
||||
python pacman.py -l mediumMaze -p SearchAgent -a fn=bfs
|
||||
```
|
||||
|
||||
== Q3: 一致代价搜索 (UCS)
|
||||
*实现思路*:一致代价搜索(UCS)扩展总路径代价最小的节点,从而保证找到最优(总代价最低)的路径。它通过使用优先队列(PriorityQueue)来实现,节点的优先级由其累积路径代价决定。
|
||||
|
||||
*核心代码* (`search.py`):
|
||||
```python
|
||||
def uniformCostSearch(problem: SearchProblem):
|
||||
fringe = util.PriorityQueue()
|
||||
visited = {}
|
||||
startState = problem.getStartState()
|
||||
fringe.push((startState, [], 0), 0)
|
||||
|
||||
while not fringe.isEmpty():
|
||||
currentState, actions, currentCost = fringe.pop()
|
||||
|
||||
if currentState in visited and currentCost >= visited[currentState]:
|
||||
continue
|
||||
|
||||
visited[currentState] = currentCost
|
||||
|
||||
if problem.isGoalState(currentState):
|
||||
return actions
|
||||
|
||||
successors = problem.getSuccessors(currentState)
|
||||
|
||||
for successor, action, stepCost in successors:
|
||||
newCost = currentCost + stepCost
|
||||
newActions = actions + [action]
|
||||
fringe.push((successor, newActions, newCost), newCost)
|
||||
|
||||
return []
|
||||
```
|
||||
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q3
|
||||
python pacman.py -l mediumMaze -p SearchAgent -a fn=ucs
|
||||
```
|
||||
|
||||
== Q4: A\* 搜索
|
||||
*实现思路*:A\*搜索是UCS的扩展,它在评估节点时不仅考虑已付出的代价$g(n)$,还引入了对未来代价的估计,即启发式函数$h(n)$。节点优先级由$f(n) = g(n) + h(n)$决定。这使得A\*能够更智能地朝向目标进行搜索。
|
||||
|
||||
*核心代码* (`search.py`):
|
||||
```python
|
||||
def aStarSearch(problem: SearchProblem, heuristic=nullHeuristic):
|
||||
fringe = util.PriorityQueue()
|
||||
visited = {}
|
||||
startState = problem.getStartState()
|
||||
startHeuristic = heuristic(startState, problem)
|
||||
fringe.push((startState, [], 0), startHeuristic)
|
||||
|
||||
while not fringe.isEmpty():
|
||||
currentState, actions, currentCost = fringe.pop()
|
||||
|
||||
if currentState in visited and currentCost >= visited[currentState]:
|
||||
continue
|
||||
|
||||
visited[currentState] = currentCost
|
||||
|
||||
if problem.isGoalState(currentState):
|
||||
return actions
|
||||
|
||||
successors = problem.getSuccessors(currentState)
|
||||
|
||||
for successor, action, stepCost in successors:
|
||||
newCost = currentCost + stepCost
|
||||
newHeuristic = heuristic(successor, problem)
|
||||
fValue = newCost + newHeuristic
|
||||
newActions = actions + [action]
|
||||
fringe.push((successor, newActions, newCost), fValue)
|
||||
|
||||
return []
|
||||
```
|
||||
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q4
|
||||
python pacman.py -l bigMaze -z .5 -p SearchAgent -a fn=astar,heuristic=manhattanHeuristic
|
||||
```
|
||||
|
||||
== Q5: 角落问题 (Corners Problem)
|
||||
*实现思路*:为了解决访问所有四个角落的问题,我们需要设计一个能够追踪Pacman位置和哪些角落已被访问的状态表示。一个合适的状态是 `(position, visited_corners)`,其中 `position` 是 `(x, y)` 坐标,`visited_corners` 是一个布尔元组,记录四个角落各自的访问情况。
|
||||
|
||||
*核心代码* (`searchAgents.py`):
|
||||
```python
|
||||
// 状态表示:(当前位置, 已访问的角落元组)
|
||||
// getStartState
|
||||
def getStartState(self):
|
||||
cornersVisited = tuple([self.startingPosition == corner for corner in self.corners])
|
||||
return (self.startingPosition, cornersVisited)
|
||||
|
||||
// isGoalState: 检查是否所有角落都已访问
|
||||
def isGoalState(self, state: Any):
|
||||
_, cornersVisited = state
|
||||
return all(cornersVisited)
|
||||
|
||||
// getSuccessors: 生成后继状态,并更新角落访问信息
|
||||
def getSuccessors(self, state: Any):
|
||||
successors = []
|
||||
currentPosition, cornersVisited = state
|
||||
|
||||
for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]:
|
||||
x, y = currentPosition
|
||||
dx, dy = Actions.directionToVector(action)
|
||||
nextx, nexty = int(x + dx), int(y + dy)
|
||||
|
||||
if not self.walls[nextx][nexty]:
|
||||
nextPosition = (nextx, nexty)
|
||||
newCornersVisited = list(cornersVisited)
|
||||
|
||||
for i, corner in enumerate(self.corners):
|
||||
if nextPosition == corner and not newCornersVisited[i]:
|
||||
newCornersVisited[i] = True
|
||||
|
||||
successorState = (nextPosition, tuple(newCornersVisited))
|
||||
successors.append((successorState, action, 1))
|
||||
|
||||
self._expanded += 1
|
||||
return successors
|
||||
```
|
||||
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q5
|
||||
python pacman.py -l mediumCorners -p SearchAgent -a fn=bfs,prob=CornersProblem
|
||||
```
|
||||
|
||||
== Q6: 角落启发式 (Corners Heuristic)
|
||||
*实现思路*:为角落问题设计一个一致且可接受的启发式函数,关键是估计从当前状态到达目标状态(所有角落被访问)的最小代价。一个有效的策略是计算当前位置到所有未访问角落的“某种”距离。我们采用的策略是:启发式的值取“当前位置到最远的未访问角落的曼哈顿距离”和“所有未访问角落两两之间最远曼哈顿距离”中的较大者。这确保了启发式是可接受的,因为它低估了必须走过的总路程。
|
||||
|
||||
*核心代码* (`searchAgents.py`):
|
||||
```python
|
||||
def cornersHeuristic(state: Any, problem: CornersProblem):
|
||||
currentPosition, cornersVisited = state
|
||||
corners = problem.corners
|
||||
|
||||
if all(cornersVisited):
|
||||
return 0
|
||||
|
||||
unvisitedCorners = []
|
||||
for i, corner in enumerate(corners):
|
||||
if not cornersVisited[i]:
|
||||
unvisitedCorners.append(corner)
|
||||
|
||||
if not unvisitedCorners:
|
||||
return 0
|
||||
|
||||
maxDistance = 0
|
||||
for corner in unvisitedCorners:
|
||||
distance = util.manhattanDistance(currentPosition, corner)
|
||||
maxDistance = max(maxDistance, distance)
|
||||
|
||||
maxCornerDistance = 0
|
||||
for i in range(len(unvisitedCorners)):
|
||||
for j in range(i + 1, len(unvisitedCorners)):
|
||||
distance = util.manhattanDistance(unvisitedCorners[i], unvisitedCorners[j])
|
||||
maxCornerDistance = max(maxCornerDistance, distance)
|
||||
|
||||
return max(maxDistance, maxCornerDistance)
|
||||
```
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q6
|
||||
python pacman.py -l mediumCorners -p AStarCornersAgent -z 0.5
|
||||
```
|
||||
|
||||
== Q7: 食物启发式 (Food Heuristic)
|
||||
*实现思路*:为“吃掉所有食物”问题设计启发式函数更具挑战性。状态包含当前位置和食物分布的网格。一个好的启发式需要有效估计吃掉所有剩余食物的最小步数。我们结合了多种策略来构造一个更强的启发式:取“当前位置到最远食物的曼哈顿距离”、“剩余食物中两两之间最远的曼哈顿距离”以及“剩余食物的数量”这三者的最大值。这个值仍然是真实代价的下界,保证了可接受性和一致性。
|
||||
|
||||
*核心代码* (`searchAgents.py`):
|
||||
```python
|
||||
def foodHeuristic(state: Tuple[Tuple, List[List]], problem: FoodSearchProblem):
|
||||
position, foodGrid = state
|
||||
foodList = foodGrid.asList()
|
||||
|
||||
if not foodList:
|
||||
return 0
|
||||
|
||||
maxDistance = 0
|
||||
for food in foodList:
|
||||
distance = util.manhattanDistance(position, food)
|
||||
maxDistance = max(maxDistance, distance)
|
||||
|
||||
maxFoodDistance = 0
|
||||
if len(foodList) > 1:
|
||||
for i in range(len(foodList)):
|
||||
for j in range(i + 1, len(foodList)):
|
||||
distance = util.manhattanDistance(foodList[i], foodList[j])
|
||||
maxFoodDistance = max(maxFoodDistance, distance)
|
||||
|
||||
foodCount = len(foodList)
|
||||
|
||||
return max(maxDistance, maxFoodDistance, foodCount)
|
||||
```
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q7
|
||||
python pacman.py -l trickySearch -p AStarFoodSearchAgent
|
||||
```
|
||||
|
||||
== Q8: 寻找最近点路径 (Closest Dot Search)
|
||||
*实现思路*:这是一个贪心算法,它不保证找到全局最优解,但通常能快速找到一个较好的解。策略是:重复寻找并移动到距离当前位置最近的食物点,直到所有食物被吃完。这个任务的核心是实现 `findPathToClosestDot` 函数。我们可以定义一个 `AnyFoodSearchProblem`,其目标是到达任意一个食物点,然后使用BFS来找到到达最近食物的最短路径。
|
||||
|
||||
*核心代码* (`searchAgents.py`):
|
||||
```python
|
||||
// AnyFoodSearchProblem 的目标测试
|
||||
def isGoalState(self, state: Tuple[int, int]):
|
||||
x,y = state
|
||||
return self.food[x][y]
|
||||
|
||||
// findPathToClosestDot 函数实现
|
||||
def findPathToClosestDot(self, gameState: pacman.GameState):
|
||||
problem = AnyFoodSearchProblem(gameState)
|
||||
// BFS保证找到步数最少的路径,即到达最近的食物
|
||||
path = search.bfs(problem)
|
||||
return path
|
||||
```
|
||||
*测试指令*:
|
||||
```fish
|
||||
python autograder.py -q q8
|
||||
python pacman.py -l bigSearch -p ClosestDotSearchAgent -z .5
|
||||
```
|
||||
|
||||
= 实验结果
|
||||
#para[
|
||||
本项目的所有8个任务均已成功实现,并通过了自动评分器(autograder)的所有测试用例,最终取得了 *25/25* 的满分成绩。自动评分器给出的最终成绩摘要如下:
|
||||
]
|
||||
|
||||
```
|
||||
Provisional grades
|
||||
==================
|
||||
Question q1: 3/3
|
||||
Question q2: 3/3
|
||||
Question q3: 3/3
|
||||
Question q4: 3/3
|
||||
Question q5: 3/3
|
||||
Question q6: 3/3
|
||||
Question q7: 4/4
|
||||
Question q8: 3/3
|
||||
------------------
|
||||
Total: 25/25
|
||||
```
|
||||
|
||||
#para[
|
||||
以下是各部分任务的详细测试结果摘要:
|
||||
]
|
||||
+ *Q1: 深度优先搜索*
|
||||
- 所有测试用例通过。
|
||||
- 在 `mediumMaze` 迷宫中找到一条长度为130的路径,扩展了146个节点。
|
||||
+ *Q2: 广度优先搜索*
|
||||
- 所有测试用例通过。
|
||||
- 在 `mediumMaze` 迷宫中找到长度为68的最短路径,扩展了269个节点。
|
||||
+ *Q3: 一致代价搜索*
|
||||
- 所有测试用例通过。
|
||||
- 在不同代价的迷宫中(`mediumDottedMaze`, `mediumScaryMaze`)均能找到最优路径。
|
||||
+ *Q4: A\*搜索*
|
||||
- 所有测试用例通过。
|
||||
- 在 `bigMaze` 中,A\* 搜索扩展的节点数(约549)少于UCS(约620),展现了启发式的有效性。
|
||||
+ *Q5: 角落问题*
|
||||
- 所有测试用例通过。
|
||||
- 在 `mediumCorners` 上,BFS扩展了接近2000个节点。
|
||||
+ *Q6: 角落启发式*
|
||||
- 所有测试用例通过,启发式满足一致性。
|
||||
- 在 `mediumCorners` 上,使用A\*和该启发式将扩展节点数减少到 *961* 个,满足了评分的最高要求。
|
||||
+ *Q7: 食物启发式*
|
||||
- 18个测试用例全部通过,启发式满足一致性。
|
||||
- 在 `trickySearch` 上,扩展节点数为 *8763* 个,达到了满分(4/4)要求。
|
||||
+ *Q8: 寻找最近点路径*
|
||||
- 所有测试用例通过,贪心策略能够成功吃掉所有食物。
|
||||
|
||||
#pagebreak()
|
||||
= 实验总结
|
||||
#para[
|
||||
通过本次实验,我成功实现了包括深度优先搜索(DFS)、广度优先搜索(BFS)、一致代价搜索(UCS)和A\*搜索在内的多种经典搜索算法,并将其应用于解决Pacman游戏中的路径规划问题。
|
||||
]
|
||||
#para[
|
||||
在实验过程中,我遇到的主要挑战在于为复杂问题设计高效、一致且可接受的启发式函数,特别是Q6的角落问题和Q7的食物收集问题。通过对问题进行抽象建模,并反复迭代优化启发式策略,最终设计出的启发式函数在满足一致性的前提下,显著减少了搜索节点的扩展数量,达到了评分要求。例如,在Q7中,通过比较“到最远食物的距离”、“食物间的最大距离”和“食物数量”等多个策略,并取其最大值作为启发式,最终将扩展节点数控制在了9000以内。
|
||||
]
|
||||
#para[
|
||||
通过这个项目,我不仅深入掌握了各种搜索算法的原理、实现细节及其适用场景,还深刻理解了状态空间表示、启发式函数设计(可接受性与一致性)对搜索性能的决定性影响。这些知识和经验对于解决更广泛的人工智能规划问题具有重要的实践意义。未来,可以尝试引入更高级的启发式策略(如基于最小生成树的启发式)或探索其他搜索算法变体,以期进一步提升求解效率。
|
||||
]
|
||||
150
proj2/task.txt
Normal file
150
proj2/task.txt
Normal file
@ -0,0 +1,150 @@
|
||||
Project 2: Multi-Agent Pacman
|
||||
– Q1: Reflex Agent (必做,15分)
|
||||
– Q2: Minimax (必做,40分)
|
||||
– Q3: Alpha-Beta Pruning (必做,40分)
|
||||
– Q5: Evaluation Fuction (选做,5分)
|
||||
实验代码只需要提交4个文件:multiAgents.py,pacman.py, game.py, util.py
|
||||
|
||||
Q1 (4 pts): Reflex Agent
|
||||
|
||||
Improve the ReflexAgent in multiAgents.py to play respectably. The provided reflex agent code provides some helpful examples of methods that query the GameState for information. A capable reflex agent will have to consider both food locations and ghost locations to perform well. Your agent should easily and reliably clear the testClassic layout:
|
||||
|
||||
python pacman.py -p ReflexAgent -l testClassic
|
||||
|
||||
Try out your reflex agent on the default mediumClassic layout with one ghost or two (and animation off to speed up the display):
|
||||
|
||||
python pacman.py --frameTime 0 -p ReflexAgent -k 1
|
||||
|
||||
python pacman.py --frameTime 0 -p ReflexAgent -k 2
|
||||
|
||||
How does your agent fare? It will likely often die with 2 ghosts on the default board, unless your evaluation function is quite good.
|
||||
|
||||
Note: Remember that newFood has the function asList()
|
||||
|
||||
Note: As features, try the reciprocal of important values (such as distance to food) rather than just the values themselves.
|
||||
|
||||
Note: The evaluation function you’re writing is evaluating state-action pairs; in later parts of the project, you’ll be evaluating states.
|
||||
|
||||
Note: You may find it useful to view the internal contents of various objects for debugging. You can do this by printing the objects’ string representations. For example, you can print newGhostStates with print(newGhostStates).
|
||||
|
||||
Options: Default ghosts are random; you can also play for fun with slightly smarter directional ghosts using -g DirectionalGhost. If the randomness is preventing you from telling whether your agent is improving, you can use -f to run with a fixed random seed (same random choices every game). You can also play multiple games in a row with -n. Turn off graphics with -q to run lots of games quickly.
|
||||
|
||||
Grading: We will run your agent on the openClassic layout 10 times. You will receive 0 points if your agent times out, or never wins. You will receive 1 point if your agent wins at least 5 times, or 2 points if your agent wins all 10 games. You will receive an additional 1 point if your agent’s average score is greater than 500, or 2 points if it is greater than 1000. You can try your agent out under these conditions with
|
||||
|
||||
python autograder.py -q q1
|
||||
|
||||
To run it without graphics, use:
|
||||
|
||||
python autograder.py -q q1 --no-graphics
|
||||
|
||||
Don’t spend too much time on this question, though, as the meat of the project lies ahead.
|
||||
Q2 (5 pts): Minimax
|
||||
|
||||
Now you will write an adversarial search agent in the provided MinimaxAgent class stub in multiAgents.py. Your minimax agent should work with any number of ghosts, so you’ll have to write an algorithm that is slightly more general than what you’ve previously seen in lecture. In particular, your minimax tree will have multiple min layers (one for each ghost) for every max layer.
|
||||
|
||||
Your code should also expand the game tree to an arbitrary depth. Score the leaves of your minimax tree with the supplied self.evaluationFunction, which defaults to scoreEvaluationFunction. MinimaxAgent extends MultiAgentSearchAgent, which gives access to self.depth and self.evaluationFunction. Make sure your minimax code makes reference to these two variables where appropriate as these variables are populated in response to command line options. (Do not harcode calls to scoreEvaluationFunction since we will be writing a better evaluation function later in the project.)
|
||||
|
||||
Important: A single search ply is considered to be one Pacman move and all the ghosts’ responses, so depth 2 search will involve Pacman and each ghost moving two times (see diagram below).
|
||||
|
||||
Minimax tree with depth 2
|
||||
|
||||
Grading: We will be checking your code to determine whether it explores the correct number of game states. This is the only reliable way to detect some very subtle bugs in implementations of minimax. As a result, the autograder will be very picky about how many times you call GameState.generateSuccessor. If you call it any more or less than necessary, the autograder will complain. To test and debug your code, run
|
||||
|
||||
python autograder.py -q q2
|
||||
|
||||
This will show what your algorithm does on a number of small trees, as well as a pacman game. To run it without graphics, use:
|
||||
|
||||
python autograder.py -q q2 --no-graphics
|
||||
|
||||
Hints and Observations
|
||||
|
||||
Implement the algorithm recursively using helper function(s).
|
||||
The correct implementation of minimax will lead to Pacman losing the game in some tests. This is not a problem: as it is correct behaviour, it will pass the tests.
|
||||
The evaluation function for the Pacman test in this part is already written (self.evaluationFunction). You shouldn’t change this function, but recognize that now we’re evaluating states rather than actions, as we were for the reflex agent. Look-ahead agents evaluate future states whereas reflex agents evaluate actions from the current state.
|
||||
The minimax values of the initial state in the minimaxClassic layout are 9, 8, 7, -492 for depths 1, 2, 3 and 4 respectively. Note that your minimax agent will often win (665/1000 games for us) despite the dire prediction of depth 4 minimax.
|
||||
|
||||
python pacman.py -p MinimaxAgent -l minimaxClassic -a depth=4
|
||||
|
||||
Pacman is always agent 0, and the agents move in order of increasing agent index.
|
||||
All states in minimax should be GameStates, either passed in to getAction or generated via GameState.generateSuccessor. In this project, you will not be abstracting to simplified states.
|
||||
On larger boards such as openClassic and mediumClassic (the default), you’ll find Pacman to be good at not dying, but quite bad at winning. He’ll often thrash around without making progress. He might even thrash around right next to a dot without eating it because he doesn’t know where he’d go after eating that dot. Don’t worry if you see this behavior, question 5 will clean up all of these issues.
|
||||
When Pacman believes that his death is unavoidable, he will try to end the game as soon as possible because of the constant penalty for living. Sometimes, this is the wrong thing to do with random ghosts, but minimax agents always assume the worst:
|
||||
|
||||
python pacman.py -p MinimaxAgent -l trappedClassic -a depth=3
|
||||
|
||||
Make sure you understand why Pacman rushes the closest ghost in this case.
|
||||
|
||||
Q3 (5 pts): Alpha-Beta Pruning
|
||||
|
||||
Make a new agent that uses alpha-beta pruning to more efficiently explore the minimax tree, in AlphaBetaAgent. Again, your algorithm will be slightly more general than the pseudocode from lecture, so part of the challenge is to extend the alpha-beta pruning logic appropriately to multiple minimizer agents.
|
||||
|
||||
You should see a speed-up (perhaps depth 3 alpha-beta will run as fast as depth 2 minimax). Ideally, depth 3 on smallClassic should run in just a few seconds per move or faster.
|
||||
|
||||
python pacman.py -p AlphaBetaAgent -a depth=3 -l smallClassic
|
||||
|
||||
The AlphaBetaAgent minimax values should be identical to the MinimaxAgent minimax values, although the actions it selects can vary because of different tie-breaking behavior. Again, the minimax values of the initial state in the minimaxClassic layout are 9, 8, 7 and -492 for depths 1, 2, 3 and 4 respectively.
|
||||
|
||||
Grading: Because we check your code to determine whether it explores the correct number of states, it is important that you perform alpha-beta pruning without reordering children. In other words, successor states should always be processed in the order returned by GameState.getLegalActions. Again, do not call GameState.generateSuccessor more than necessary.
|
||||
|
||||
You must not prune on equality in order to match the set of states explored by our autograder. (Indeed, alternatively, but incompatible with our autograder, would be to also allow for pruning on equality and invoke alpha-beta once on each child of the root node, but this will not match the autograder.)
|
||||
|
||||
The pseudo-code below represents the algorithm you should implement for this question.
|
||||
|
||||
Alpha-Beta Implementation
|
||||
|
||||
To test and debug your code, run
|
||||
|
||||
python autograder.py -q q3
|
||||
|
||||
This will show what your algorithm does on a number of small trees, as well as a pacman game. To run it without graphics, use:
|
||||
|
||||
python autograder.py -q q3 --no-graphics
|
||||
|
||||
The correct implementation of alpha-beta pruning will lead to Pacman losing some of the tests. This is not a problem: as it is correct behaviour, it will pass the tests.
|
||||
|
||||
Q4 (5 pts): Expectimax
|
||||
|
||||
Minimax and alpha-beta are great, but they both assume that you are playing against an adversary who makes optimal decisions. As anyone who has ever won tic-tac-toe can tell you, this is not always the case. In this question you will implement the ExpectimaxAgent, which is useful for modeling probabilistic behavior of agents who may make suboptimal choices.
|
||||
|
||||
As with the search and problems yet to be covered in this class, the beauty of these algorithms is their general applicability. To expedite your own development, we’ve supplied some test cases based on generic trees. You can debug your implementation on small the game trees using the command:
|
||||
|
||||
python autograder.py -q q4
|
||||
|
||||
Debugging on these small and manageable test cases is recommended and will help you to find bugs quickly.
|
||||
|
||||
Once your algorithm is working on small trees, you can observe its success in Pacman. Random ghosts are of course not optimal minimax agents, and so modeling them with minimax search may not be appropriate. ExpectimaxAgent will no longer take the min over all ghost actions, but the expectation according to your agent’s model of how the ghosts act. To simplify your code, assume you will only be running against an adversary which chooses amongst their getLegalActions uniformly at random.
|
||||
|
||||
To see how the ExpectimaxAgent behaves in Pacman, run:
|
||||
|
||||
python pacman.py -p ExpectimaxAgent -l minimaxClassic -a depth=3
|
||||
|
||||
You should now observe a more cavalier approach in close quarters with ghosts. In particular, if Pacman perceives that he could be trapped but might escape to grab a few more pieces of food, he’ll at least try. Investigate the results of these two scenarios:
|
||||
|
||||
python pacman.py -p AlphaBetaAgent -l trappedClassic -a depth=3 -q -n 10
|
||||
|
||||
python pacman.py -p ExpectimaxAgent -l trappedClassic -a depth=3 -q -n 10
|
||||
|
||||
You should find that your ExpectimaxAgent wins about half the time, while your AlphaBetaAgent always loses. Make sure you understand why the behavior here differs from the minimax case.
|
||||
|
||||
The correct implementation of expectimax will lead to Pacman losing some of the tests. This is not a problem: as it is correct behaviour, it will pass the tests.
|
||||
Q5 (6 pts): Evaluation Function
|
||||
|
||||
Write a better evaluation function for Pacman in the provided function betterEvaluationFunction. The evaluation function should evaluate states, rather than actions like your reflex agent evaluation function did. With depth 2 search, your evaluation function should clear the smallClassic layout with one random ghost more than half the time and still run at a reasonable rate (to get full credit, Pacman should be averaging around 1000 points when he’s winning).
|
||||
|
||||
Grading: the autograder will run your agent on the smallClassic layout 10 times. We will assign points to your evaluation function in the following way:
|
||||
|
||||
If you win at least once without timing out the autograder, you receive 1 points. Any agent not satisfying these criteria will receive 0 points.
|
||||
+1 for winning at least 5 times, +2 for winning all 10 times
|
||||
+1 for an average score of at least 500, +2 for an average score of at least 1000 (including scores on lost games)
|
||||
+1 if your games take on average less than 30 seconds on the autograder machine, when run with --no-graphics.
|
||||
The additional points for average score and computation time will only be awarded if you win at least 5 times.
|
||||
Please do not copy any files from Project 1, as it will not pass the autograder on Gradescope.
|
||||
|
||||
You can try your agent out under these conditions with
|
||||
|
||||
python autograder.py -q q5
|
||||
|
||||
To run it without graphics, use:
|
||||
|
||||
python autograder.py -q q5 --no-graphics
|
||||
|
||||
204
proj2/testClasses.py
Normal file
204
proj2/testClasses.py
Normal file
@ -0,0 +1,204 @@
|
||||
# testClasses.py
|
||||
# --------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
# import modules from python standard library
|
||||
import inspect
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
# Class which models a question in a project. Note that questions have a
|
||||
# maximum number of points they are worth, and are composed of a series of
|
||||
# test cases
|
||||
class Question(object):
|
||||
|
||||
def raiseNotDefined(self):
|
||||
print('Method not implemented: %s' % inspect.stack()[1][3])
|
||||
sys.exit(1)
|
||||
|
||||
def __init__(self, questionDict, display):
|
||||
self.maxPoints = int(questionDict['max_points'])
|
||||
self.testCases = []
|
||||
self.display = display
|
||||
|
||||
def getDisplay(self):
|
||||
return self.display
|
||||
|
||||
def getMaxPoints(self):
|
||||
return self.maxPoints
|
||||
|
||||
# Note that 'thunk' must be a function which accepts a single argument,
|
||||
# namely a 'grading' object
|
||||
def addTestCase(self, testCase, thunk):
|
||||
self.testCases.append((testCase, thunk))
|
||||
|
||||
def execute(self, grades):
|
||||
self.raiseNotDefined()
|
||||
|
||||
# Question in which all test cases must be passed in order to receive credit
|
||||
class PassAllTestsQuestion(Question):
|
||||
|
||||
def execute(self, grades):
|
||||
# TODO: is this the right way to use grades? The autograder doesn't seem to use it.
|
||||
testsFailed = False
|
||||
grades.assignZeroCredit()
|
||||
for _, f in self.testCases:
|
||||
if not f(grades):
|
||||
testsFailed = True
|
||||
if testsFailed:
|
||||
grades.fail("Tests failed.")
|
||||
else:
|
||||
grades.assignFullCredit()
|
||||
|
||||
|
||||
class ExtraCreditPassAllTestsQuestion(Question):
|
||||
def __init__(self, questionDict, display):
|
||||
Question.__init__(self, questionDict, display)
|
||||
self.extraPoints = int(questionDict['extra_points'])
|
||||
|
||||
def execute(self, grades):
|
||||
# TODO: is this the right way to use grades? The autograder doesn't seem to use it.
|
||||
testsFailed = False
|
||||
grades.assignZeroCredit()
|
||||
for _, f in self.testCases:
|
||||
if not f(grades):
|
||||
testsFailed = True
|
||||
if testsFailed:
|
||||
grades.fail("Tests failed.")
|
||||
else:
|
||||
grades.assignFullCredit()
|
||||
grades.addPoints(self.extraPoints)
|
||||
|
||||
# Question in which predict credit is given for test cases with a ``points'' property.
|
||||
# All other tests are mandatory and must be passed.
|
||||
class HackedPartialCreditQuestion(Question):
|
||||
|
||||
def execute(self, grades):
|
||||
# TODO: is this the right way to use grades? The autograder doesn't seem to use it.
|
||||
grades.assignZeroCredit()
|
||||
|
||||
points = 0
|
||||
passed = True
|
||||
for testCase, f in self.testCases:
|
||||
testResult = f(grades)
|
||||
if "points" in testCase.testDict:
|
||||
if testResult:
|
||||
points += float(testCase.testDict["points"])
|
||||
else:
|
||||
passed = passed and testResult
|
||||
|
||||
# FIXME: Below terrible hack to match q3's logic
|
||||
if int(points) == self.maxPoints and not passed:
|
||||
grades.assignZeroCredit()
|
||||
else:
|
||||
grades.addPoints(int(points))
|
||||
|
||||
|
||||
class Q6PartialCreditQuestion(Question):
|
||||
"""Fails any test which returns False, otherwise doesn't effect the grades object.
|
||||
Partial credit tests will add the required points."""
|
||||
|
||||
def execute(self, grades):
|
||||
grades.assignZeroCredit()
|
||||
|
||||
results = []
|
||||
for _, f in self.testCases:
|
||||
results.append(f(grades))
|
||||
if False in results:
|
||||
grades.assignZeroCredit()
|
||||
|
||||
|
||||
class PartialCreditQuestion(Question):
|
||||
"""Fails any test which returns False, otherwise doesn't effect the grades object.
|
||||
Partial credit tests will add the required points."""
|
||||
|
||||
def execute(self, grades):
|
||||
grades.assignZeroCredit()
|
||||
|
||||
for _, f in self.testCases:
|
||||
if not f(grades):
|
||||
grades.assignZeroCredit()
|
||||
grades.fail("Tests failed.")
|
||||
return False
|
||||
|
||||
|
||||
class NumberPassedQuestion(Question):
|
||||
"""Grade is the number of test cases passed."""
|
||||
|
||||
def execute(self, grades):
|
||||
grades.addPoints([f(grades) for _, f in self.testCases].count(True))
|
||||
|
||||
|
||||
# Template modeling a generic test case
|
||||
class TestCase(object):
|
||||
|
||||
def raiseNotDefined(self):
|
||||
print('Method not implemented: %s' % inspect.stack()[1][3])
|
||||
sys.exit(1)
|
||||
|
||||
def getPath(self):
|
||||
return self.path
|
||||
|
||||
def __init__(self, question, testDict):
|
||||
self.question = question
|
||||
self.testDict = testDict
|
||||
self.path = testDict['path']
|
||||
self.messages = []
|
||||
|
||||
def __str__(self):
|
||||
self.raiseNotDefined()
|
||||
|
||||
def execute(self, grades, moduleDict, solutionDict):
|
||||
self.raiseNotDefined()
|
||||
|
||||
def writeSolution(self, moduleDict, filePath):
|
||||
self.raiseNotDefined()
|
||||
return True
|
||||
|
||||
# Tests should call the following messages for grading
|
||||
# to ensure a uniform format for test output.
|
||||
#
|
||||
# TODO: this is hairy, but we need to fix grading.py's interface
|
||||
# to get a nice hierarchical project - question - test structure,
|
||||
# then these should be moved into Question proper.
|
||||
def testPass(self, grades):
|
||||
grades.addMessage('PASS: %s' % (self.path,))
|
||||
for line in self.messages:
|
||||
grades.addMessage(' %s' % (line,))
|
||||
return True
|
||||
|
||||
def testFail(self, grades):
|
||||
grades.addMessage('FAIL: %s' % (self.path,))
|
||||
for line in self.messages:
|
||||
grades.addMessage(' %s' % (line,))
|
||||
return False
|
||||
|
||||
# This should really be question level?
|
||||
def testPartial(self, grades, points, maxPoints):
|
||||
grades.addPoints(points)
|
||||
extraCredit = max(0, points - maxPoints)
|
||||
regularCredit = points - extraCredit
|
||||
|
||||
grades.addMessage('%s: %s (%s of %s points)' % (
|
||||
"PASS" if points >= maxPoints else "FAIL", self.path, regularCredit, maxPoints))
|
||||
if extraCredit > 0:
|
||||
grades.addMessage('EXTRA CREDIT: %s points' % (extraCredit,))
|
||||
|
||||
for line in self.messages:
|
||||
grades.addMessage(' %s' % (line,))
|
||||
|
||||
return True
|
||||
|
||||
def addMessage(self, message):
|
||||
self.messages.extend(message.split('\n'))
|
||||
86
proj2/testParser.py
Normal file
86
proj2/testParser.py
Normal file
@ -0,0 +1,86 @@
|
||||
# testParser.py
|
||||
# -------------
|
||||
# Licensing Information: You are free to use or extend these projects for
|
||||
# educational purposes provided that (1) you do not distribute or publish
|
||||
# solutions, (2) you retain this notice, and (3) you provide clear
|
||||
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
||||
#
|
||||
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
||||
# The core projects and autograders were primarily created by John DeNero
|
||||
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
||||
# Student side autograding was added by Brad Miller, Nick Hay, and
|
||||
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
||||
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class TestParser(object):
|
||||
|
||||
def __init__(self, path):
|
||||
# save the path to the test file
|
||||
self.path = path
|
||||
|
||||
def removeComments(self, rawlines):
|
||||
# remove any portion of a line following a '#' symbol
|
||||
fixed_lines = []
|
||||
for l in rawlines:
|
||||
idx = l.find('#')
|
||||
if idx == -1:
|
||||
fixed_lines.append(l)
|
||||
else:
|
||||
fixed_lines.append(l[0:idx])
|
||||
return '\n'.join(fixed_lines)
|
||||
|
||||
def parse(self):
|
||||
# read in the test case and remove comments
|
||||
test = {}
|
||||
with open(self.path) as handle:
|
||||
raw_lines = handle.read().split('\n')
|
||||
|
||||
test_text = self.removeComments(raw_lines)
|
||||
test['__raw_lines__'] = raw_lines
|
||||
test['path'] = self.path
|
||||
test['__emit__'] = []
|
||||
lines = test_text.split('\n')
|
||||
i = 0
|
||||
# read a property in each loop cycle
|
||||
while(i < len(lines)):
|
||||
# skip blank lines
|
||||
if re.match(r'\A\s*\Z', lines[i]):
|
||||
test['__emit__'].append(("raw", raw_lines[i]))
|
||||
i += 1
|
||||
continue
|
||||
m = re.match(r'\A([^"]*?):\s*"([^"]*)"\s*\Z', lines[i])
|
||||
if m:
|
||||
test[m.group(1)] = m.group(2)
|
||||
test['__emit__'].append(("oneline", m.group(1)))
|
||||
i += 1
|
||||
continue
|
||||
m = re.match(r'\A([^"]*?):\s*"""\s*\Z', lines[i])
|
||||
if m:
|
||||
msg = []
|
||||
i += 1
|
||||
while(not re.match(r'\A\s*"""\s*\Z', lines[i])):
|
||||
msg.append(raw_lines[i])
|
||||
i += 1
|
||||
test[m.group(1)] = '\n'.join(msg)
|
||||
test['__emit__'].append(("multiline", m.group(1)))
|
||||
i += 1
|
||||
continue
|
||||
print('error parsing test file: %s' % self.path)
|
||||
sys.exit(1)
|
||||
return test
|
||||
|
||||
|
||||
def emitTestDict(testDict, handle):
|
||||
for kind, data in testDict['__emit__']:
|
||||
if kind == "raw":
|
||||
handle.write(data + "\n")
|
||||
elif kind == "oneline":
|
||||
handle.write('%s: "%s"\n' % (data, testDict[data]))
|
||||
elif kind == "multiline":
|
||||
handle.write('%s: """\n%s\n"""\n' % (data, testDict[data]))
|
||||
else:
|
||||
raise Exception("Bad __emit__")
|
||||
1
proj2/test_cases/CONFIG
Normal file
1
proj2/test_cases/CONFIG
Normal file
@ -0,0 +1 @@
|
||||
order: "q1 q2 q3 q4 q5"
|
||||
2
proj2/test_cases/extra/CONFIG
Normal file
2
proj2/test_cases/extra/CONFIG
Normal file
@ -0,0 +1,2 @@
|
||||
max_points: "0"
|
||||
class: "PartialCreditQuestion"
|
||||
11
proj2/test_cases/extra/grade-agent.test
Normal file
11
proj2/test_cases/extra/grade-agent.test
Normal file
@ -0,0 +1,11 @@
|
||||
class: "EvalAgentTest"
|
||||
|
||||
agentName: "ContestAgent"
|
||||
layoutName: "contestClassic"
|
||||
maxTime: "180"
|
||||
numGames: "5"
|
||||
|
||||
scoreThresholds: "2500 2900"
|
||||
|
||||
randomSeed: "0"
|
||||
ghosts: "[DirectionalGhost(1), DirectionalGhost(2), DirectionalGhost(3)]"
|
||||
2
proj2/test_cases/q1/CONFIG
Normal file
2
proj2/test_cases/q1/CONFIG
Normal file
@ -0,0 +1,2 @@
|
||||
max_points: "4"
|
||||
class: "PartialCreditQuestion"
|
||||
2
proj2/test_cases/q1/grade-agent.solution
Normal file
2
proj2/test_cases/q1/grade-agent.solution
Normal file
@ -0,0 +1,2 @@
|
||||
# This is the solution file for test_cases/q1/grade-agent.test.
|
||||
# File intentionally blank.
|
||||
18
proj2/test_cases/q1/grade-agent.test
Normal file
18
proj2/test_cases/q1/grade-agent.test
Normal file
@ -0,0 +1,18 @@
|
||||
class: "EvalAgentTest"
|
||||
|
||||
agentName: "ReflexAgent"
|
||||
layoutName: "openClassic"
|
||||
maxTime: "120"
|
||||
numGames: "10"
|
||||
|
||||
|
||||
nonTimeoutMinimum: "10"
|
||||
|
||||
scoreThresholds: "500 1000"
|
||||
|
||||
winsMinimum: "1"
|
||||
winsThresholds: "5 10"
|
||||
|
||||
|
||||
randomSeed: "0"
|
||||
ghosts: "[RandomGhost(1)]"
|
||||
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/0-eval-function-lose-states-1.test.
|
||||
action: "Left"
|
||||
generated: "lose1 lose2 root"
|
||||
30
proj2/test_cases/q2/0-eval-function-lose-states-1.test
Normal file
30
proj2/test_cases/q2/0-eval-function-lose-states-1.test
Normal file
@ -0,0 +1,30 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "2"
|
||||
|
||||
diagram: """
|
||||
root
|
||||
/ \
|
||||
lose1 lose2
|
||||
1 0
|
||||
|
||||
If your algorithm is returning a different
|
||||
action, make sure you are calling the
|
||||
evaluation function on losing states.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "root"
|
||||
win_states: ""
|
||||
lose_states: "lose1 lose2"
|
||||
|
||||
successors: """
|
||||
root Left lose1
|
||||
root Right lose2
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
lose1 1.0
|
||||
lose2 0.0
|
||||
"""
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/0-eval-function-lose-states-2.test.
|
||||
action: "Right"
|
||||
generated: "lose1 lose2 root"
|
||||
30
proj2/test_cases/q2/0-eval-function-lose-states-2.test
Normal file
30
proj2/test_cases/q2/0-eval-function-lose-states-2.test
Normal file
@ -0,0 +1,30 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "2"
|
||||
|
||||
diagram: """
|
||||
root
|
||||
/ \
|
||||
lose1 lose2
|
||||
0 1
|
||||
|
||||
If your algorithm is returning a different
|
||||
action, make sure you are calling the
|
||||
evaluation function on losing states.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "root"
|
||||
win_states: ""
|
||||
lose_states: "lose1 lose2"
|
||||
|
||||
successors: """
|
||||
root Left lose1
|
||||
root Right lose2
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
lose1 0.0
|
||||
lose2 1.0
|
||||
"""
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/0-eval-function-win-states-1.test.
|
||||
action: "Left"
|
||||
generated: "root win1 win2"
|
||||
30
proj2/test_cases/q2/0-eval-function-win-states-1.test
Normal file
30
proj2/test_cases/q2/0-eval-function-win-states-1.test
Normal file
@ -0,0 +1,30 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "2"
|
||||
|
||||
diagram: """
|
||||
root
|
||||
/ \
|
||||
win1 win2
|
||||
1 0
|
||||
|
||||
If your algorithm is returning a different
|
||||
action, make sure you are calling the
|
||||
evaluation function on winning states.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "root"
|
||||
win_states: "win1 win2"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
root Left win1
|
||||
root Right win2
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
win1 1.0
|
||||
win2 0.0
|
||||
"""
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/0-eval-function-win-states-2.test.
|
||||
action: "Right"
|
||||
generated: "root win1 win2"
|
||||
30
proj2/test_cases/q2/0-eval-function-win-states-2.test
Normal file
30
proj2/test_cases/q2/0-eval-function-win-states-2.test
Normal file
@ -0,0 +1,30 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "2"
|
||||
|
||||
diagram: """
|
||||
root
|
||||
/ \
|
||||
win1 win2
|
||||
0 1
|
||||
|
||||
If your algorithm is returning a different
|
||||
action, make sure you are calling the
|
||||
evaluation function on winning states.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "root"
|
||||
win_states: "win1 win2"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
root Left win1
|
||||
root Right win2
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
win1 0.0
|
||||
win2 1.0
|
||||
"""
|
||||
|
||||
3
proj2/test_cases/q2/0-lecture-6-tree.solution
Normal file
3
proj2/test_cases/q2/0-lecture-6-tree.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/0-lecture-6-tree.test.
|
||||
action: "Center"
|
||||
generated: "A B C D E F G H I max min1 min2 min3"
|
||||
50
proj2/test_cases/q2/0-lecture-6-tree.test
Normal file
50
proj2/test_cases/q2/0-lecture-6-tree.test
Normal file
@ -0,0 +1,50 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "2"
|
||||
|
||||
# Tree from lecture 6 slides
|
||||
diagram: """
|
||||
max
|
||||
/-/ | \--\
|
||||
/ | \
|
||||
/ | \
|
||||
min1 min2 min3
|
||||
/|\ /|\ /|\
|
||||
/ | \ / | \ / | \
|
||||
A B C D E F G H I
|
||||
3 12 8 5 4 6 14 1 11
|
||||
"""
|
||||
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "max"
|
||||
win_states: "A B C D E F G H I"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
max Left min1
|
||||
max Center min2
|
||||
max Right min3
|
||||
min1 Left A
|
||||
min1 Center B
|
||||
min1 Right C
|
||||
min2 Left D
|
||||
min2 Center E
|
||||
min2 Right F
|
||||
min3 Left G
|
||||
min3 Center H
|
||||
min3 Right I
|
||||
"""
|
||||
|
||||
|
||||
evaluation: """
|
||||
A 3.0
|
||||
B 12.0
|
||||
C 8.0
|
||||
D 5.0
|
||||
E 4.0
|
||||
F 6.0
|
||||
G 14.0
|
||||
H 1.0
|
||||
I 11.0
|
||||
"""
|
||||
3
proj2/test_cases/q2/0-small-tree.solution
Normal file
3
proj2/test_cases/q2/0-small-tree.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/0-small-tree.test.
|
||||
action: "pacLeft"
|
||||
generated: "A B C D deeper minLeft minRight root"
|
||||
36
proj2/test_cases/q2/0-small-tree.test
Normal file
36
proj2/test_cases/q2/0-small-tree.test
Normal file
@ -0,0 +1,36 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "3"
|
||||
|
||||
diagram: """
|
||||
root
|
||||
/ \
|
||||
minLeft minRight
|
||||
/ \ / \
|
||||
A B C deeper
|
||||
4 3 2 |
|
||||
D
|
||||
1000
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "root"
|
||||
win_states: "A C"
|
||||
lose_states: "B D"
|
||||
|
||||
successors: """
|
||||
root pacLeft minLeft
|
||||
root pacRight minRight
|
||||
minLeft gLeft A
|
||||
minLeft gRight B
|
||||
minRight gLeft C
|
||||
minRight gRight deeper
|
||||
deeper pacLeft D
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
A 4.0
|
||||
B 3.0
|
||||
C 2.0
|
||||
D 1000.0
|
||||
"""
|
||||
3
proj2/test_cases/q2/1-1-minmax.solution
Normal file
3
proj2/test_cases/q2/1-1-minmax.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/1-1-minmax.test.
|
||||
action: "Left"
|
||||
generated: "a b1 b2 c1 c2 cx d1 d2 d3 d4 dx"
|
||||
47
proj2/test_cases/q2/1-1-minmax.test
Normal file
47
proj2/test_cases/q2/1-1-minmax.test
Normal file
@ -0,0 +1,47 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "3"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
/ \ |
|
||||
c1 c2 cx
|
||||
/ \ / \ |
|
||||
d1 d2 d3 d4 dx
|
||||
-3 -9 10 6 -3.01
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b1 is -3.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Left c1
|
||||
b1 Right c2
|
||||
b2 Down cx
|
||||
c1 Left d1
|
||||
c1 Right d2
|
||||
c2 Left d3
|
||||
c2 Right d4
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
d1 -3.0
|
||||
d2 -9.0
|
||||
d3 10.0
|
||||
d4 6.0
|
||||
dx -3.01
|
||||
"""
|
||||
3
proj2/test_cases/q2/1-2-minmax.solution
Normal file
3
proj2/test_cases/q2/1-2-minmax.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/1-2-minmax.test.
|
||||
action: "Right"
|
||||
generated: "a b1 b2 c1 c2 cx d1 d2 d3 d4 dx"
|
||||
47
proj2/test_cases/q2/1-2-minmax.test
Normal file
47
proj2/test_cases/q2/1-2-minmax.test
Normal file
@ -0,0 +1,47 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "3"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
/ \ |
|
||||
c1 c2 cx
|
||||
/ \ / \ |
|
||||
d1 d2 d3 d4 dx
|
||||
-3 -9 10 6 -2.99
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b1 is -3.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Left c1
|
||||
b1 Right c2
|
||||
b2 Down cx
|
||||
c1 Left d1
|
||||
c1 Right d2
|
||||
c2 Left d3
|
||||
c2 Right d4
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
d1 -3.0
|
||||
d2 -9.0
|
||||
d3 10.0
|
||||
d4 6.0
|
||||
dx -2.99
|
||||
"""
|
||||
3
proj2/test_cases/q2/1-3-minmax.solution
Normal file
3
proj2/test_cases/q2/1-3-minmax.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/1-3-minmax.test.
|
||||
action: "Left"
|
||||
generated: "a b1 b2 c3 c4 cx d5 d6 d7 d8 dx"
|
||||
47
proj2/test_cases/q2/1-3-minmax.test
Normal file
47
proj2/test_cases/q2/1-3-minmax.test
Normal file
@ -0,0 +1,47 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "3"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
| / \
|
||||
cx c3 c4
|
||||
| / \ / \
|
||||
dx d5 d6 d7 d8
|
||||
4.01 4 -7 0 5
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b2 is 4.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 d5 d6 d7 d8 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Down cx
|
||||
b2 Left c3
|
||||
b2 Right c4
|
||||
c3 Left d5
|
||||
c3 Right d6
|
||||
c4 Left d7
|
||||
c4 Right d8
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
d5 4.0
|
||||
d6 -7.0
|
||||
d7 0.0
|
||||
d8 5.0
|
||||
dx 4.01
|
||||
"""
|
||||
3
proj2/test_cases/q2/1-4-minmax.solution
Normal file
3
proj2/test_cases/q2/1-4-minmax.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/1-4-minmax.test.
|
||||
action: "Right"
|
||||
generated: "a b1 b2 c3 c4 cx d5 d6 d7 d8 dx"
|
||||
47
proj2/test_cases/q2/1-4-minmax.test
Normal file
47
proj2/test_cases/q2/1-4-minmax.test
Normal file
@ -0,0 +1,47 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "3"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
| / \
|
||||
cx c3 c4
|
||||
| / \ / \
|
||||
dx d5 d6 d7 d8
|
||||
3.99 4 -7 0 5
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b2 is 4.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 d5 d6 d7 d8 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Down cx
|
||||
b2 Left c3
|
||||
b2 Right c4
|
||||
c3 Left d5
|
||||
c3 Right d6
|
||||
c4 Left d7
|
||||
c4 Right d8
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
d5 4.0
|
||||
d6 -7.0
|
||||
d7 0.0
|
||||
d8 5.0
|
||||
dx 3.99
|
||||
"""
|
||||
3
proj2/test_cases/q2/1-5-minmax.solution
Normal file
3
proj2/test_cases/q2/1-5-minmax.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/1-5-minmax.test.
|
||||
action: "Right"
|
||||
generated: "A B C D E F G H Z a b1 b2 c1 c2 cx d1 d2 d3 d4 dx"
|
||||
75
proj2/test_cases/q2/1-5-minmax.test
Normal file
75
proj2/test_cases/q2/1-5-minmax.test
Normal file
@ -0,0 +1,75 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "4"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
/ \ |
|
||||
c1 c2 cx
|
||||
/ \ / \ |
|
||||
d1 d2 d3 d4 dx
|
||||
/ \ / \ / \ / \ |
|
||||
A B C D E F G H Z
|
||||
-3 13 5 9 10 3 -6 8 3.01
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
d - min
|
||||
|
||||
Note the minimax value of b1 is 3.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "A B C D E F G H I J K L M N O P Z"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Left c1
|
||||
b1 Right c2
|
||||
b2 Down cx
|
||||
c1 Left d1
|
||||
c1 Right d2
|
||||
c2 Left d3
|
||||
c2 Right d4
|
||||
c3 Left d5
|
||||
c3 Right d6
|
||||
c4 Left d7
|
||||
c4 Right d8
|
||||
cx Down dx
|
||||
d1 Left A
|
||||
d1 Right B
|
||||
d2 Left C
|
||||
d2 Right D
|
||||
d3 Left E
|
||||
d3 Right F
|
||||
d4 Left G
|
||||
d4 Right H
|
||||
d5 Left I
|
||||
d5 Right J
|
||||
d6 Left K
|
||||
d6 Right L
|
||||
d7 Left M
|
||||
d7 Right N
|
||||
d8 Left O
|
||||
d8 Right P
|
||||
dx Down Z
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
A -3.0
|
||||
B 13.0
|
||||
C 5.0
|
||||
D 9.0
|
||||
E 10.0
|
||||
F 3.0
|
||||
G -6.0
|
||||
H 8.0
|
||||
Z 3.01
|
||||
"""
|
||||
3
proj2/test_cases/q2/1-6-minmax.solution
Normal file
3
proj2/test_cases/q2/1-6-minmax.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/1-6-minmax.test.
|
||||
action: "Left"
|
||||
generated: "A B C D E F G H Z a b1 b2 c1 c2 cx d1 d2 d3 d4 dx"
|
||||
75
proj2/test_cases/q2/1-6-minmax.test
Normal file
75
proj2/test_cases/q2/1-6-minmax.test
Normal file
@ -0,0 +1,75 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "4"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
/ \ |
|
||||
c1 c2 cx
|
||||
/ \ / \ |
|
||||
d1 d2 d3 d4 dx
|
||||
/ \ / \ / \ / \ |
|
||||
A B C D E F G H Z
|
||||
-3 13 5 9 10 3 -6 8 2.99
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
d - min
|
||||
|
||||
Note the minimax value of b1 is 3.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "A B C D E F G H I J K L M N O P Z"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Left c1
|
||||
b1 Right c2
|
||||
b2 Down cx
|
||||
c1 Left d1
|
||||
c1 Right d2
|
||||
c2 Left d3
|
||||
c2 Right d4
|
||||
c3 Left d5
|
||||
c3 Right d6
|
||||
c4 Left d7
|
||||
c4 Right d8
|
||||
cx Down dx
|
||||
d1 Left A
|
||||
d1 Right B
|
||||
d2 Left C
|
||||
d2 Right D
|
||||
d3 Left E
|
||||
d3 Right F
|
||||
d4 Left G
|
||||
d4 Right H
|
||||
d5 Left I
|
||||
d5 Right J
|
||||
d6 Left K
|
||||
d6 Right L
|
||||
d7 Left M
|
||||
d7 Right N
|
||||
d8 Left O
|
||||
d8 Right P
|
||||
dx Down Z
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
A -3.0
|
||||
B 13.0
|
||||
C 5.0
|
||||
D 9.0
|
||||
E 10.0
|
||||
F 3.0
|
||||
G -6.0
|
||||
H 8.0
|
||||
Z 2.99
|
||||
"""
|
||||
3
proj2/test_cases/q2/1-7-minmax.solution
Normal file
3
proj2/test_cases/q2/1-7-minmax.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/1-7-minmax.test.
|
||||
action: "Left"
|
||||
generated: "I J K L M N O P Z a b1 b2 c3 c4 cx d5 d6 d7 d8 dx"
|
||||
75
proj2/test_cases/q2/1-7-minmax.test
Normal file
75
proj2/test_cases/q2/1-7-minmax.test
Normal file
@ -0,0 +1,75 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "4"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
| / \
|
||||
cx c3 c4
|
||||
| / \ / \
|
||||
dx d5 d6 d7 d8
|
||||
| / \ / \ / \ / \
|
||||
Z I J K L M N O P
|
||||
-1.99 -1 -9 4 7 2 5 -3 -2
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - min
|
||||
d - max
|
||||
|
||||
Note that the minimax value of b2 is -2
|
||||
"""
|
||||
num_agents: "3"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "A B C D E F G H I J K L M N O P Z"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Down cx
|
||||
b2 Left c3
|
||||
b2 Right c4
|
||||
c1 Left d1
|
||||
c1 Right d2
|
||||
c2 Left d3
|
||||
c2 Right d4
|
||||
c3 Left d5
|
||||
c3 Right d6
|
||||
c4 Left d7
|
||||
c4 Right d8
|
||||
cx Down dx
|
||||
d1 Left A
|
||||
d1 Right B
|
||||
d2 Left C
|
||||
d2 Right D
|
||||
d3 Left E
|
||||
d3 Right F
|
||||
d4 Left G
|
||||
d4 Right H
|
||||
d5 Left I
|
||||
d5 Right J
|
||||
d6 Left K
|
||||
d6 Right L
|
||||
d7 Left M
|
||||
d7 Right N
|
||||
d8 Left O
|
||||
d8 Right P
|
||||
dx Down Z
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
I -1.0
|
||||
J -9.0
|
||||
K 4.0
|
||||
L 7.0
|
||||
M 2.0
|
||||
N 5.0
|
||||
O -3.0
|
||||
P -2.0
|
||||
Z -1.99
|
||||
"""
|
||||
3
proj2/test_cases/q2/1-8-minmax.solution
Normal file
3
proj2/test_cases/q2/1-8-minmax.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/1-8-minmax.test.
|
||||
action: "Right"
|
||||
generated: "I J K L M N O P Z a b1 b2 c3 c4 cx d5 d6 d7 d8 dx"
|
||||
75
proj2/test_cases/q2/1-8-minmax.test
Normal file
75
proj2/test_cases/q2/1-8-minmax.test
Normal file
@ -0,0 +1,75 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "4"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
| / \
|
||||
cx c3 c4
|
||||
| / \ / \
|
||||
dx d5 d6 d7 d8
|
||||
| / \ / \ / \ / \
|
||||
Z I J K L M N O P
|
||||
-2.01 -1 -9 4 7 2 5 -3 -2
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - min
|
||||
d - max
|
||||
|
||||
Note that the minimax value of b2 is -2.01
|
||||
"""
|
||||
num_agents: "3"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "A B C D E F G H I J K L M N O P Z"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Down cx
|
||||
b2 Left c3
|
||||
b2 Right c4
|
||||
c1 Left d1
|
||||
c1 Right d2
|
||||
c2 Left d3
|
||||
c2 Right d4
|
||||
c3 Left d5
|
||||
c3 Right d6
|
||||
c4 Left d7
|
||||
c4 Right d8
|
||||
cx Down dx
|
||||
d1 Left A
|
||||
d1 Right B
|
||||
d2 Left C
|
||||
d2 Right D
|
||||
d3 Left E
|
||||
d3 Right F
|
||||
d4 Left G
|
||||
d4 Right H
|
||||
d5 Left I
|
||||
d5 Right J
|
||||
d6 Left K
|
||||
d6 Right L
|
||||
d7 Left M
|
||||
d7 Right N
|
||||
d8 Left O
|
||||
d8 Right P
|
||||
dx Down Z
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
I -1.0
|
||||
J -9.0
|
||||
K 4.0
|
||||
L 7.0
|
||||
M 2.0
|
||||
N 5.0
|
||||
O -3.0
|
||||
P -2.0
|
||||
Z -2.01
|
||||
"""
|
||||
3
proj2/test_cases/q2/2-1a-vary-depth.solution
Normal file
3
proj2/test_cases/q2/2-1a-vary-depth.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/2-1a-vary-depth.test.
|
||||
action: "Left"
|
||||
generated: "a b1 b2 c1 c2 cx"
|
||||
52
proj2/test_cases/q2/2-1a-vary-depth.test
Normal file
52
proj2/test_cases/q2/2-1a-vary-depth.test
Normal file
@ -0,0 +1,52 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "1"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
/ \ |
|
||||
-4 c1 c2 9 cx -4.01
|
||||
/ \ / \ |
|
||||
d1 d2 d3 d4 dx
|
||||
-3 -9 10 6 -4.01
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b1 is -3, but the depth=1 limited value is -4.
|
||||
The values next to c1, c2, and cx are the values of the evaluation function, not
|
||||
necessarily the correct minimax backup.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Left c1
|
||||
b1 Right c2
|
||||
b2 Down cx
|
||||
c1 Left d1
|
||||
c1 Right d2
|
||||
c2 Left d3
|
||||
c2 Right d4
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
c1 -4.0
|
||||
c2 9.0
|
||||
cx -4.01
|
||||
d1 -3.0
|
||||
d2 -9.0
|
||||
d3 10.0
|
||||
d4 6.0
|
||||
dx -4.01
|
||||
"""
|
||||
3
proj2/test_cases/q2/2-1b-vary-depth.solution
Normal file
3
proj2/test_cases/q2/2-1b-vary-depth.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/2-1b-vary-depth.test.
|
||||
action: "Left"
|
||||
generated: "a b1 b2 c1 c2 cx d1 d2 d3 d4 dx"
|
||||
52
proj2/test_cases/q2/2-1b-vary-depth.test
Normal file
52
proj2/test_cases/q2/2-1b-vary-depth.test
Normal file
@ -0,0 +1,52 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "2"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
/ \ |
|
||||
-4 c1 c2 9 cx -4.01
|
||||
/ \ / \ |
|
||||
d1 d2 d3 d4 dx
|
||||
-3 -9 10 6 -4.01
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b1 is -3, but the depth=1 limited value is -4.
|
||||
The values next to c1, c2, and cx are the values of the evaluation function, not
|
||||
necessarily the correct minimax backup.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Left c1
|
||||
b1 Right c2
|
||||
b2 Down cx
|
||||
c1 Left d1
|
||||
c1 Right d2
|
||||
c2 Left d3
|
||||
c2 Right d4
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
c1 -4.0
|
||||
c2 9.0
|
||||
cx -4.01
|
||||
d1 -3.0
|
||||
d2 -9.0
|
||||
d3 10.0
|
||||
d4 6.0
|
||||
dx -4.01
|
||||
"""
|
||||
3
proj2/test_cases/q2/2-2a-vary-depth.solution
Normal file
3
proj2/test_cases/q2/2-2a-vary-depth.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/2-2a-vary-depth.test.
|
||||
action: "Right"
|
||||
generated: "a b1 b2 c1 c2 cx"
|
||||
52
proj2/test_cases/q2/2-2a-vary-depth.test
Normal file
52
proj2/test_cases/q2/2-2a-vary-depth.test
Normal file
@ -0,0 +1,52 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "1"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
/ \ |
|
||||
-4 c1 c2 9 cx -3.99
|
||||
/ \ / \ |
|
||||
d1 d2 d3 d4 dx
|
||||
-3 -9 10 6 -3.99
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b1 is -3, but the depth=1 limited value is -4.
|
||||
The values next to c1, c2, and cx are the values of the evaluation function, not
|
||||
necessarily the correct minimax backup.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Left c1
|
||||
b1 Right c2
|
||||
b2 Down cx
|
||||
c1 Left d1
|
||||
c1 Right d2
|
||||
c2 Left d3
|
||||
c2 Right d4
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
c1 -4.0
|
||||
c2 9.0
|
||||
cx -3.99
|
||||
d1 -3.0
|
||||
d2 -9.0
|
||||
d3 10.0
|
||||
d4 6.0
|
||||
dx -3.99
|
||||
"""
|
||||
3
proj2/test_cases/q2/2-2b-vary-depth.solution
Normal file
3
proj2/test_cases/q2/2-2b-vary-depth.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/2-2b-vary-depth.test.
|
||||
action: "Left"
|
||||
generated: "a b1 b2 c1 c2 cx d1 d2 d3 d4 dx"
|
||||
52
proj2/test_cases/q2/2-2b-vary-depth.test
Normal file
52
proj2/test_cases/q2/2-2b-vary-depth.test
Normal file
@ -0,0 +1,52 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "2"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
/ \ |
|
||||
-4 c1 c2 9 cx -3.99
|
||||
/ \ / \ |
|
||||
d1 d2 d3 d4 dx
|
||||
-3 -9 10 6 -3.99
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b1 is -3, but the depth=1 limited value is -4.
|
||||
The values next to c1, c2, and cx are the values of the evaluation function, not
|
||||
necessarily the correct minimax backup.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Left c1
|
||||
b1 Right c2
|
||||
b2 Down cx
|
||||
c1 Left d1
|
||||
c1 Right d2
|
||||
c2 Left d3
|
||||
c2 Right d4
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
c1 -4.0
|
||||
c2 9.0
|
||||
cx -3.99
|
||||
d1 -3.0
|
||||
d2 -9.0
|
||||
d3 10.0
|
||||
d4 6.0
|
||||
dx -3.99
|
||||
"""
|
||||
3
proj2/test_cases/q2/2-3a-vary-depth.solution
Normal file
3
proj2/test_cases/q2/2-3a-vary-depth.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/2-3a-vary-depth.test.
|
||||
action: "Left"
|
||||
generated: "a b1 b2 c3 c4 cx"
|
||||
52
proj2/test_cases/q2/2-3a-vary-depth.test
Normal file
52
proj2/test_cases/q2/2-3a-vary-depth.test
Normal file
@ -0,0 +1,52 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "1"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
| / \
|
||||
5.01 cx 8 c3 c4 5
|
||||
| / \ / \
|
||||
dx d5 d6 d7 d8
|
||||
5.01 4 -7 0 5
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b1 is 4, but the depth=1 limited value is 5.
|
||||
The values next to c3, c4, and cx are the values of the evaluation function, not
|
||||
necessarily the correct minimax backup.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 d5 d6 d7 d8 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Down cx
|
||||
b2 Left c3
|
||||
b2 Right c4
|
||||
c3 Left d5
|
||||
c3 Right d6
|
||||
c4 Left d7
|
||||
c4 Right d8
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
c3 8.0
|
||||
c4 5.0
|
||||
cx 5.01
|
||||
d5 4.0
|
||||
d6 -7.0
|
||||
d7 0.0
|
||||
d8 5.0
|
||||
dx 5.01
|
||||
"""
|
||||
3
proj2/test_cases/q2/2-3b-vary-depth.solution
Normal file
3
proj2/test_cases/q2/2-3b-vary-depth.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/2-3b-vary-depth.test.
|
||||
action: "Left"
|
||||
generated: "a b1 b2 c3 c4 cx d5 d6 d7 d8 dx"
|
||||
52
proj2/test_cases/q2/2-3b-vary-depth.test
Normal file
52
proj2/test_cases/q2/2-3b-vary-depth.test
Normal file
@ -0,0 +1,52 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "2"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
| / \
|
||||
5.01 cx 8 c3 c4 5
|
||||
| / \ / \
|
||||
dx d5 d6 d7 d8
|
||||
5.01 4 -7 0 5
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b1 is 4, but the depth=1 limited value is 5.
|
||||
The values next to c3, c4, and cx are the values of the evaluation function, not
|
||||
necessarily the correct minimax backup.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 d5 d6 d7 d8 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Down cx
|
||||
b2 Left c3
|
||||
b2 Right c4
|
||||
c3 Left d5
|
||||
c3 Right d6
|
||||
c4 Left d7
|
||||
c4 Right d8
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
c3 8.0
|
||||
c4 5.0
|
||||
cx 5.01
|
||||
d5 4.0
|
||||
d6 -7.0
|
||||
d7 0.0
|
||||
d8 5.0
|
||||
dx 5.01
|
||||
"""
|
||||
3
proj2/test_cases/q2/2-4a-vary-depth.solution
Normal file
3
proj2/test_cases/q2/2-4a-vary-depth.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/2-4a-vary-depth.test.
|
||||
action: "Right"
|
||||
generated: "a b1 b2 c3 c4 cx"
|
||||
52
proj2/test_cases/q2/2-4a-vary-depth.test
Normal file
52
proj2/test_cases/q2/2-4a-vary-depth.test
Normal file
@ -0,0 +1,52 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "1"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
| / \
|
||||
4.99 cx 8 c3 c4 5
|
||||
| / \ / \
|
||||
dx d5 d6 d7 d8
|
||||
4.99 4 -7 0 5
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b1 is 4, but the depth=1 limited value is 5.
|
||||
The values next to c3, c4, and cx are the values of the evaluation function, not
|
||||
necessarily the correct minimax backup.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 d5 d6 d7 d8 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Down cx
|
||||
b2 Left c3
|
||||
b2 Right c4
|
||||
c3 Left d5
|
||||
c3 Right d6
|
||||
c4 Left d7
|
||||
c4 Right d8
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
c3 8.0
|
||||
c4 5.0
|
||||
cx 4.99
|
||||
d5 4.0
|
||||
d6 -7.0
|
||||
d7 0.0
|
||||
d8 5.0
|
||||
dx 4.99
|
||||
"""
|
||||
3
proj2/test_cases/q2/2-4b-vary-depth.solution
Normal file
3
proj2/test_cases/q2/2-4b-vary-depth.solution
Normal file
@ -0,0 +1,3 @@
|
||||
# This is the solution file for test_cases/q2/2-4b-vary-depth.test.
|
||||
action: "Left"
|
||||
generated: "a b1 b2 c3 c4 cx d5 d6 d7 d8 dx"
|
||||
52
proj2/test_cases/q2/2-4b-vary-depth.test
Normal file
52
proj2/test_cases/q2/2-4b-vary-depth.test
Normal file
@ -0,0 +1,52 @@
|
||||
class: "GraphGameTreeTest"
|
||||
alg: "MinimaxAgent"
|
||||
depth: "2"
|
||||
|
||||
diagram: """
|
||||
/-----a------\
|
||||
/ \
|
||||
/ \
|
||||
b1 b2
|
||||
| / \
|
||||
4.99 cx 8 c3 c4 5
|
||||
| / \ / \
|
||||
dx d5 d6 d7 d8
|
||||
4.99 4 -7 0 5
|
||||
|
||||
a - max
|
||||
b - min
|
||||
c - max
|
||||
|
||||
Note that the minimax value of b1 is 4, but the depth=1 limited value is 5.
|
||||
The values next to c3, c4, and cx are the values of the evaluation function, not
|
||||
necessarily the correct minimax backup.
|
||||
"""
|
||||
num_agents: "2"
|
||||
|
||||
start_state: "a"
|
||||
win_states: "d1 d2 d3 d4 d5 d6 d7 d8 dx"
|
||||
lose_states: ""
|
||||
|
||||
successors: """
|
||||
a Left b1
|
||||
a Right b2
|
||||
b1 Down cx
|
||||
b2 Left c3
|
||||
b2 Right c4
|
||||
c3 Left d5
|
||||
c3 Right d6
|
||||
c4 Left d7
|
||||
c4 Right d8
|
||||
cx Down dx
|
||||
"""
|
||||
|
||||
evaluation: """
|
||||
c3 8.0
|
||||
c4 5.0
|
||||
cx 4.99
|
||||
d5 4.0
|
||||
d6 -7.0
|
||||
d7 0.0
|
||||
d8 5.0
|
||||
dx 4.99
|
||||
"""
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user