summaryrefslogtreecommitdiff
path: root/snakegame/snake.py
diff options
context:
space:
mode:
Diffstat (limited to 'snakegame/snake.py')
-rw-r--r--snakegame/snake.py161
1 files changed, 161 insertions, 0 deletions
diff --git a/snakegame/snake.py b/snakegame/snake.py
new file mode 100644
index 0000000..89e97da
--- /dev/null
+++ b/snakegame/snake.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+
+from __future__ import division
+
+import sys
+import time
+import string
+import random
+from colour import hash_colour
+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, wrap=False, results=False,
+ *args, **kwargs):
+ super(SnakeEngine, self).__init__(*args, **kwargs)
+
+ self.wrap = wrap
+ self.bots = {}
+ self.results = None
+ if results:
+ self.results = open('results.csv', 'a+')
+
+ 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.game_ticks = 0
+ self.game_id = random.randint(0, sys.maxint)
+
+ self.letters = list(string.lowercase)
+ self.letters.reverse()
+
+ 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):
+ """
+ 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()
+
+ name = bot.__name__
+ colour = hash_colour(name)
+
+ position = self.replace_random(Squares.EMPTY, letter.upper())
+ if position is None:
+ raise KeyError, "Could not insert snake into the board."
+
+ self.bots[letter] = [bot, colour, deque([position])]
+ return letter
+
+ def remove_bot(self, letter):
+ letter = letter.lower()
+
+ time_score = self.game_ticks
+
+ for row in self.board:
+ for x, cell in enumerate(row):
+ if cell.lower() == letter:
+ row[x] = Squares.EMPTY
+
+ bot = self.bots[letter]
+ del self.bots[letter]
+
+ if not self.results:
+ return
+
+ try:
+ name = bot[0].__name__
+ except AttributeError:
+ pass
+ else:
+ apple_score = len(bot[2])
+ self.results.write('%s,%s,%s,%s\n' % \
+ (self.game_id, name, apple_score, time_score))
+ self.results.flush()
+
+ def update_snakes(self, directions_id=id(directions)):
+ assert id(directions) == directions_id, \
+ "The common.directions dictionary has been modified since startup..."
+
+ self.game_ticks += 1
+
+ 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 self.wrap:
+ ny %= self.rows
+ nx %= self.columns
+ else:
+ 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)
+