From 6916ec3e381aba2d1d3c44e5ee2a66047c73d8e8 Mon Sep 17 00:00:00 2001 From: Peter Ward Date: Fri, 20 Jul 2012 00:12:03 +1000 Subject: Get the game to actually work (pyglet, at least!). --- setup.py | 4 ++ snakegame/__init__.py | 60 ++++++++++++++-- snakegame/bots/__init__.py | 8 +++ snakegame/engines/__init__.py | 25 ++----- snakegame/engines/base.py | 38 +++++----- snakegame/engines/curses.py | 35 ++++------ snakegame/engines/pygame.py | 42 +++++------ snakegame/engines/pyglet.py | 43 ++++-------- snakegame/snake.py | 159 ------------------------------------------ 9 files changed, 138 insertions(+), 276 deletions(-) delete mode 100644 snakegame/snake.py 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) - -- cgit v1.2.3