From b99240781b813dd030e18750bb9d2ac62711c249 Mon Sep 17 00:00:00 2001 From: Peter Ward Date: Sat, 21 Jul 2012 12:14:16 +1000 Subject: Refactoring into a single engine and viewers. --- snakegame/__init__.py | 28 ++---- snakegame/engine.py | 196 ++++++++++++++++++++++++++++++++++++++++++ snakegame/engines/__init__.py | 15 ---- snakegame/engines/base.py | 190 ---------------------------------------- snakegame/engines/curses.py | 57 ------------ snakegame/engines/pygame.py | 152 -------------------------------- snakegame/engines/pyglet.py | 118 ------------------------- snakegame/utils.py | 18 ++++ snakegame/viewers/__init__.py | 10 +++ snakegame/viewers/curses.py | 54 ++++++++++++ snakegame/viewers/pygame.py | 131 ++++++++++++++++++++++++++++ snakegame/viewers/pyglet.py | 117 +++++++++++++++++++++++++ 12 files changed, 535 insertions(+), 551 deletions(-) create mode 100644 snakegame/engine.py delete mode 100644 snakegame/engines/base.py delete mode 100644 snakegame/engines/curses.py delete mode 100644 snakegame/engines/pygame.py delete mode 100644 snakegame/engines/pyglet.py create mode 100644 snakegame/utils.py create mode 100644 snakegame/viewers/__init__.py create mode 100644 snakegame/viewers/curses.py create mode 100644 snakegame/viewers/pygame.py create mode 100644 snakegame/viewers/pyglet.py diff --git a/snakegame/__init__.py b/snakegame/__init__.py index c3e9f4e..c4dbc83 100644 --- a/snakegame/__init__.py +++ b/snakegame/__init__.py @@ -1,4 +1,5 @@ -from snakegame.engines import BUILTIN_ENGINES +from snakegame.engine import Engine +from snakegame.viewers import BUILTIN_VIEWERS def first(d): for item in d: @@ -14,22 +15,13 @@ def import_thing(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): import argparse 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, + '-v', '--viewer', + default=first(BUILTIN_VIEWERS), ) parser.add_argument( '-w', '--width', @@ -46,17 +38,15 @@ def main(argv=None): parser.add_argument('bot', nargs='+') args = parser.parse_args(argv) - engine = load_engine(args.engine) + viewer_name = BUILTIN_VIEWERS.get(args.viewer, args.viewer) + viewer_class = import_thing(viewer_name, 'Viewer') - game = engine(args.height, args.width, args.apples) + 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() + viewer = viewer_class(game) + viewer.run() diff --git a/snakegame/engine.py b/snakegame/engine.py new file mode 100644 index 0000000..7ba9e0b --- /dev/null +++ b/snakegame/engine.py @@ -0,0 +1,196 @@ +from collections import defaultdict, deque +from copy import deepcopy +from random import Random +from string import ascii_lowercase as lowercase +import sys +import time +import traceback + +from snakegame.colour import hash_colour +from snakegame import common + +SOFT_TIME_LIMIT = 0.5 +HARD_TIME_LIMIT = 1.0 + +class Engine(object): + def __init__( + self, + rows, columns, n_apples, + wrap=True, results=False, + random=None, + *args, **kwargs + ): + super(Engine, self).__init__(*args, **kwargs) + + if random is None: + random = Random() + self.random = random + + 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 = self.random.randrange(0, self.columns) + y = self.random.randrange(0, self.rows) + 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 = self.random.randint(0, sys.maxint) + + self.letters = list(lowercase) + self.letters.reverse() + + self.rows = rows + self.columns = columns + + self.messages_by_team = defaultdict(dict) + + # make board + 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] = common.APPLE + + def add_bot(self, bot, team=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') + + If team is not None, this means you will get a third parameter, + containing messages from the other bots on your team. + """ + letter = self.letters.pop() + + name = bot.__name__ + colour = hash_colour(name) + + position = self.replace_random(common.EMPTY, letter.upper()) + if position is None: + raise KeyError, "Could not insert snake into the board." + + self.bots[letter] = [bot, colour, deque([position]), team] + 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] = common.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): + self.game_ticks += 1 + + for letter, (bot, colour, path, team) in self.bots.items(): + board = deepcopy(self.board) + try: + x, y = path[-1] + + start = time.time() + + if team is None: + d = bot(board, (x, y)) + else: + messages = self.messages_by_team[team] + d, message = bot(board, (x, y), messages) + + assert isinstance(message, str), \ + "Message should be a byte string, not %s (%r)." % ( + type(message), + message, + ) + messages[letter] = message + + end = time.time() + delta = end - start + assert delta < HARD_TIME_LIMIT, 'Exceeded hard time limit.' + if delta >= SOFT_TIME_LIMIT: + print 'Bot %s (%r) exceeded soft time limit.' % (letter.upper(), bot) + + # Sanity checking... + assert isinstance(d, basestring), \ + "Return value should be a string." + d = d.upper() + assert d in common.directions, "Return value should be 'U', 'D', 'L' or 'R'." + + # Get new position. + dx, dy = common.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 common.is_vacant(oldcell): + # 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 == common.APPLE: + # Add in an apple to compensate. + self.replace_random(common.EMPTY, common.APPLE) + else: + # Remove last part of snake. + ox, oy = path.popleft() + self.board[oy][ox] = common.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) + + def __iter__(self): + yield self.board + while self.bots: + self.update_snakes() + yield self.board + diff --git a/snakegame/engines/__init__.py b/snakegame/engines/__init__.py index e03894f..8b13789 100644 --- a/snakegame/engines/__init__.py +++ b/snakegame/engines/__init__.py @@ -1,16 +1 @@ -try: - from collections import OrderedDict as MaybeOrderedDict -except ImportError: - MaybeOrderedDict = dict -from snakegame.engines.base import Engine - -BUILTIN_ENGINES = MaybeOrderedDict() - -def add_engine(name): - class_name = name.title() + 'Engine' - BUILTIN_ENGINES[name] = 'snakegame.engines.%s:%s' % (name, class_name) - -add_engine('pyglet') -add_engine('pygame') -add_engine('curses') diff --git a/snakegame/engines/base.py b/snakegame/engines/base.py deleted file mode 100644 index 2797aa1..0000000 --- a/snakegame/engines/base.py +++ /dev/null @@ -1,190 +0,0 @@ -from collections import defaultdict, deque -from copy import deepcopy -from random import Random -from string import ascii_lowercase as lowercase -import sys -import time -import traceback - -from snakegame.colour import hash_colour -from snakegame import common - -SOFT_TIME_LIMIT = 0.5 -HARD_TIME_LIMIT = 1.0 - -class Engine(object): - def __init__( - self, - rows, columns, n_apples, - wrap=True, results=False, - random=None, - *args, **kwargs - ): - super(Engine, self).__init__(*args, **kwargs) - - if random is None: - random = Random() - self.random = random - - 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 = self.random.randrange(0, self.columns) - y = self.random.randrange(0, self.rows) - 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 = self.random.randint(0, sys.maxint) - - self.letters = list(lowercase) - self.letters.reverse() - - self.rows = rows - self.columns = columns - - self.messages_by_team = defaultdict(dict) - - # make board - 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] = common.APPLE - - def add_bot(self, bot, team=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') - - If team is not None, this means you will get a third parameter, - containing messages from the other bots on your team. - """ - letter = self.letters.pop() - - name = bot.__name__ - colour = hash_colour(name) - - position = self.replace_random(common.EMPTY, letter.upper()) - if position is None: - raise KeyError, "Could not insert snake into the board." - - self.bots[letter] = [bot, colour, deque([position]), team] - 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] = common.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): - self.game_ticks += 1 - - for letter, (bot, colour, path, team) in self.bots.items(): - board = deepcopy(self.board) - try: - x, y = path[-1] - - start = time.time() - - if team is None: - d = bot(board, (x, y)) - else: - messages = self.messages_by_team[team] - d, message = bot(board, (x, y), messages) - - assert isinstance(message, str), \ - "Message should be a byte string, not %s (%r)." % ( - type(message), - message, - ) - messages[letter] = message - - end = time.time() - delta = end - start - assert delta < HARD_TIME_LIMIT, 'Exceeded hard time limit.' - if delta >= SOFT_TIME_LIMIT: - print 'Bot %s (%r) exceeded soft time limit.' % (letter.upper(), bot) - - # Sanity checking... - assert isinstance(d, basestring), \ - "Return value should be a string." - d = d.upper() - assert d in common.directions, "Return value should be 'U', 'D', 'L' or 'R'." - - # Get new position. - dx, dy = common.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 common.is_vacant(oldcell): - # 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 == common.APPLE: - # Add in an apple to compensate. - self.replace_random(common.EMPTY, common.APPLE) - else: - # Remove last part of snake. - ox, oy = path.popleft() - self.board[oy][ox] = common.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) - diff --git a/snakegame/engines/curses.py b/snakegame/engines/curses.py deleted file mode 100644 index 715f321..0000000 --- a/snakegame/engines/curses.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import absolute_import - -import curses -from functools import wraps -import time - -from snakegame import common -from snakegame.engines import Engine - -class CursesEngine(Engine): - def new_game(self, *args): - super(CursesEngine, self).new_game(*args) - - self.window = curses.initscr() - curses.start_color() - - curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) - curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK) - - self.EMPTY_COLOUR = curses.color_pair(0) - self.APPLE_COLOUR = curses.color_pair(1) - self.SNAKE_COLOUR = curses.color_pair(4) - - def draw_board(self): - # Draw grid. - for y, row in enumerate(self.board): - for x, cell in enumerate(row): - char = '.' - colour = self.EMPTY_COLOUR - - # Draw the things on the square. - if cell == common.APPLE: - char = '@' - colour = self.APPLE_COLOUR - - elif cell.isalpha(): # Snake... -# colour = self.bots[cell.lower()][1] - char = cell - colour = self.SNAKE_COLOUR - - self.window.addstr(y, x, char, colour) - - def run(self): - while self.bots: - # Clear the screen. - self.window.erase() - - # Draw the board. - self.draw_board() - - # Update the display. - self.window.refresh() - time.sleep(0.025) - - # Let the snakes move! - self.update_snakes() - diff --git a/snakegame/engines/pygame.py b/snakegame/engines/pygame.py deleted file mode 100644 index b15f3a9..0000000 --- a/snakegame/engines/pygame.py +++ /dev/null @@ -1,152 +0,0 @@ -from __future__ import absolute_import - -import time - -import pkg_resources - -import pygame -from pygame.image import load -pygame.init() - -from snakegame import common -from snakegame.engines import Engine - -sprite_cache = {} - -def load_sprite(filename): - if filename in sprite_cache: - return sprite_cache[filename] - - f = pkg_resources.resource_stream('snakegame', filename) - image = load(f).convert_alpha() - - sprite_cache[filename] = image - return image - -def load_image(filename, xscale, yscale): - image = load_sprite(filename) - new_size = scale_aspect(image.get_size(), (xscale, yscale)) - return pygame.transform.smoothscale(image, new_size) - -def scale_aspect((source_width, source_height), (target_width, target_height)): - source_aspect = source_width / source_height - target_aspect = target_width / target_height - if source_aspect > target_aspect: - # restrict width - width = target_width - height = width / source_aspect - else: - # restrict height - height = target_height - width = height * source_aspect - return (width, height) - -class PygameEngine(Engine): - EDGE_COLOR = (255, 255, 255) - EDGE_WIDTH = 1 - - def __init__(self, rows, columns, n_apples, - width=800, height=600, fullscreen=False, - **kwargs): - flags = 0 - if fullscreen: - flags |= pygame.FULLSCREEN - self.screen = pygame.display.set_mode((width, height), flags) - - self.width = width - self.height = height - - super(PygameEngine, self).__init__(rows, columns, n_apples, - **kwargs) - - def new_game(self, rows, columns, n_apples): - super(PygameEngine, self).new_game(rows, columns, n_apples) - - # make board surface - self.board_width, self.board_height = scale_aspect( - (columns, rows), (self.width, self.height) - ) - self.surface = pygame.Surface((self.board_width, self.board_height)) - - # load sprites - xscale = self.board_width / self.columns - yscale = self.board_height / self.rows - - self.apple = load_image('images/apple.png', xscale, yscale) - self.eyes = load_image('images/eyes.png', xscale, yscale) - - def draw_board(self): - xscale = self.board_width / self.columns - yscale = self.board_height / self.rows - - # Draw grid. - for y, row in enumerate(self.board): - for x, cell in enumerate(row): - left = int(x * xscale) - top = int(y * yscale) - w = int((x + 1) * xscale) - left - h = int((y + 1) * yscale) - top - r = pygame.Rect(left, top, w, h) - - # Draw a square. - pygame.draw.rect(self.surface, self.EDGE_COLOR, r, - self.EDGE_WIDTH) - - # Draw the things on the square. - if cell == common.APPLE: - self.surface.blit(self.apple, r.topleft) - - elif cell.isalpha(): # Snake... - colour = self.bots[cell.lower()][1] - self.surface.fill(colour, r) - - if cell.isupper(): # Snake head - self.surface.blit(self.eyes, r.topleft) - - def run(self): - clock = pygame.time.Clock() - - running = True - while running and self.bots: - for event in pygame.event.get(): - if event.type == pygame.QUIT or \ - (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): - running = False - break - if not running: break - - # Clear the screen. - self.screen.fill((0, 0, 0)) - self.surface.fill((0, 0, 0)) - - # Draw the board. - self.draw_board() - - # Center the board. - x = (self.width - self.board_width) / 2 - y = (self.height - self.board_height) / 2 - self.screen.blit(self.surface, (x, y)) - - # Update the display. - pygame.display.flip() - clock.tick(20) - - # Let the snakes move! - self.update_snakes() - - if running: - time.sleep(2) - -#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 deleted file mode 100644 index 1d88ccf..0000000 --- a/snakegame/engines/pyglet.py +++ /dev/null @@ -1,118 +0,0 @@ -from __future__ import absolute_import - -import pyglet.resource -pyglet.resource.path.append('@snakegame') -pyglet.resource.reindex() - -from pyglet import gl - -from snakegame import common -from snakegame.engines import Engine - -def scale_aspect((source_width, source_height), (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 = float(width) / source_aspect - else: - # restrict height - height = target_height - width = height * source_aspect - return (width, height) - -class PygletEngine(Engine, pyglet.window.Window): - EDGE_COLOR = (255, 255, 255, 255) - EDGE_WIDTH = 2 - - def __init__(self, rows, columns, n_apples, *args, **kwargs): - kwargs.setdefault('caption', 'SnakeGame Window') - kwargs.setdefault('resizable', True) - - super(PygletEngine, self).__init__( - rows, columns, n_apples, - *args, **kwargs - ) - - gl.glEnable(gl.GL_BLEND) - gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) - - pyglet.clock.schedule_interval(lambda t: self.update_snakes(), 1/30.0) - - def new_game(self, *args): - super(PygletEngine, self).new_game(*args) - self.on_resize(self.width, self.height) - - def on_resize(self, width, height): - super(PygletEngine, self).on_resize(width, height) - - assert width == self.width - assert height == self.height - - # make board surface - self.board_width, self.board_height = scale_aspect( - (self.columns, self.rows), (self.width, self.height) - ) - - # load sprites - xscale = float(self.board_width) / self.columns - yscale = float(self.board_height) / self.rows - - 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('images/eyes.png') - self.eyes.size = scale_aspect( - (self.eyes.width, self.eyes.height), - (xscale, yscale) - ) - - def on_draw(self): - self.clear() - - xscale = float(self.board_width) / self.columns - yscale = float(self.board_height) / self.rows - - # Draw grid. - for y, row in enumerate(self.board): - for x, cell in enumerate(row): - left = int(x * xscale) - top = self.height - int(y * yscale) - right = int((x + 1) * xscale) - bottom = self.height - int((y + 1) * yscale) - r = (left, top, right, top, right, bottom, left, bottom) - - # Draw a square. - gl.glLineWidth(self.EDGE_WIDTH) - pyglet.graphics.draw(4, gl.GL_LINE_LOOP, - ('v2f', r), - ('c4B', self.EDGE_COLOR * 4)) - - # Draw the things on the square. - if cell == common.APPLE: - w, h = self.apple.size - self.apple.blit(left + (xscale - w) / 2.0, top - h, width=w, height=h) - - elif cell.isalpha(): # Snake... - colour = self.bots[cell.lower()][1] + (255,) - gl.glPolygonMode(gl.GL_FRONT, gl.GL_FILL) - pyglet.graphics.draw(4, gl.GL_POLYGON, - ('v2f', r), - ('c4B', colour * 4), - ) - - if cell.isupper(): # Snake head - w, h = self.eyes.size - self.eyes.blit(left, top - h, width=w, height=h) - - def update_snakes(self, *args): - if not self.bots: - pyglet.app.exit() - super(PygletEngine, self).update_snakes(*args) - - def run(self): - pyglet.app.run() - diff --git a/snakegame/utils.py b/snakegame/utils.py new file mode 100644 index 0000000..162c0b2 --- /dev/null +++ b/snakegame/utils.py @@ -0,0 +1,18 @@ +try: + from collections import OrderedDict as MaybeOrderedDict +except ImportError: + MaybeOrderedDict = dict + +def scale_aspect((source_width, source_height), (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 = float(width) / source_aspect + else: + # restrict height + height = target_height + width = height * source_aspect + return (width, height) + diff --git a/snakegame/viewers/__init__.py b/snakegame/viewers/__init__.py new file mode 100644 index 0000000..7864e39 --- /dev/null +++ b/snakegame/viewers/__init__.py @@ -0,0 +1,10 @@ +from snakegame.utils import MaybeOrderedDict + +BUILTIN_VIEWERS = MaybeOrderedDict() + +def add_viewer(name): + BUILTIN_VIEWERS[name] = 'snakegame.viewers.%s:Viewer' % name + +add_viewer('pyglet') +add_viewer('pygame') +add_viewer('curses') diff --git a/snakegame/viewers/curses.py b/snakegame/viewers/curses.py new file mode 100644 index 0000000..f8a9602 --- /dev/null +++ b/snakegame/viewers/curses.py @@ -0,0 +1,54 @@ +from __future__ import absolute_import + +import curses +import time + +from snakegame import common + +class Viewer(object): + def __init__(self, engine, *args, **kwargs): + super(Viewer, self).__init__(*args, **kwargs) + + self.engine = engine + + self.window = curses.initscr() + curses.start_color() + + curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) + curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK) + + self.EMPTY_COLOUR = curses.color_pair(0) + self.APPLE_COLOUR = curses.color_pair(1) + self.SNAKE_COLOUR = curses.color_pair(4) + + def draw_board(self, board): + # Draw grid. + for y, row in enumerate(board): + for x, cell in enumerate(row): + char = '.' + colour = self.EMPTY_COLOUR + + # Draw the things on the square. + if cell == common.APPLE: + char = '@' + colour = self.APPLE_COLOUR + + elif cell.isalpha(): # Snake... +# colour = self.bots[cell.lower()][1] + char = cell + colour = self.SNAKE_COLOUR + + self.window.addstr(y, x, char, colour) + + def run(self): + for board in self.engine: + # Clear the screen. + self.window.erase() + + # Draw the board. + self.draw_board(board) + + # Update the display. + self.window.refresh() + time.sleep(0.025) + diff --git a/snakegame/viewers/pygame.py b/snakegame/viewers/pygame.py new file mode 100644 index 0000000..8b8fca4 --- /dev/null +++ b/snakegame/viewers/pygame.py @@ -0,0 +1,131 @@ +from __future__ import absolute_import + +import time + +import pkg_resources + +import pygame +from pygame.image import load +pygame.init() + +from snakegame import common +from snakegame.utils import scale_aspect + +sprite_cache = {} + +def load_sprite(filename): + if filename in sprite_cache: + return sprite_cache[filename] + + f = pkg_resources.resource_stream('snakegame', filename) + image = load(f).convert_alpha() + + sprite_cache[filename] = image + return image + +def load_image(filename, xscale, yscale): + image = load_sprite(filename) + w, h = scale_aspect(image.get_size(), (xscale, yscale)) + return pygame.transform.smoothscale(image, (int(w), int(h))) + +class Viewer(object): + EDGE_COLOR = (255, 255, 255) + EDGE_WIDTH = 1 + + def __init__(self, engine, width=800, height=600, fullscreen=False, **kwargs): + super(Viewer, self).__init__(**kwargs) + + self.engine = engine + + flags = 0 + if fullscreen: + flags |= pygame.FULLSCREEN + self.screen = pygame.display.set_mode((width, height), flags) + + self.width = width + self.height = height + + self.columns = None + self.rows = None + + def on_resize(self): + # make board surface + self.board_width, self.board_height = scale_aspect( + (self.columns, self.rows), (self.width, self.height) + ) + self.surface = pygame.Surface((self.board_width, self.board_height)) + + # load sprites + xscale = self.board_width / self.columns + yscale = self.board_height / self.rows + + self.apple = load_image('images/apple.png', xscale, yscale) + self.eyes = load_image('images/eyes.png', xscale, yscale) + + def draw_board(self, board): + xscale = self.board_width / self.columns + yscale = self.board_height / self.rows + + # Draw grid. + for y, row in enumerate(board): + for x, cell in enumerate(row): + left = int(x * xscale) + top = int(y * yscale) + w = int((x + 1) * xscale) - left + h = int((y + 1) * yscale) - top + r = pygame.Rect(left, top, w, h) + + # Draw a square. + pygame.draw.rect(self.surface, self.EDGE_COLOR, r, + self.EDGE_WIDTH) + + # Draw the things on the square. + if cell == common.APPLE: + self.surface.blit(self.apple, r.topleft) + + elif common.is_snake(cell): + bot = self.engine.bots[cell.lower()] + colour = bot[1] + self.surface.fill(colour, r) + + if common.is_snake_head(cell): + self.surface.blit(self.eyes, r.topleft) + + def run(self): + clock = pygame.time.Clock() + + running = True + + for board in self.engine: + columns, rows = common.get_size(board) + if columns != self.columns or rows != self.rows: + self.columns = columns + self.rows = rows + self.on_resize() + + for event in pygame.event.get(): + if event.type == pygame.QUIT or \ + (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): + running = False + break + if not running: break + + # Clear the screen. + self.screen.fill((0, 0, 0)) + self.surface.fill((0, 0, 0)) + + # Draw the board. + self.draw_board(board) + + # Center the board. + x = (self.width - self.board_width) / 2 + y = (self.height - self.board_height) / 2 + self.screen.blit(self.surface, (x, y)) + + # Update the display. + pygame.display.flip() + clock.tick(20) + + if running: + time.sleep(2) + diff --git a/snakegame/viewers/pyglet.py b/snakegame/viewers/pyglet.py new file mode 100644 index 0000000..9b7a023 --- /dev/null +++ b/snakegame/viewers/pyglet.py @@ -0,0 +1,117 @@ +from __future__ import absolute_import + +import pyglet.resource +pyglet.resource.path.append('@snakegame') +pyglet.resource.reindex() + +from pyglet import gl + +from snakegame import common +from snakegame.utils import scale_aspect + +class Viewer(pyglet.window.Window): + EDGE_COLOR = (255, 255, 255, 255) + EDGE_WIDTH = 2 + + def __init__(self, engine, caption='SnakeGame Window', resizable=True, **kwargs): + super(Viewer, self).__init__( + caption=caption, + resizable=resizable, + **kwargs + ) + + self.engine = engine + self.engine_iter = iter(engine) + + gl.glEnable(gl.GL_BLEND) + gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) + + pyglet.clock.schedule_interval(lambda t: self.update_snakes(), 1/30.0) + + self.board = None + self.columns = None + self.rows = None + + def update_snakes(self, *args): + self.board = next(self.engine_iter, None) + if self.board is None: + pyglet.app.exit() + return + + columns, rows = common.get_size(self.board) + if columns != self.columns or rows != self.rows: + self.columns = columns + self.rows = rows + self.on_resize(self.width, self.height) + + def on_resize(self, width, height): + super(Viewer, self).on_resize(width, height) + + if self.board is None: + return + + # make board surface + self.board_width, self.board_height = scale_aspect( + (self.columns, self.rows), (self.width, self.height) + ) + + # load sprites + xscale = float(self.board_width) / self.columns + yscale = float(self.board_height) / self.rows + + 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('images/eyes.png') + self.eyes.size = scale_aspect( + (self.eyes.width, self.eyes.height), + (xscale, yscale) + ) + + def on_draw(self): + self.clear() + + if self.board is None: + return + + xscale = float(self.board_width) / self.columns + yscale = float(self.board_height) / self.rows + + # Draw grid. + for y, row in enumerate(self.board): + for x, cell in enumerate(row): + left = int(x * xscale) + top = self.height - int(y * yscale) + right = int((x + 1) * xscale) + bottom = self.height - int((y + 1) * yscale) + r = (left, top, right, top, right, bottom, left, bottom) + + # Draw a square. + gl.glLineWidth(self.EDGE_WIDTH) + pyglet.graphics.draw(4, gl.GL_LINE_LOOP, + ('v2f', r), + ('c4B', self.EDGE_COLOR * 4)) + + # Draw the things on the square. + if cell == common.APPLE: + w, h = self.apple.size + self.apple.blit(left + (xscale - w) / 2.0, top - h, width=w, height=h) + + elif common.is_snake(cell): + bot = self.engine.bots[cell.lower()] + colour = bot[1] + (255,) + gl.glPolygonMode(gl.GL_FRONT, gl.GL_FILL) + pyglet.graphics.draw(4, gl.GL_POLYGON, + ('v2f', r), + ('c4B', colour * 4), + ) + + if common.is_snake_head(cell): + w, h = self.eyes.size + self.eyes.blit(left, top - h, width=w, height=h) + + def run(self): + pyglet.app.run() + -- cgit v1.2.3