summaryrefslogtreecommitdiff
path: root/snake.py
blob: 48048d83424d6d84b4d97b6829a17ca238a1175f (about) (plain)
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)