I wrote a python model for the game "snake" that I'm not really satisfied with.
My intention was to separate the game logic from the drawing and input handling (which worked quite well) but I've got several points which I don't know how to solve better.
To use this model one needs to initialize it with callbacks that fire when the score increases and when the game is over. They pass the score as an integer. I don't like this solution because it feels a lot like C, passing around function pointers.
For the drawing the user needs to access the head, tail and food variables in the model. Should I write explicit getters for that? I prefixed everything the user shouldn't directly access with an underscore. How can I make this interface more clear?
One needs to call _move_snake(dx, dy) with the direction dx, dy the snake should move although the model already has a variable that holds the snake's direction. But I guess this way it's easier for testing?
import random class SnakeModel(object): """ Model for a snake-like game. """ def __init__(self, width, height, gameover_callback, atefood_callback): """ Initializes the game gameover_callback and atefood_callback are callback functions. They are passed the current player score (length of the snake's tail) as an integer. """ self.width = width self.height = height self._gameover = gameover_callback self._ateFood = atefood_callback self.reset() def reset(self): """ Starts a new game. Sets the starting position, length and direction of the snake as well as the position of the first food item. """ self._dir = (0, -1) # You can read head, tail and food directly for drawing self.head = (self.width / 2, self.height - 2) self.tail = [(self.width / 2, self.height - 1)] self.food = (self.width / 2, self.height / 3) def step(self): """ Advances the game one step """ # check for collisions if self.head in self.tail: self._gameover(len(self.tail)) # eat food if self.head == self.food: self.tail.insert(0, self.head) self.food = self._new_food_location() self._ateFood(len(self.tail)) # move snake self._move_snake(*self._dir) def set_snake_direction(self, dx, dy): """ Sets the direction the snake will move on the next step """ self._dir = (dx, dy) def _move_snake(self, dx, dy): """ Moves the snake one step in the given direction. The snake can move through the game area's borders and comes out on the other side. """ x, y = self.head newX = (x + dx) % self.width newY = (y + dy) % self.height self.head = (newX, newY) self.tail.insert(0, (x, y)) self.tail.pop() def _new_food_location(self): """ Returns a new possible food position which is not in the snake. """ game_field = set([(x, y) for x in range(self.width) for y in range(self.height)]) possible = game_field - set(self.tail) - set([self.head]) new_position = random.choice(list(possible)) return new_position