summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Ward <peteraward@gmail.com>2012-07-20 00:12:03 +1000
committerPeter Ward <peteraward@gmail.com>2012-07-20 00:12:03 +1000
commit6916ec3e381aba2d1d3c44e5ee2a66047c73d8e8 (patch)
tree975bec996f94e3ff4154a7de7d871580ceeda41a
parenta2130079ca771104d87a919f0b4d88583e66d566 (diff)
Get the game to actually work (pyglet, at least!).
-rw-r--r--setup.py4
-rw-r--r--snakegame/__init__.py60
-rw-r--r--snakegame/bots/__init__.py8
-rw-r--r--snakegame/engines/__init__.py25
-rw-r--r--snakegame/engines/base.py38
-rw-r--r--snakegame/engines/curses.py35
-rw-r--r--snakegame/engines/pygame.py42
-rw-r--r--snakegame/engines/pyglet.py43
-rw-r--r--snakegame/snake.py159
9 files changed, 138 insertions, 276 deletions
diff --git a/setup.py b/setup.py
index 559bf7b..0dea571 100644
--- a/setup.py
+++ b/setup.py
@@ -7,6 +7,10 @@ setup(
author='Peter Ward',
author_email='peteraward@gmail.com',
packages=['snakegame'],
+ zip_safe=False,
+ package_data={
+ 'snakegame': 'images/*.png',
+ },
entry_points={
'console_scripts': [
'snakegame = snakegame:main',
diff --git a/snakegame/__init__.py b/snakegame/__init__.py
index 7fbff4d..73e1060 100644
--- a/snakegame/__init__.py
+++ b/snakegame/__init__.py
@@ -1,10 +1,62 @@
import argparse
from snakegame.engines import BUILTIN_ENGINES
-from snakegame.bots import BUILTIN_BOTS
+
+def first(d):
+ for item in d:
+ return item
+
+def rsplit_get(s, sep, default):
+ if sep not in s:
+ return (s, default)
+ return s.rsplit(sep, 1)
+
+def import_thing(name, default_obj):
+ pkg, obj = rsplit_get(name, ':', default_obj)
+ mod = __import__(pkg, fromlist=[obj])
+ return getattr(mod, obj)
+
+def load_engine(name, builtins=BUILTIN_ENGINES):
+ engine = BUILTIN_ENGINES.get(name, name)
+ return import_thing(engine, 'Engine')
def main(argv=None):
- parser = argparse.ArgumentParser()
- parser.add_argument('-e', '--engine')
+ parser = argparse.ArgumentParser(conflict_handler='resolve')
+ parser.add_argument(
+ '-e', '--engine',
+ default=first(BUILTIN_ENGINES),
+ )
+ parser.add_argument(
+ '-l', '--loop',
+ action='store_true',
+ default=False,
+ )
+ parser.add_argument(
+ '-w', '--width',
+ default=30,
+ )
+ parser.add_argument(
+ '-h', '--height',
+ default=20,
+ )
+ parser.add_argument(
+ '-a', '--apples',
+ default=40,
+ )
+ parser.add_argument('bot', nargs='+')
args = parser.parse_args(argv)
- print args
+
+ engine = load_engine(args.engine)
+
+ game = engine(args.height, args.width, args.apples)
+
+ for name in args.bot:
+ bot = import_thing(name, 'bot')
+ game.add_bot(bot)
+
+ game.run()
+
+ if args.loop:
+ while True:
+ game.run()
+
diff --git a/snakegame/bots/__init__.py b/snakegame/bots/__init__.py
index 05e3a78..c404485 100644
--- a/snakegame/bots/__init__.py
+++ b/snakegame/bots/__init__.py
@@ -34,3 +34,11 @@ def random_avoid_bot(board, position):
return 'U'
return choice(available)
+BUILTIN_BOTS = {
+ 'up_bot': up_bot,
+ 'down_bot': down_bot,
+ 'left_bot': left_bot,
+ 'right_bot': right_bot,
+ 'random_bot': random_bot,
+ 'random_avoid_bot': random_avoid_bot,
+}
diff --git a/snakegame/engines/__init__.py b/snakegame/engines/__init__.py
index 197aeec..e03894f 100644
--- a/snakegame/engines/__init__.py
+++ b/snakegame/engines/__init__.py
@@ -7,23 +7,10 @@ from snakegame.engines.base import Engine
BUILTIN_ENGINES = MaybeOrderedDict()
-try:
- from snakegame.engines.pyglet import PygletEngine
-except ImportError:
- pass
-else:
- BUILTIN_ENGINES['pyglet'] = PygletEngine
+def add_engine(name):
+ class_name = name.title() + 'Engine'
+ BUILTIN_ENGINES[name] = 'snakegame.engines.%s:%s' % (name, class_name)
-try:
- from snakegame.engines.pygame import PygameEngine
-except ImportError:
- pass
-else:
- BUILTIN_ENGINES['pygame'] = PygameEngine
-
-try:
- from snakegame.engines.curses import CursesEngine
-except ImportError:
- pass
-else:
- BUILTIN_ENGINES['curses'] = CursesEngine
+add_engine('pyglet')
+add_engine('pygame')
+add_engine('curses')
diff --git a/snakegame/engines/base.py b/snakegame/engines/base.py
index 539498f..f6bb9e9 100644
--- a/snakegame/engines/base.py
+++ b/snakegame/engines/base.py
@@ -1,20 +1,21 @@
-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 *
+from snakegame.colour import hash_colour
+from snakegame import common
class Engine(object):
- def __init__(self, rows, columns, n_apples, wrap=False, results=False,
- *args, **kwargs):
+ def __init__(
+ self,
+ rows, columns, n_apples,
+ wrap=True, results=False,
+ *args, **kwargs
+ ):
super(Engine, self).__init__(*args, **kwargs)
self.wrap = wrap
@@ -48,10 +49,10 @@ class Engine(object):
self.columns = columns
# make board
- self.board = [[Squares.EMPTY for x in xrange(columns)] for y in xrange(rows)]
+ self.board = [[common.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
+ self.board[y][x] = common.APPLE
def add_bot(self, bot):
"""
@@ -67,7 +68,7 @@ class Engine(object):
name = bot.__name__
colour = hash_colour(name)
- position = self.replace_random(Squares.EMPTY, letter.upper())
+ position = self.replace_random(common.EMPTY, letter.upper())
if position is None:
raise KeyError, "Could not insert snake into the board."
@@ -100,10 +101,7 @@ class Engine(object):
(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..."
-
+ def update_snakes(self):
self.game_ticks += 1
for letter, (bot, colour, path) in self.bots.items():
@@ -116,10 +114,10 @@ class Engine(object):
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'."
+ assert d in common.directions, "Return value should be 'U', 'D', 'L' or 'R'."
# Get new position.
- dx, dy = directions[d]
+ dx, dy = common.directions[d]
nx = x + dx
ny = y + dy
@@ -132,7 +130,7 @@ class Engine(object):
continue
oldcell = self.board[ny][nx]
- if oldcell in (Squares.EMPTY, Squares.APPLE):
+ if common.is_vacant(oldcell):
# Move snake forward.
self.board[ny][nx] = letter.upper()
path.append((nx, ny))
@@ -140,13 +138,13 @@ class Engine(object):
# Make old head into body.
self.board[y][x] = letter.lower()
- if oldcell == Squares.APPLE:
+ if oldcell == common.APPLE:
# Add in an apple to compensate.
- self.replace_random(Squares.EMPTY, Squares.APPLE)
+ self.replace_random(common.EMPTY, common.APPLE)
else:
# Remove last part of snake.
ox, oy = path.popleft()
- self.board[oy][ox] = Squares.EMPTY
+ self.board[oy][ox] = common.EMPTY
else:
self.remove_bot(letter)
diff --git a/snakegame/engines/curses.py b/snakegame/engines/curses.py
index 1db5e38..1d93c0b 100644
--- a/snakegame/engines/curses.py
+++ b/snakegame/engines/curses.py
@@ -1,17 +1,19 @@
-#!/usr/bin/env python
-
-from __future__ import division
-
+import curses
+from functools import wraps
import time
-import curses
+from snakegame import common
+from snakegame.engines import Engine
-from common import *
-from snake import SnakeEngine
+def curses_wrapper(fn):
+ @wraps(fn)
+ def deco(*args, **kwargs):
+ return curses.wrapper(fn, *args, **kwargs)
+ return deco
-class ConsoleSnakeEngine(SnakeEngine):
+class CursesEngine(Engine):
def new_game(self, *args):
- super(ConsoleSnakeEngine, self).new_game(*args)
+ super(CursesEngine, self).new_game(*args)
self.window = curses.initscr()
curses.start_color()
@@ -42,6 +44,7 @@ class ConsoleSnakeEngine(SnakeEngine):
self.window.addstr(y, x, char, colour)
+ @curses_wrapper
def run(self):
while self.bots:
# Clear the screen.
@@ -57,17 +60,3 @@ class ConsoleSnakeEngine(SnakeEngine):
# Let the snakes move!
self.update_snakes()
-def main(*args):
- import sys
- from processbot import BotWrapper
-
- rows, columns, apples = map(int, sys.argv[1:4])
- game = ConsoleSnakeEngine(rows, columns, apples)
- for filename in sys.argv[4:]:
- bot = BotWrapper(filename)
- game.add_bot(bot)
- game.run()
-
-if __name__ == '__main__':
- curses.wrapper(main)
-
diff --git a/snakegame/engines/pygame.py b/snakegame/engines/pygame.py
index cf07297..b26a0a1 100644
--- a/snakegame/engines/pygame.py
+++ b/snakegame/engines/pygame.py
@@ -1,6 +1,4 @@
-#!/usr/bin/env python
-
-from __future__ import division
+from __future__ import absolute_import
import os
import time
@@ -9,8 +7,8 @@ import pygame
pygame.init()
from pygame.locals import *
-from common import *
-from snake import SnakeEngine
+from snakegame import common
+from snakegame.engines import Engine
class Sprites(object):
PREFIX = 'images'
@@ -38,7 +36,7 @@ def scale_aspect((source_width, source_height), (target_width, target_height)):
width = height * source_aspect
return (width, height)
-class PygameSnakeEngine(SnakeEngine):
+class PygameEngine(Engine):
EDGE_COLOR = (255, 255, 255)
EDGE_WIDTH = 1
@@ -53,11 +51,11 @@ class PygameSnakeEngine(SnakeEngine):
self.width = width
self.height = height
- super(PygameSnakeEngine, self).__init__(rows, columns, n_apples,
+ super(PygameEngine, self).__init__(rows, columns, n_apples,
**kwargs)
def new_game(self, rows, columns, n_apples):
- super(PygameSnakeEngine, self).new_game(rows, columns, n_apples)
+ super(PygameEngine, self).new_game(rows, columns, n_apples)
# make board surface
self.board_width, self.board_height = scale_aspect(
@@ -138,18 +136,16 @@ class PygameSnakeEngine(SnakeEngine):
if running:
time.sleep(2)
-if __name__ == '__main__':
- import sys
- from processbot import BotWrapper
-
- rows, columns, apples = map(int, sys.argv[1:4])
- game = PygameSnakeEngine(rows, columns, apples)
- for filename in sys.argv[4:]:
- bot = BotWrapper(filename)
- game.add_bot(bot)
- game.run()
-
- # Early window close, late process cleanup.
- pygame.display.quit()
-
-
+#if __name__ == '__main__':
+# import sys
+# from processbot import BotWrapper
+#
+# rows, columns, apples = map(int, sys.argv[1:4])
+# game = PygameEngine(rows, columns, apples)
+# for filename in sys.argv[4:]:
+# bot = BotWrapper(filename)
+# game.add_bot(bot)
+# game.run()
+#
+# # Early window close, late process cleanup.
+# pygame.display.quit()
diff --git a/snakegame/engines/pyglet.py b/snakegame/engines/pyglet.py
index f5b5d0e..bff48ac 100644
--- a/snakegame/engines/pyglet.py
+++ b/snakegame/engines/pyglet.py
@@ -1,23 +1,21 @@
-#!/usr/bin/env python
+from __future__ import absolute_import
-from __future__ import division
-
-import pyglet
-pyglet.resource.path = ['images']
+import pyglet.resource
+pyglet.resource.path.append('@snakegame')
pyglet.resource.reindex()
from pyglet import gl
-import common
-from snakegame.engine import Engine
+from snakegame import common
+from snakegame.engines import Engine
def scale_aspect((source_width, source_height), (target_width, target_height)):
- source_aspect = source_width / source_height
- target_aspect = target_width / target_height
+ source_aspect = float(source_width) / source_height
+ target_aspect = float(target_width) / target_height
if source_aspect > target_aspect:
# restrict width
width = target_width
- height = width / source_aspect
+ height = float(width) / source_aspect
else:
# restrict height
height = target_height
@@ -45,15 +43,15 @@ class PygletEngine(Engine, pyglet.window.Window):
)
# load sprites
- xscale = self.board_width / self.columns
- yscale = self.board_height / self.rows
+ xscale = float(self.board_width) / self.columns
+ yscale = float(self.board_height) / self.rows
- self.apple = pyglet.resource.image('apple.png')
+ self.apple = pyglet.resource.image('images/apple.png')
self.apple.size = scale_aspect(
(self.apple.width, self.apple.height),
(xscale, yscale)
)
- self.eyes = pyglet.resource.image('eyes.png')
+ self.eyes = pyglet.resource.image('images/eyes.png')
self.eyes.size = scale_aspect(
(self.eyes.width, self.eyes.height),
(xscale, yscale)
@@ -62,8 +60,8 @@ class PygletEngine(Engine, pyglet.window.Window):
def on_draw(self):
self.clear()
- xscale = self.board_width / self.columns
- yscale = self.board_height / self.rows
+ xscale = float(self.board_width) / self.columns
+ yscale = float(self.board_height) / self.rows
# Draw grid.
for y, row in enumerate(self.board):
@@ -81,7 +79,7 @@ class PygletEngine(Engine, pyglet.window.Window):
('c4B', self.EDGE_COLOR * 4))
# Draw the things on the square.
- if cell == common.Squares.APPLE:
+ if cell == common.APPLE:
w, h = self.apple.size
self.apple.blit(left + (xscale - w) / 2.0, top - h, width=w, height=h)
@@ -105,14 +103,3 @@ class PygletEngine(Engine, pyglet.window.Window):
def run(self):
pyglet.app.run()
-if __name__ == '__main__':
- import sys
- from processbot import BotWrapper
-
- rows, columns, apples = map(int, sys.argv[1:4])
- game = PygletEngine(rows, columns, apples)
- for filename in sys.argv[4:]:
- bot = BotWrapper(filename)
- game.add_bot(bot)
- game.run()
-
diff --git a/snakegame/snake.py b/snakegame/snake.py
deleted file mode 100644
index 539498f..0000000
--- a/snakegame/snake.py
+++ /dev/null
@@ -1,159 +0,0 @@
-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 Engine(object):
- def __init__(self, rows, columns, n_apples, wrap=False, results=False,
- *args, **kwargs):
- super(Engine, 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)
-