Skip to content

Commit 2aabc25

Browse files
Made the capture timeout options settable in the CLI.
1 parent 13da5b8 commit 2aabc25

File tree

8 files changed

+236
-21
lines changed

8 files changed

+236
-21
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ __pycache__
77
/html/
88
/*.gif
99
/._site/
10+
/build/

pacai/agents/capture/timeout.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import random
2+
import time
3+
4+
from pacai.agents.capture.capture import CaptureAgent
5+
6+
DEFAULT_WAIT_DURATION_SECS = 0
7+
8+
class TimeoutAgent(CaptureAgent):
9+
"""
10+
An agent that always waits a specified duration before making a random move.
11+
"""
12+
13+
# The duration to wait.
14+
# Testers may edit this directly (and reset() when done).
15+
waitMoveDurationSecs = DEFAULT_WAIT_DURATION_SECS
16+
waitInitDurationSecs = DEFAULT_WAIT_DURATION_SECS
17+
18+
def reset():
19+
TimeoutAgent.waitMoveDurationSecs = DEFAULT_WAIT_DURATION_SECS
20+
TimeoutAgent.waitInitDurationSecs = DEFAULT_WAIT_DURATION_SECS
21+
22+
def __init__(self, index, **kwargs):
23+
super().__init__(index, **kwargs)
24+
25+
def registerInitialState(self, gameState):
26+
# Wait first.
27+
time.sleep(TimeoutAgent.waitInitDurationSecs)
28+
29+
super().registerInitialState(gameState)
30+
31+
def chooseAction(self, gameState):
32+
# Wait first.
33+
time.sleep(TimeoutAgent.waitMoveDurationSecs)
34+
35+
# Take a random action.
36+
actions = gameState.getLegalActions(self.index)
37+
return random.choice(actions)

pacai/bin/capture.py

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,25 @@
3838

3939
SCARED_TIME = 40
4040

41+
# The number of moves shared between all agents.
42+
DEFAULT_MAX_MOVES = 1200
43+
44+
# Total time allowed for an agent,
45+
# move limits generally stop this from happening.
46+
DEFAULT_MAX_TOTAL_AGENT_TIME_SECS = 900
47+
48+
# Time allowed for registerInitialState().
49+
DEFAULT_MAX_STARTUP_TIME_SECS = 15
50+
51+
# Some number of warnings will cause a forfeit.
52+
DEFAULT_MOVE_WARNING_TIME_SECS = 1
53+
54+
# Any violation of this is an instant forfeit.
55+
DEFAULT_MOVE_TIMEOUT_TIME_SECS = 3
56+
57+
# Next violation causes a forfeit.
58+
DEFAULT_MAX_MOVE_WARNINGS = 2
59+
4160
class CaptureGameState(AbstractGameState):
4261
"""
4362
A game state specific to capture.
@@ -246,7 +265,13 @@ class CaptureRules:
246265
and how the game starts and ends.
247266
"""
248267

249-
def newGame(self, layout, agents, display, length, catchExceptions):
268+
def newGame(self, layout, agents, display, length, catchExceptions,
269+
maxTotalAgentTimeSecs = DEFAULT_MAX_TOTAL_AGENT_TIME_SECS,
270+
maxStartupTimeSecs = DEFAULT_MAX_STARTUP_TIME_SECS,
271+
moveWarningTimeSecs = DEFAULT_MOVE_WARNING_TIME_SECS,
272+
moveTimeoutTimeSecs = DEFAULT_MOVE_TIMEOUT_TIME_SECS,
273+
maxMoveWarnings = DEFAULT_MAX_MOVE_WARNINGS,
274+
**kwargs):
250275
initState = CaptureGameState(layout, length)
251276
starter = random.randint(0, 1)
252277
logging.info('%s team starts' % ['Red', 'Blue'][starter])
@@ -258,6 +283,12 @@ def newGame(self, layout, agents, display, length, catchExceptions):
258283
self._totalBlueFood = initState.getBlueFood().count()
259284
self._totalRedFood = initState.getRedFood().count()
260285

286+
self._maxTotalAgentTimeSecs = maxTotalAgentTimeSecs
287+
self._maxStartupTimeSecs = maxStartupTimeSecs
288+
self._moveWarningTimeSecs = moveWarningTimeSecs
289+
self._moveTimeoutTimeSecs = moveTimeoutTimeSecs
290+
self._maxMoveWarnings = maxMoveWarnings
291+
261292
return game
262293

263294
def process(self, state, game):
@@ -313,20 +344,20 @@ def agentCrash(self, game, agentIndex):
313344
logging.error("Blue agent crashed.")
314345
game.state.setScore(1)
315346

316-
def getMaxTotalTime(self, agentIndex):
317-
return 900 # Move limits should prevent this from ever happening
347+
def getMaxTotalAgentTime(self, agentIndex):
348+
return self._maxTotalAgentTimeSecs
318349

319350
def getMaxStartupTime(self, agentIndex):
320-
return 15 # 15 seconds for registerInitialState
351+
return self._maxStartupTimeSecs
321352

322353
def getMoveWarningTime(self, agentIndex):
323-
return 1 # One second per move
354+
return self._moveWarningTimeSecs
324355

325356
def getMoveTimeout(self, agentIndex):
326-
return 3 # Three seconds results in instant forfeit
357+
return self._moveTimeoutTimeSecs
327358

328359
def getMaxTimeWarnings(self, agentIndex):
329-
return 2 # Third violation loses the game
360+
return self._maxMoveWarnings
330361

331362
class AgentRules:
332363
"""
@@ -536,14 +567,34 @@ def readCommand(argv):
536567
help = 'make agent 3 (second blue player) a keyboard agent (default: %(default)s)')
537568

538569
parser.add_argument('--max-moves', dest = 'maxMoves',
539-
action = 'store', type = int, default = 1200,
540-
help = 'set maximum number of moves in a game (default: %(default)s)')
570+
action = 'store', type = int, default = DEFAULT_MAX_MOVES,
571+
help = 'set maximum number of moves between all agents in a game (default: %(default)s)')
541572

542573
parser.add_argument('--red-args', dest = 'redArgs',
543574
action = 'store', type = str, default = None,
544575
help = 'comma separated arguments to be passed to red team (e.g. \'opt1=val1,opt2\') '
545576
+ '(default: %(default)s)')
546577

578+
parser.add_argument('--max-total-agent-time', dest = 'maxTotalAgentTimeSecs',
579+
action = 'store', type = float, default = DEFAULT_MAX_TOTAL_AGENT_TIME_SECS,
580+
help = 'set maximum number of seconds a game can run (default: %(default)s)')
581+
582+
parser.add_argument('--max-startup-time', dest = 'maxStartupTimeSecs',
583+
action = 'store', type = float, default = DEFAULT_MAX_STARTUP_TIME_SECS,
584+
help = 'set maximum number of seconds allowed for registerInitialState() (default: %(default)s)')
585+
586+
parser.add_argument('--move-warning-time', dest = 'moveWarningTimeSecs',
587+
action = 'store', type = float, default = DEFAULT_MOVE_WARNING_TIME_SECS,
588+
help = 'set maximum number of seconds an agent can take on a move before a warning is issued (default: %(default)s)')
589+
590+
parser.add_argument('--move-timeout-time', dest = 'moveTimeoutTimeSecs',
591+
action = 'store', type = float, default = DEFAULT_MOVE_TIMEOUT_TIME_SECS,
592+
help = 'set maximum number of seconds an agent can take on a move before forfeiting (default: %(default)s)')
593+
594+
parser.add_argument('--max-move-warnings', dest = 'maxMoveWarnings',
595+
action = 'store', type = int, default = DEFAULT_MAX_MOVE_WARNINGS,
596+
help = 'set maximum number of warnings issued to an agent before that agent is disqualified (default: %(default)s)')
597+
547598
options, otherjunk = parser.parse_known_args(argv)
548599
args = dict()
549600

@@ -639,6 +690,11 @@ def readCommand(argv):
639690
args['record'] = options.record
640691
args['catchExceptions'] = options.catchExceptions
641692
args['replay'] = options.replay
693+
args['maxTotalAgentTimeSecs'] = options.maxTotalAgentTimeSecs
694+
args['maxStartupTimeSecs'] = options.maxStartupTimeSecs
695+
args['moveWarningTimeSecs'] = options.moveWarningTimeSecs
696+
args['moveTimeoutTimeSecs'] = options.moveTimeoutTimeSecs
697+
args['maxMoveWarnings'] = options.maxMoveWarnings
642698

643699
return args
644700

@@ -660,10 +716,10 @@ def loadAgents(isRed, agentModule, textgraphics, args):
660716

661717
return createTeamFunction(indices[0], indices[1], isRed, **args)
662718

663-
def replayGame(layout, agents, actions, display, length, redTeamName, blueTeamName):
719+
def replayGame(layout, agents, actions, display, length, redTeamName, blueTeamName, **kwargs):
664720
agents = [DummyAgent(index) for index in range(len(agents))]
665721
rules = CaptureRules()
666-
game = rules.newGame(layout, agents, display, length, False)
722+
game = rules.newGame(layout, agents, display, length, False, **kwargs)
667723
state = game.state
668724
display.redTeam = redTeamName
669725
display.blueTeam = blueTeamName
@@ -698,7 +754,7 @@ def runGames(layout, agents, display, length, numGames, record, numTraining,
698754
else:
699755
gameDisplay = display
700756

701-
g = rules.newGame(layout, agents, gameDisplay, length, catchExceptions)
757+
g = rules.newGame(layout, agents, gameDisplay, length, catchExceptions, **kwargs)
702758
g.run()
703759

704760
if (not isTraining):

pacai/bin/pacman.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def agentCrash(self, game, agentIndex):
187187
else:
188188
logging.error('A ghost crashed')
189189

190-
def getMaxTotalTime(self, agentIndex):
190+
def getMaxTotalAgentTime(self, agentIndex):
191191
return self.timeout
192192

193193
def getMaxStartupTime(self, agentIndex):

pacai/core/game.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def _checkForTimeouts(self, agentIndex, timeTaken):
141141
return True
142142

143143
# Check if the agent has used too much time overall.
144-
maxTotalTime = self.rules.getMaxTotalTime(agentIndex)
144+
maxTotalTime = self.rules.getMaxTotalAgentTime(agentIndex)
145145
if (self.totalAgentTimes[agentIndex] > maxTotalTime):
146146
logging.warning('Agent %d ran out of time! (time: %1.2f)' %
147147
(agentIndex, self.totalAgentTimes[agentIndex]))
@@ -164,7 +164,7 @@ def _registerInitialState(self):
164164
self._agentCrash(agentIndex)
165165
return False
166166

167-
maxStartupTime = int(self.rules.getMaxStartupTime(agentIndex))
167+
maxStartupTime = float(self.rules.getMaxStartupTime(agentIndex))
168168
startTime = time.time()
169169

170170
try:

pacai/core/timeoutTeam.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from pacai.util import reflection
2+
3+
def createTeam(firstIndex, secondIndex, isRed,
4+
first = 'pacai.agents.capture.timeout.TimeoutAgent',
5+
second = 'pacai.agents.capture.timeout.TimeoutAgent'):
6+
"""
7+
A team for testing timeouts.
8+
"""
9+
10+
firstAgent = reflection.qualifiedImport(first)
11+
secondAgent = reflection.qualifiedImport(second)
12+
13+
return [
14+
firstAgent(firstIndex),
15+
secondAgent(secondIndex),
16+
]

tests/test_bin.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
from pacai.bin import gridworld
55
from pacai.bin import pacman
66

7-
"""
8-
This is a test class to assess the executables of this project.
9-
"""
107
class BinTest(unittest.TestCase):
8+
"""
9+
This is a test class to assess the executables of this project.
10+
"""
1111

1212
def test_pacman(self):
1313
# Run game of pacman with valid agent.
@@ -31,7 +31,10 @@ def test_pacman_help(self):
3131

3232
def test_capture(self):
3333
# Run game of capture with default agents.
34-
capture.main(['--null-graphics'])
34+
capture.main([
35+
'--null-graphics',
36+
'--max-moves', '16',
37+
])
3538

3639
def test_capture_help(self):
3740
# Show all capture arguments.
@@ -62,10 +65,18 @@ def test_seeded_runs(self):
6265

6366
def test_capture_seeded_maze_generations(self):
6467
# Run game of capture with random generated map without seed value.
65-
capture.main(['--null-graphics', '--layout', 'RANDOM'])
68+
capture.main([
69+
'--null-graphics',
70+
'--layout', 'RANDOM',
71+
'--max-moves', '16',
72+
])
6673

6774
# Run game of capture with random generated map with seed value.
68-
capture.main(['--null-graphics', '--layout', 'RANDOM94'])
75+
capture.main([
76+
'--null-graphics',
77+
'--layout', 'RANDOM94',
78+
'--max-moves', '16',
79+
])
6980

7081
if __name__ == '__main__':
7182
unittest.main()

tests/test_capture.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import unittest
2+
3+
from pacai.bin import capture
4+
from pacai.agents.capture.timeout import TimeoutAgent
5+
6+
class CaptureTest(unittest.TestCase):
7+
def test_base(self):
8+
games = capture.main([
9+
'--null-graphics',
10+
'--catch-exceptions',
11+
'--max-moves', '16',
12+
])
13+
14+
self.assertTrue(games[0].gameOver)
15+
self.assertFalse(games[0].agentCrashed)
16+
self.assertFalse(games[0].agentTimeout)
17+
18+
def test_full_game_timeout(self):
19+
games = capture.main([
20+
'--null-graphics',
21+
'--catch-exceptions',
22+
'--max-total-agent-time', '0.01',
23+
])
24+
25+
self.assertTrue(games[0].gameOver)
26+
self.assertTrue(games[0].agentCrashed)
27+
self.assertTrue(games[0].agentTimeout)
28+
29+
def test_no_timeout(self):
30+
games = capture.main([
31+
'--null-graphics',
32+
'--catch-exceptions',
33+
'--blue', 'pacai.core.timeoutTeam',
34+
'--max-moves', '16',
35+
])
36+
37+
self.assertTrue(games[0].gameOver)
38+
self.assertFalse(games[0].agentCrashed)
39+
self.assertFalse(games[0].agentTimeout)
40+
41+
def test_init_timeout(self):
42+
try:
43+
TimeoutAgent.waitInitDurationSecs = 0.5
44+
45+
games = capture.main([
46+
'--null-graphics',
47+
'--catch-exceptions',
48+
'--blue', 'pacai.core.timeoutTeam',
49+
'--max-startup-time', '0.1',
50+
])
51+
finally:
52+
TimeoutAgent.reset()
53+
54+
self.assertTrue(games[0].gameOver)
55+
self.assertTrue(games[0].agentCrashed)
56+
self.assertTrue(games[0].agentTimeout)
57+
58+
def test_move_timeout(self):
59+
try:
60+
TimeoutAgent.waitMoveDurationSecs = 0.5
61+
62+
games = capture.main([
63+
'--null-graphics',
64+
'--catch-exceptions',
65+
'--blue', 'pacai.core.timeoutTeam',
66+
'--move-timeout-time', '0.1',
67+
])
68+
finally:
69+
TimeoutAgent.reset()
70+
71+
self.assertTrue(games[0].gameOver)
72+
self.assertTrue(games[0].agentCrashed)
73+
self.assertTrue(games[0].agentTimeout)
74+
75+
def test_move_warnings(self):
76+
try:
77+
TimeoutAgent.waitMoveDurationSecs = 0.1
78+
79+
games = capture.main([
80+
'--null-graphics',
81+
'--catch-exceptions',
82+
'--blue', 'pacai.core.timeoutTeam',
83+
'--move-warning-time', '0.05',
84+
])
85+
finally:
86+
TimeoutAgent.reset()
87+
88+
self.assertTrue(games[0].gameOver)
89+
self.assertTrue(games[0].agentCrashed)
90+
self.assertTrue(games[0].agentTimeout)
91+
92+
# Each blue agent should get a warning on every turn.
93+
# The game ends right away at the third warning an agent gets.
94+
self.assertEqual(5, sum(games[0].totalAgentTimeWarnings))

0 commit comments

Comments
 (0)