1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
#!/usr/bin/env python
from __future__ import division
import string
from random import randint
from collections import deque
from copy import deepcopy
import traceback
from common import *
class SnakeEngine(object):
def __init__(self, rows, columns, n_apples):
super(SnakeEngine, self).__init__()
self.letters = list(string.lowercase)
self.letters.reverse()
self.bots = {}
self.new_game(rows, columns, n_apples)
def get_random_position(self):
x = randint(0, self.columns - 1)
y = randint(0, self.rows - 1)
return (x, y)
def replace_random(self, old, new):
for i in xrange(self.rows * self.columns):
x, y = self.get_random_position()
if self.board[y][x] == old:
self.board[y][x] = new
return x, y
def new_game(self, rows, columns, n_apples):
self.rows = rows
self.columns = columns
# make board
self.board = [[Squares.EMPTY for x in xrange(columns)] for y in xrange(rows)]
for i in xrange(n_apples):
x, y = self.get_random_position()
self.board[y][x] = Squares.APPLE
def add_bot(self, bot, colour=None):
"""
A bot is a callable object, with this method signature:
def bot_callable(
board=[[cell for cell in row] for row in board],
position=(snake_x, snake_y)
):
return random.choice('RULD')
"""
letter = self.letters.pop()
position = self.replace_random(Squares.EMPTY, letter.upper())
if position is None:
raise KeyError, "Could not insert snake into the board."
if colour is None:
colour = (randint(0, 255), randint(0, 255), randint(0, 255))
self.bots[letter] = [bot, colour, deque([position])]
return letter
def remove_bot(self, letter):
letter = letter.lower()
for row in self.board:
for x, cell in enumerate(row):
if cell.lower() == letter:
row[x] = Squares.EMPTY
del self.bots[letter]
def update_snakes(self, directions_id=id(directions)):
assert id(directions) == directions_id, \
"The common.directions dictionary has been modified since startup..."
for letter, (bot, colour, path) in self.bots.items():
board = deepcopy(self.board)
try:
x, y = path[-1]
d = bot(board, (x, y))
# Sanity checking...
assert isinstance(d, basestring), \
"Return value should be a string."
d = d.upper()
assert d in directions, "Return value should be 'U', 'D', 'L' or 'R'."
# Get new position.
dx, dy = directions[d]
nx = x + dx
ny = y + dy
if ny < 0 or ny >= self.rows or nx < 0 or nx >= self.columns:
self.remove_bot(letter)
continue
oldcell = self.board[ny][nx]
if oldcell in (Squares.EMPTY, Squares.APPLE):
# Move snake forward.
self.board[ny][nx] = letter.upper()
path.append((nx, ny))
# Make old head into body.
self.board[y][x] = letter.lower()
if oldcell == Squares.APPLE:
# Add in an apple to compensate.
self.replace_random(Squares.EMPTY, Squares.APPLE)
else:
# Remove last part of snake.
ox, oy = path.popleft()
self.board[oy][ox] = Squares.EMPTY
else:
self.remove_bot(letter)
except:
print "Exception in bot %s (%s):" % (letter.upper(), bot)
print '-'*60
traceback.print_exc()
print '-'*60
self.remove_bot(letter)
|