summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Ward <peteraward@gmail.com>2012-07-19 23:05:30 +1000
committerPeter Ward <peteraward@gmail.com>2012-07-19 23:05:30 +1000
commita2130079ca771104d87a919f0b4d88583e66d566 (patch)
tree62218f3830864c3fbeabfcaefe76982e1c2daa22
parentb981ab7e032af8764b4ad2d8c6678dccc87ea0e4 (diff)
Lots of refactoring.
-rw-r--r--README141
-rw-r--r--setup.py6
-rw-r--r--snakegame/__init__.py10
-rw-r--r--snakegame/bots/__init__.py36
-rw-r--r--snakegame/common.py55
-rw-r--r--snakegame/engines/__init__.py29
-rw-r--r--snakegame/engines/base.py159
-rw-r--r--snakegame/engines/pyglet.py12
-rw-r--r--snakegame/pngcanvas.py291
-rw-r--r--snakegame/pngchart.py53
-rw-r--r--snakegame/pygooglechart.py1066
-rw-r--r--snakegame/snake.py6
-rw-r--r--snakegame/stats.py61
13 files changed, 354 insertions, 1571 deletions
diff --git a/README b/README
index 97e7b5b..8e6140f 100644
--- a/README
+++ b/README
@@ -1,86 +1,57 @@
-How to write a bot
-==================
-
-guarantee code indicates something you can run which will execute without errors
-if "input" refers to a valid board.
-
-input = {
- # the object you control
-
- # guarantee code:
- # me = input["whoami"]
- # my = input["objects"][me]
- # assert my["type"] == "snake"
- # (see below for more guarantees)
-
- "whoami": "a",
-
- # the board is by a torus by default (wraps around horiz & vert),
- # but maps will have walls on all the edges to disable this.
-
- # guarantee code:
- # for row in input["board"]:
- # for item in row:
- # assert item in input["objects"]
-
- "board": [
- ["W", "W", "W", "W", "W", "W"],
- ["W", " ", "*", " ", " ", "W"],
- ["W", "a", " ", "b", "b", "W"],
- ["W", " ", " ", " ", " ", "W"],
- ["W", "W", "W", "W", "W", "W"]
- ],
-
- # each object refers to a type of "thing" which can be in the board.
-
- # guarantee code:
- # for key, thing in input["objects"].items():
- # if thing["type"] == "snake":
- # assert "valid_moves" in thing
- # x, y = thing["head"]
- # assert input["board"][y][x] == key
- # elif thing["type"] == "special":
- # for effect in thing["effects"]:
- # assert effect[0] in ("die", "grow")
- # else:
- # raise TypeError("invalid thing type")
-
- "objects": {
- "a": {
- "type": "snake",
- "head": [1, 2],
- "valid_moves": {
- "L": [0, 2],
- "R": [2, 2],
- "U": [1, 1],
- "D": [1, 3]
- }
- },
-
- "b": {
- "type": "snake",
- "head": [4, 2],
- "valid_moves": {
- "L": [3, 2],
- "R": [5, 2],
- "U": [4, 1],
- "D": [4, 3]
- }
- },
-
- "W": {
- "type": "special",
- "effects": [
- ["die"]
- ]
- },
-
- "*": { # represents an apple
- "type": "special",
- "effects": [
- ["grow", 1]
- ]
- }
- }
-}
+= Getting started =
+
+$ cat > simple.py
+def bot(board, position):
+ return 'L'
+^D
+
+$ snakegame -e pyglet simple
+
+= Writing a bot =
+
+A bot is simply a Python function which takes two arguments, the current state
+of the board, and the current position of the head of your snake. The function
+must return one of the strings 'L', 'U', 'R' or 'D', indicating which direction
+the snake should next move (left, up, right or down, respectively).
+
+== The Board ==
+
+The board is a list containing each row of the board.
+Each row is a list containing the cells of that row.
+
+The board is actually a torus (that is, the top edge wraps to the bottom, and
+the left edge to the right, and vice versa).
+Map designers can easily turn this into a normal grid simply by placing walls on
+the edges.
+
+Each cell is a single character string:
+
+* period (.) indicates an empty cell
+* asterisk (*) indicates an apple
+* plus (+) indicates an ice cream
+* minus (-) indicates a shrinking potion
+* octothorpe (#) indicates a wall
+* uppercase letters (A-Z) indicate the head of a snake.
+* lowercase letters (a-z) indicate the body of a snake.
+
+All other characters are reserved for future use.
+
+Every snake will have exactly one head.
+Snakes may have no body.
+Snakes may have a body which is not contiguous on the board.
+
+== Usual boilerplate ==
+
+The typical boilerplate for writing a bot looks like this, which gets the
+character of your snake’s head, and the size of the board.
+
+def bot(board, position):
+ x, y = position
+ me = board[y][x]
+
+ height = len(board)
+ width = len(board[0])
+
+ # ...
+ return 'L'
diff --git a/setup.py b/setup.py
index 9c07911..559bf7b 100644
--- a/setup.py
+++ b/setup.py
@@ -7,5 +7,9 @@ setup(
author='Peter Ward',
author_email='peteraward@gmail.com',
packages=['snakegame'],
- scripts=[],
+ entry_points={
+ 'console_scripts': [
+ 'snakegame = snakegame:main',
+ ]
+ },
)
diff --git a/snakegame/__init__.py b/snakegame/__init__.py
index e69de29..7fbff4d 100644
--- a/snakegame/__init__.py
+++ b/snakegame/__init__.py
@@ -0,0 +1,10 @@
+import argparse
+
+from snakegame.engines import BUILTIN_ENGINES
+from snakegame.bots import BUILTIN_BOTS
+
+def main(argv=None):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-e', '--engine')
+ args = parser.parse_args(argv)
+ print args
diff --git a/snakegame/bots/__init__.py b/snakegame/bots/__init__.py
new file mode 100644
index 0000000..05e3a78
--- /dev/null
+++ b/snakegame/bots/__init__.py
@@ -0,0 +1,36 @@
+from random import choice
+
+from snakegame import common
+
+def make_direction_bot(direction, human):
+ def bot(board, position):
+ return direction
+ bot.__doc__ = 'This bot always moves %s.' % human
+ return bot
+
+up_bot = make_direction_bot('U', 'up')
+down_bot = make_direction_bot('D', 'down')
+left_bot = make_direction_bot('L', 'left')
+right_bot = make_direction_bot('R', 'right')
+
+def random_bot(board, position):
+ "This bot just chooses a random direction to move."
+ return choice('UDLR')
+
+def random_avoid_bot(board, position):
+ """
+ This bot chooses a random direction to move, but will not move into a
+ square which will kill it immediately (unless it has no choice).
+ """
+ x, y = position
+
+ available = []
+ for direction, (dx, dy) in common.directions.items():
+ cell = common.get_cell(board, x + dx, y + dy)
+ if common.is_vacant(cell):
+ available.append(direction)
+
+ if not available:
+ return 'U'
+ return choice(available)
+
diff --git a/snakegame/common.py b/snakegame/common.py
index 398f810..a44219b 100644
--- a/snakegame/common.py
+++ b/snakegame/common.py
@@ -1,4 +1,5 @@
-import os
+from string import ascii_lowercase as lowercase, ascii_uppercase as uppercase
+alphabet = lowercase + uppercase
directions = {
'U': (0, -1),
@@ -7,7 +8,53 @@ directions = {
'R': (1, 0),
}
-class Squares(object):
- EMPTY = '.'
- APPLE = '*'
+EMPTY = '.'
+APPLE = '*'
+WALL = '#'
+is_empty = EMPTY.__eq__
+is_apple = APPLE.__eq__
+is_wall = WALL.__eq__
+
+def is_vacant(cell):
+ return cell in (EMPTY, APPLE)
+
+def is_blocking(cell):
+ return cell not in (EMPTY, APPLE)
+
+def is_snake(cell):
+ return cell in alphabet
+
+def is_snake_head(cell):
+ return cell in uppercase
+
+def is_snake_body(cell):
+ return cell in lowercase
+
+def is_enemy_snake(cell, me):
+ assert me.isupper()
+ return is_snake(cell) and cell.upper() != me
+
+def is_my_snake(cell, me):
+ assert me.isupper()
+ return cell.upper() == me
+
+def get_size(board):
+ height = len(board)
+ width = len(board[0])
+ return width, height
+
+def in_bounds(x, y, width, height):
+ return (
+ x >= 0 and x < width and
+ y >= 0 and y < height
+ )
+
+def get_cell(board, x, y, wrap=True):
+ width, height = get_size(board)
+ if wrap:
+ x %= width
+ y %= height
+ elif not in_bounds(x, y, width, height):
+ return None
+ return board[y][x]
diff --git a/snakegame/engines/__init__.py b/snakegame/engines/__init__.py
index e69de29..197aeec 100644
--- a/snakegame/engines/__init__.py
+++ b/snakegame/engines/__init__.py
@@ -0,0 +1,29 @@
+try:
+ from collections import OrderedDict as MaybeOrderedDict
+except ImportError:
+ MaybeOrderedDict = dict
+
+from snakegame.engines.base import Engine
+
+BUILTIN_ENGINES = MaybeOrderedDict()
+
+try:
+ from snakegame.engines.pyglet import PygletEngine
+except ImportError:
+ pass
+else:
+ BUILTIN_ENGINES['pyglet'] = PygletEngine
+
+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
diff --git a/snakegame/engines/base.py b/snakegame/engines/base.py
new file mode 100644
index 0000000..539498f
--- /dev/null
+++ b/snakegame/engines/base.py
@@ -0,0 +1,159 @@
+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)
+
diff --git a/snakegame/engines/pyglet.py b/snakegame/engines/pyglet.py
index 5a17ac0..f5b5d0e 100644
--- a/snakegame/engines/pyglet.py
+++ b/snakegame/engines/pyglet.py
@@ -9,7 +9,7 @@ pyglet.resource.reindex()
from pyglet import gl
import common
-from snake import SnakeEngine
+from snakegame.engine import Engine
def scale_aspect((source_width, source_height), (target_width, target_height)):
source_aspect = source_width / source_height
@@ -24,12 +24,12 @@ def scale_aspect((source_width, source_height), (target_width, target_height)):
width = height * source_aspect
return (width, height)
-class PygletSnakeEngine(SnakeEngine, pyglet.window.Window):
+class PygletEngine(Engine, pyglet.window.Window):
EDGE_COLOR = (255, 255, 255, 255)
EDGE_WIDTH = 2
def __init__(self, rows, columns, n_apples, *args, **kwargs):
- super(PygletSnakeEngine, self).__init__(rows, columns, n_apples, *args, **kwargs)
+ 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)
@@ -37,7 +37,7 @@ class PygletSnakeEngine(SnakeEngine, pyglet.window.Window):
pyglet.clock.schedule_interval(lambda t: self.update_snakes(), 0.025)
def new_game(self, rows, columns, n_apples):
- super(PygletSnakeEngine, self).new_game(rows, columns, n_apples)
+ super(PygletEngine, self).new_game(rows, columns, n_apples)
# make board surface
self.board_width, self.board_height = scale_aspect(
@@ -100,7 +100,7 @@ class PygletSnakeEngine(SnakeEngine, pyglet.window.Window):
def update_snakes(self, *args):
if not self.bots:
pyglet.app.exit()
- super(PygletSnakeEngine, self).update_snakes(*args)
+ super(PygletEngine, self).update_snakes(*args)
def run(self):
pyglet.app.run()
@@ -110,7 +110,7 @@ if __name__ == '__main__':
from processbot import BotWrapper
rows, columns, apples = map(int, sys.argv[1:4])
- game = PygletSnakeEngine(rows, columns, apples)
+ game = PygletEngine(rows, columns, apples)
for filename in sys.argv[4:]:
bot = BotWrapper(filename)
game.add_bot(bot)
diff --git a/snakegame/pngcanvas.py b/snakegame/pngcanvas.py
deleted file mode 100644
index 394ff4f..0000000
--- a/snakegame/pngcanvas.py
+++ /dev/null
@@ -1,291 +0,0 @@
-#!/usr/bin/env python
-
-"""Simple PNG Canvas for Python"""
-__version__ = "0.8"
-__author__ = "Rui Carmo (http://the.taoofmac.com)"
-__copyright__ = "CC Attribution-NonCommercial-NoDerivs 2.0 Rui Carmo"
-__contributors__ = ["http://collaboa.weed.rbse.com/repository/file/branches/pgsql/lib/spark_pr.rb"], ["Eli Bendersky"]
-
-import zlib, struct
-
-signature = struct.pack("8B", 137, 80, 78, 71, 13, 10, 26, 10)
-
-# alpha blends two colors, using the alpha given by c2
-def blend(c1, c2):
- return [c1[i]*(0xFF-c2[3]) + c2[i]*c2[3] >> 8 for i in range(3)]
-
-# calculate a new alpha given a 0-0xFF intensity
-def intensity(c,i):
- return [c[0],c[1],c[2],(c[3]*i) >> 8]
-
-# calculate perceptive grayscale value
-def grayscale(c):
- return int(c[0]*0.3 + c[1]*0.59 + c[2]*0.11)
-
-# calculate gradient colors
-def gradientList(start,end,steps):
- delta = [end[i] - start[i] for i in range(4)]
- grad = []
- for i in range(steps+1):
- grad.append([start[j] + (delta[j]*i)/steps for j in range(4)])
- return grad
-
-class PNGCanvas:
- def __init__(self, width, height,bgcolor=[0xff,0xff,0xff,0xff],color=[0,0,0,0xff]):
- self.canvas = []
- self.width = width
- self.height = height
- self.color = color #rgba
- bgcolor = bgcolor[0:3] # we don't need alpha for background
- for i in range(height):
- self.canvas.append([bgcolor] * width)
-
- def point(self,x,y,color=None):
- if x<0 or y<0 or x>self.width-1 or y>self.height-1: return
- if color == None: color = self.color
- self.canvas[y][x] = blend(self.canvas[y][x],color)
-
- def _rectHelper(self,x0,y0,x1,y1):
- x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1)
- if x0 > x1: x0, x1 = x1, x0
- if y0 > y1: y0, y1 = y1, y0
- return [x0,y0,x1,y1]
-
- def verticalGradient(self,x0,y0,x1,y1,start,end):
- x0, y0, x1, y1 = self._rectHelper(x0,y0,x1,y1)
- grad = gradientList(start,end,y1-y0)
- for x in range(x0, x1+1):
- for y in range(y0, y1+1):
- self.point(x,y,grad[y-y0])
-
- def rectangle(self,x0,y0,x1,y1):
- x0, y0, x1, y1 = self._rectHelper(x0,y0,x1,y1)
- self.polyline([[x0,y0],[x1,y0],[x1,y1],[x0,y1],[x0,y0]])
-
- def filledRectangle(self,x0,y0,x1,y1):
- x0, y0, x1, y1 = self._rectHelper(x0,y0,x1,y1)
- for x in range(x0, x1+1):
- for y in range(y0, y1+1):
- self.point(x,y,self.color)
-
- def copyRect(self,x0,y0,x1,y1,dx,dy,destination):
- x0, y0, x1, y1 = self._rectHelper(x0,y0,x1,y1)
- for x in range(x0, x1+1):
- for y in range(y0, y1+1):
- destination.canvas[dy+y-y0][dx+x-x0] = self.canvas[y][x]
-
- def blendRect(self,x0,y0,x1,y1,dx,dy,destination,alpha=0xff):
- x0, y0, x1, y1 = self._rectHelper(x0,y0,x1,y1)
- for x in range(x0, x1+1):
- for y in range(y0, y1+1):
- rgba = self.canvas[y][x] + [alpha]
- destination.point(dx+x-x0,dy+y-y0,rgba)
-
- # draw a line using Xiaolin Wu's antialiasing technique
- def line(self,x0, y0, x1, y1):
- # clean params
- x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1)
- if y0>y1:
- y0, y1, x0, x1 = y1, y0, x1, x0
- dx = x1-x0
- if dx < 0:
- sx = -1
- else:
- sx = 1
- dx *= sx
- dy = y1-y0
-
- # 'easy' cases
- if dy == 0:
- for x in range(x0,x1,sx):
- self.point(x, y0)
- return
- if dx == 0:
- for y in range(y0,y1):
- self.point(x0, y)
- self.point(x1, y1)
- return
- if dx == dy:
- for x in range(x0,x1,sx):
- self.point(x, y0)
- y0 = y0 + 1
- return
-
- # main loop
- self.point(x0, y0)
- e_acc = 0
- if dy > dx: # vertical displacement
- e = (dx << 16) / dy
- for i in range(y0,y1-1):
- e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
- if (e_acc <= e_acc_temp):
- x0 = x0 + sx
- w = 0xFF-(e_acc >> 8)
- self.point(x0, y0, intensity(self.color,(w)))
- y0 = y0 + 1
- self.point(x0 + sx, y0, intensity(self.color,(0xFF-w)))
- self.point(x1, y1)
- return
-
- # horizontal displacement
- e = (dy << 16) / dx
- for i in range(x0,x1-sx,sx):
- e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
- if (e_acc <= e_acc_temp):
- y0 = y0 + 1
- w = 0xFF-(e_acc >> 8)
- self.point(x0, y0, intensity(self.color,(w)))
- x0 = x0 + sx
- self.point(x0, y0 + 1, intensity(self.color,(0xFF-w)))
- self.point(x1, y1)
-
- def polyline(self,arr):
- for i in range(0,len(arr)-1):
- self.line(arr[i][0],arr[i][1],arr[i+1][0], arr[i+1][1])
-
- def dump(self):
- raw_list = []
- for y in range(self.height):
- raw_list.append(chr(0)) # filter type 0 (None)
- for x in range(self.width):
- raw_list.append(struct.pack("!3B",*self.canvas[y][x]))
- raw_data = ''.join(raw_list)
-
- # 8-bit image represented as RGB tuples
- # simple transparency, alpha is pure white
- return signature + \
- self.pack_chunk('IHDR', struct.pack("!2I5B",self.width,self.height,8,2,0,0,0)) + \
- self.pack_chunk('tRNS', struct.pack("!6B",0xFF,0xFF,0xFF,0xFF,0xFF,0xFF)) + \
- self.pack_chunk('IDAT', zlib.compress(raw_data,9)) + \
- self.pack_chunk('IEND', '')
-
- def pack_chunk(self,tag,data):
- to_check = tag + data
- return struct.pack("!I",len(data)) + to_check + struct.pack("!I", zlib.crc32(to_check) & 0xFFFFFFFF)
-
- def load(self,f):
- assert f.read(8) == signature
- self.canvas=[]
- for tag, data in self.chunks(f):
- if tag == "IHDR":
- ( width,
- height,
- bitdepth,
- colortype,
- compression, filter, interlace ) = struct.unpack("!2I5B",data)
- self.width = width
- self.height = height
- if (bitdepth,colortype,compression, filter, interlace) != (8,2,0,0,0):
- raise TypeError('Unsupported PNG format')
- # we ignore tRNS because we use pure white as alpha anyway
- elif tag == 'IDAT':
- raw_data = zlib.decompress(data)
- rows = []
- i = 0
- for y in range(height):
- filtertype = ord(raw_data[i])
- i = i + 1
- cur = [ord(x) for x in raw_data[i:i+width*3]]
- if y == 0:
- rgb = self.defilter(cur,None,filtertype)
- else:
- rgb = self.defilter(cur,prev,filtertype)
- prev = cur
- i = i+width*3
- row = []
- j = 0
- for x in range(width):
- pixel = rgb[j:j+3]
- row.append(pixel)
- j = j + 3
- self.canvas.append(row)
-
- def defilter(self,cur,prev,filtertype,bpp=3):
- if filtertype == 0: # No filter
- return cur
- elif filtertype == 1: # Sub
- xp = 0
- for xc in range(bpp,len(cur)):
- cur[xc] = (cur[xc] + cur[xp]) % 256
- xp = xp + 1
- elif filtertype == 2: # Up
- for xc in range(len(cur)):
- cur[xc] = (cur[xc] + prev[xc]) % 256
- elif filtertype == 3: # Average
- xp = 0
- for xc in range(len(cur)):
- cur[xc] = (cur[xc] + (cur[xp] + prev[xc])/2) % 256
- xp = xp + 1
- elif filtertype == 4: # Paeth
- xp = 0
- for i in range(bpp):
- cur[i] = (cur[i] + prev[i]) % 256
- for xc in range(bpp,len(cur)):
- a = cur[xp]
- b = prev[xc]
- c = prev[xp]
- p = a + b - c
- pa = abs(p - a)
- pb = abs(p - b)
- pc = abs(p - c)
- if pa <= pb and pa <= pc:
- value = a
- elif pb <= pc:
- value = b
- else:
- value = c
- cur[xc] = (cur[xc] + value) % 256
- xp = xp + 1
- else:
- raise TypeError('Unrecognized scanline filter type')
- return cur
-
- def chunks(self,f):
- while 1:
- try:
- length = struct.unpack("!I",f.read(4))[0]
- tag = f.read(4)
- data = f.read(length)
- crc = struct.unpack("!i",f.read(4))[0]
- except:
- return
- if zlib.crc32(tag + data) != crc:
- raise IOError
- yield [tag,data]
-
-if __name__ == '__main__':
- width = 128
- height = 64
- print "Creating Canvas..."
- c = PNGCanvas(width,height)
- c.color = [0xff,0,0,0xff]
- c.rectangle(0,0,width-1,height-1)
- print "Generating Gradient..."
- c.verticalGradient(1,1,width-2, height-2,[0xff,0,0,0xff],[0x20,0,0xff,0x80])
- print "Drawing Lines..."
- c.color = [0,0,0,0xff]
- c.line(0,0,width-1,height-1)
- c.line(0,0,width/2,height-1)
- c.line(0,0,width-1,height/2)
- # Copy Rect to Self
- print "Copy Rect"
- c.copyRect(1,1,width/2-1,height/2-1,0,height/2,c)
- # Blend Rect to Self
- print "Blend Rect"
- c.blendRect(1,1,width/2-1,height/2-1,width/2,0,c)
- # Write test
- print "Writing to file..."
- f = open("test.png", "wb")
- f.write(c.dump())
- f.close()
- # Read test
- print "Reading from file..."
- f = open("test.png", "rb")
- c.load(f)
- f.close()
- # Write back
- print "Writing to new file..."
- f = open("recycle.png","wb")
- f.write(c.dump())
- f.close()
-
diff --git a/snakegame/pngchart.py b/snakegame/pngchart.py
deleted file mode 100644
index 5428718..0000000
--- a/snakegame/pngchart.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from pngcanvas import PNGCanvas
-
-try:
- from itertools import izip as zip
-except ImportError:
- pass
-
-class SimpleLineChart(object):
- def __init__(self, width, height, colours=None, legend=None):
- self.canvas = PNGCanvas(width, height)
-
- self.width = width
- self.height = height
-
- self.colours = colours
- self.legend = legend
-
- self.series = []
-
- def add_data(self, series):
- self.series.append(series)
-
- def render(self):
- max_width = max(map(len, self.series))
- max_height = max(map(max, self.series))
- x_scale = float(self.width) / max_width
- y_scale = float(self.height) / max_height
-
- data = zip(self.series, self.colours or [], self.legend or [])
- for series, colour, legend in data:
- colour = int(colour, 16)
- self.canvas.color = (
- colour>>16 & 0xff,
- colour>>8 & 0xff,
- colour & 0xff,
- 0xff,
- )
- last = None
- for x, y in enumerate(series):
- if y is not None:
- y = self.height - y * y_scale
- if last is not None:
- x *= x_scale
- self.canvas.line(x - x_scale, last, x, y)
- last = y
-
- def download(self, filename):
- self.render()
-
- f = open(filename, 'wb')
- f.write(self.canvas.dump())
- f.close()
-
diff --git a/snakegame/pygooglechart.py b/snakegame/pygooglechart.py
deleted file mode 100644
index 0c17973..0000000
--- a/snakegame/pygooglechart.py
+++ /dev/null
@@ -1,1066 +0,0 @@
-"""
-pygooglechart - A complete Python wrapper for the Google Chart API
-
-http://pygooglechart.slowchop.com/
-
-Copyright 2007-2008 Gerald Kaszuba
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""
-
-import os
-import urllib
-import urllib2
-import math
-import random
-import re
-import warnings
-import copy
-
-# Helper variables and functions
-# -----------------------------------------------------------------------------
-
-__version__ = '0.2.1'
-__author__ = 'Gerald Kaszuba'
-
-reo_colour = re.compile('^([A-Fa-f0-9]{2,2}){3,4}$')
-
-def _check_colour(colour):
- if not reo_colour.match(colour):
- raise InvalidParametersException('Colours need to be in ' \
- 'RRGGBB or RRGGBBAA format. One of your colours has %s' % \
- colour)
-
-
-def _reset_warnings():
- """Helper function to reset all warnings. Used by the unit tests."""
- globals()['__warningregistry__'] = None
-
-
-# Exception Classes
-# -----------------------------------------------------------------------------
-
-
-class PyGoogleChartException(Exception):
- pass
-
-
-class DataOutOfRangeException(PyGoogleChartException):
- pass
-
-
-class UnknownDataTypeException(PyGoogleChartException):
- pass
-
-
-class NoDataGivenException(PyGoogleChartException):
- pass
-
-
-class InvalidParametersException(PyGoogleChartException):
- pass
-
-
-class BadContentTypeException(PyGoogleChartException):
- pass
-
-
-class AbstractClassException(PyGoogleChartException):
- pass
-
-
-class UnknownChartType(PyGoogleChartException):
- pass
-
-
-# Data Classes
-# -----------------------------------------------------------------------------
-
-
-class Data(object):
-
- def __init__(self, data):
- if type(self) == Data:
- raise AbstractClassException('This is an abstract class')
- self.data = data
-
- @classmethod
- def float_scale_value(cls, value, range):
- lower, upper = range
- assert(upper > lower)
- scaled = (value - lower) * (float(cls.max_value) / (upper - lower))
- return scaled
-
- @classmethod
- def clip_value(cls, value):
- return max(0, min(value, cls.max_value))
-
- @classmethod
- def int_scale_value(cls, value, range):
- return int(round(cls.float_scale_value(value, range)))
-
- @classmethod
- def scale_value(cls, value, range):
- scaled = cls.int_scale_value(value, range)
- clipped = cls.clip_value(scaled)
- Data.check_clip(scaled, clipped)
- return clipped
-
- @staticmethod
- def check_clip(scaled, clipped):
- if clipped != scaled:
- warnings.warn('One or more of of your data points has been '
- 'clipped because it is out of range.')
-
-
-class SimpleData(Data):
-
- max_value = 61
- enc_map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
-
- def __repr__(self):
- encoded_data = []
- for data in self.data:
- sub_data = []
- for value in data:
- if value is None:
- sub_data.append('_')
- elif value >= 0 and value <= self.max_value:
- sub_data.append(SimpleData.enc_map[value])
- else:
- raise DataOutOfRangeException('cannot encode value: %d'
- % value)
- encoded_data.append(''.join(sub_data))
- return 'chd=s:' + ','.join(encoded_data)
-
-
-class TextData(Data):
-
- max_value = 100
-
- def __repr__(self):
- encoded_data = []
- for data in self.data:
- sub_data = []
- for value in data:
- if value is None:
- sub_data.append(-1)
- elif value >= 0 and value <= self.max_value:
- sub_data.append("%.1f" % float(value))
- else:
- raise DataOutOfRangeException()
- encoded_data.append(','.join(sub_data))
- return 'chd=t:' + '|'.join(encoded_data)
-
- @classmethod
- def scale_value(cls, value, range):
- # use float values instead of integers because we don't need an encode
- # map index
- scaled = cls.float_scale_value(value, range)
- clipped = cls.clip_value(scaled)
- Data.check_clip(scaled, clipped)
- return clipped
-
-
-class ExtendedData(Data):
-
- max_value = 4095
- enc_map = \
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'
-
- def __repr__(self):
- encoded_data = []
- enc_size = len(ExtendedData.enc_map)
- for data in self.data:
- sub_data = []
- for value in data:
- if value is None:
- sub_data.append('__')
- elif value >= 0 and value <= self.max_value:
- first, second = divmod(int(value), enc_size)
- sub_data.append('%s%s' % (
- ExtendedData.enc_map[first],
- ExtendedData.enc_map[second]))
- else:
- raise DataOutOfRangeException( \
- 'Item #%i "%s" is out of range' % (data.index(value), \
- value))
- encoded_data.append(''.join(sub_data))
- return 'chd=e:' + ','.join(encoded_data)
-
-
-# Axis Classes
-# -----------------------------------------------------------------------------
-
-
-class Axis(object):
-
- BOTTOM = 'x'
- TOP = 't'
- LEFT = 'y'
- RIGHT = 'r'
- TYPES = (BOTTOM, TOP, LEFT, RIGHT)
-
- def __init__(self, axis_index, axis_type, **kw):
- assert(axis_type in Axis.TYPES)
- self.has_style = False
- self.axis_index = axis_index
- self.axis_type = axis_type
- self.positions = None
-
- def set_index(self, axis_index):
- self.axis_index = axis_index
-
- def set_positions(self, positions):
- self.positions = positions
-
- def set_style(self, colour, font_size=None, alignment=None):
- _check_colour(colour)
- self.colour = colour
- self.font_size = font_size
- self.alignment = alignment
- self.has_style = True
-
- def style_to_url(self):
- bits = []
- bits.append(str(self.axis_index))
- bits.append(self.colour)
- if self.font_size is not None:
- bits.append(str(self.font_size))
- if self.alignment is not None:
- bits.append(str(self.alignment))
- return ','.join(bits)
-
- def positions_to_url(self):
- bits = []
- bits.append(str(self.axis_index))
- bits += [str(a) for a in self.positions]
- return ','.join(bits)
-
-
-class LabelAxis(Axis):
-
- def __init__(self, axis_index, axis_type, values, **kwargs):
- Axis.__init__(self, axis_index, axis_type, **kwargs)
- self.values = [str(a) for a in values]
-
- def __repr__(self):
- return '%i:|%s' % (self.axis_index, '|'.join(self.values))
-
-
-class RangeAxis(Axis):
-
- def __init__(self, axis_index, axis_type, low, high, **kwargs):
- Axis.__init__(self, axis_index, axis_type, **kwargs)
- self.low = low
- self.high = high
-
- def __repr__(self):
- return '%i,%s,%s' % (self.axis_index, self.low, self.high)
-
-# Chart Classes
-# -----------------------------------------------------------------------------
-
-
-class Chart(object):
- """Abstract class for all chart types.
-
- width are height specify the dimensions of the image. title sets the title
- of the chart. legend requires a list that corresponds to datasets.
- """
-
- BASE_URL = 'http://chart.apis.google.com/chart?'
- BACKGROUND = 'bg'
- CHART = 'c'
- ALPHA = 'a'
- VALID_SOLID_FILL_TYPES = (BACKGROUND, CHART, ALPHA)
- SOLID = 's'
- LINEAR_GRADIENT = 'lg'
- LINEAR_STRIPES = 'ls'
-
- def __init__(self, width, height, title=None, legend=None, colours=None,
- auto_scale=True, x_range=None, y_range=None,
- colours_within_series=None):
- if type(self) == Chart:
- raise AbstractClassException('This is an abstract class')
- assert(isinstance(width, int))
- assert(isinstance(height, int))
- self.width = width
- self.height = height
- self.data = []
- self.set_title(title)
- self.set_legend(legend)
- self.set_legend_position(None)
- self.set_colours(colours)
- self.set_colours_within_series(colours_within_series)
-
- # Data for scaling.
- self.auto_scale = auto_scale # Whether to automatically scale data
- self.x_range = x_range # (min, max) x-axis range for scaling
- self.y_range = y_range # (min, max) y-axis range for scaling
- self.scaled_data_class = None
- self.scaled_x_range = None
- self.scaled_y_range = None
-
- self.fill_types = {
- Chart.BACKGROUND: None,
- Chart.CHART: None,
- Chart.ALPHA: None,
- }
- self.fill_area = {
- Chart.BACKGROUND: None,
- Chart.CHART: None,
- Chart.ALPHA: None,
- }
- self.axis = []
- self.markers = []
- self.line_styles = {}
- self.grid = None
-
- # URL generation
- # -------------------------------------------------------------------------
-
- def get_url(self, data_class=None):
- url_bits = self.get_url_bits(data_class=data_class)
- return self.BASE_URL + '&'.join(url_bits)
-
- def get_url_bits(self, data_class=None):
- url_bits = []
- # required arguments
- url_bits.append(self.type_to_url())
- url_bits.append('chs=%ix%i' % (self.width, self.height))
- url_bits.append(self.data_to_url(data_class=data_class))
- # optional arguments
- if self.title:
- url_bits.append('chtt=%s' % self.title)
- if self.legend:
- url_bits.append('chdl=%s' % '|'.join(self.legend))
- if self.legend_position:
- url_bits.append('chdlp=%s' % (self.legend_position))
- if self.colours:
- url_bits.append('chco=%s' % ','.join(self.colours))
- if self.colours_within_series:
- url_bits.append('chco=%s' % '|'.join(self.colours_within_series))
- ret = self.fill_to_url()
- if ret:
- url_bits.append(ret)
- ret = self.axis_to_url()
- if ret:
- url_bits.append(ret)
- if self.markers:
- url_bits.append(self.markers_to_url())
- if self.line_styles:
- style = []
- for index in xrange(max(self.line_styles) + 1):
- if index in self.line_styles:
- values = self.line_styles[index]
- else:
- values = ('1', )
- style.append(','.join(values))
- url_bits.append('chls=%s' % '|'.join(style))
- if self.grid:
- url_bits.append('chg=%s' % self.grid)
- return url_bits
-
- # Downloading
- # -------------------------------------------------------------------------
-
- def download(self, file_name):
- opener = urllib2.urlopen(self.get_url())
-
- if opener.headers['content-type'] != 'image/png':
- raise BadContentTypeException('Server responded with a ' \
- 'content-type of %s' % opener.headers['content-type'])
-
- open(file_name, 'wb').write(opener.read())
-
- # Simple settings
- # -------------------------------------------------------------------------
-
- def set_title(self, title):
- if title:
- self.title = urllib.quote(title)
- else:
- self.title = None
-
- def set_legend(self, legend):
- """legend needs to be a list, tuple or None"""
- assert(isinstance(legend, list) or isinstance(legend, tuple) or
- legend is None)
- if legend:
- self.legend = [urllib.quote(a) for a in legend]
- else:
- self.legend = None
-
- def set_legend_position(self, legend_position):
- if legend_position:
- self.legend_position = urllib.quote(legend_position)
- else:
- self.legend_position = None
-
- # Chart colours
- # -------------------------------------------------------------------------
-
- def set_colours(self, colours):
- # colours needs to be a list, tuple or None
- assert(isinstance(colours, list) or isinstance(colours, tuple) or
- colours is None)
- # make sure the colours are in the right format
- if colours:
- for col in colours:
- _check_colour(col)
- self.colours = colours
-
- def set_colours_within_series(self, colours):
- # colours needs to be a list, tuple or None
- assert(isinstance(colours, list) or isinstance(colours, tuple) or
- colours is None)
- # make sure the colours are in the right format
- if colours:
- for col in colours:
- _check_colour(col)
- self.colours_within_series = colours
-
- # Background/Chart colours
- # -------------------------------------------------------------------------
-
- def fill_solid(self, area, colour):
- assert(area in Chart.VALID_SOLID_FILL_TYPES)
- _check_colour(colour)
- self.fill_area[area] = colour
- self.fill_types[area] = Chart.SOLID
-
- def _check_fill_linear(self, angle, *args):
- assert(isinstance(args, list) or isinstance(args, tuple))
- assert(angle >= 0 and angle <= 90)
- assert(len(args) % 2 == 0)
- args = list(args) # args is probably a tuple and we need to mutate
- for a in xrange(len(args) / 2):
- col = args[a * 2]
- offset = args[a * 2 + 1]
- _check_colour(col)
- assert(offset >= 0 and offset <= 1)
- args[a * 2 + 1] = str(args[a * 2 + 1])
- return args
-
- def fill_linear_gradient(self, area, angle, *args):
- assert(area in Chart.VALID_SOLID_FILL_TYPES)
- args = self._check_fill_linear(angle, *args)
- self.fill_types[area] = Chart.LINEAR_GRADIENT
- self.fill_area[area] = ','.join([str(angle)] + args)
-
- def fill_linear_stripes(self, area, angle, *args):
- assert(area in Chart.VALID_SOLID_FILL_TYPES)
- args = self._check_fill_linear(angle, *args)
- self.fill_types[area] = Chart.LINEAR_STRIPES
- self.fill_area[area] = ','.join([str(angle)] + args)
-
- def fill_to_url(self):
- areas = []
- for area in (Chart.BACKGROUND, Chart.CHART, Chart.ALPHA):
- if self.fill_types[area]:
- areas.append('%s,%s,%s' % (area, self.fill_types[area], \
- self.fill_area[area]))
- if areas:
- return 'chf=' + '|'.join(areas)
-
- # Data
- # -------------------------------------------------------------------------
-
- def data_class_detection(self, data):
- """Determines the appropriate data encoding type to give satisfactory
- resolution (http://code.google.com/apis/chart/#chart_data).
- """
- assert(isinstance(data, list) or isinstance(data, tuple))
- if not isinstance(self, (LineChart, BarChart, ScatterChart)):
- # From the link above:
- # Simple encoding is suitable for all other types of chart
- # regardless of size.
- return SimpleData
- elif self.height < 100:
- # The link above indicates that line and bar charts less
- # than 300px in size can be suitably represented with the
- # simple encoding. I've found that this isn't sufficient,
- # e.g. examples/line-xy-circle.png. Let's try 100px.
- return SimpleData
- else:
- return ExtendedData
-
- def _filter_none(self, data):
- return [r for r in data if r is not None]
-
- def data_x_range(self):
- """Return a 2-tuple giving the minimum and maximum x-axis
- data range.
- """
- try:
- lower = min([min(self._filter_none(s))
- for type, s in self.annotated_data()
- if type == 'x'])
- upper = max([max(self._filter_none(s))
- for type, s in self.annotated_data()
- if type == 'x'])
- return (lower, upper)
- except ValueError:
- return None # no x-axis datasets
-
- def data_y_range(self):
- """Return a 2-tuple giving the minimum and maximum y-axis
- data range.
- """
- try:
- lower = min([min(self._filter_none(s))
- for type, s in self.annotated_data()
- if type == 'y'])
- upper = max([max(self._filter_none(s)) + 1
- for type, s in self.annotated_data()
- if type == 'y'])
- return (lower, upper)
- except ValueError:
- return None # no y-axis datasets
-
- def scaled_data(self, data_class, x_range=None, y_range=None):
- """Scale `self.data` as appropriate for the given data encoding
- (data_class) and return it.
-
- An optional `y_range` -- a 2-tuple (lower, upper) -- can be
- given to specify the y-axis bounds. If not given, the range is
- inferred from the data: (0, <max-value>) presuming no negative
- values, or (<min-value>, <max-value>) if there are negative
- values. `self.scaled_y_range` is set to the actual lower and
- upper scaling range.
-
- Ditto for `x_range`. Note that some chart types don't have x-axis
- data.
- """
- self.scaled_data_class = data_class
-
- # Determine the x-axis range for scaling.
- if x_range is None:
- x_range = self.data_x_range()
- if x_range and x_range[0] > 0:
- x_range = (x_range[0], x_range[1])
- self.scaled_x_range = x_range
-
- # Determine the y-axis range for scaling.
- if y_range is None:
- y_range = self.data_y_range()
- if y_range and y_range[0] > 0:
- y_range = (y_range[0], y_range[1])
- self.scaled_y_range = y_range
-
- scaled_data = []
- for type, dataset in self.annotated_data():
- if type == 'x':
- scale_range = x_range
- elif type == 'y':
- scale_range = y_range
- elif type == 'marker-size':
- scale_range = (0, max(dataset))
- scaled_dataset = []
- for v in dataset:
- if v is None:
- scaled_dataset.append(None)
- else:
- scaled_dataset.append(
- data_class.scale_value(v, scale_range))
- scaled_data.append(scaled_dataset)
- return scaled_data
-
- def add_data(self, data):
- self.data.append(data)
- return len(self.data) - 1 # return the "index" of the data set
-
- def data_to_url(self, data_class=None):
- if not data_class:
- data_class = self.data_class_detection(self.data)
- if not issubclass(data_class, Data):
- raise UnknownDataTypeException()
- if self.auto_scale:
- data = self.scaled_data(data_class, self.x_range, self.y_range)
- else:
- data = self.data
- return repr(data_class(data))
-
- def annotated_data(self):
- for dataset in self.data:
- yield ('x', dataset)
-
- # Axis Labels
- # -------------------------------------------------------------------------
-
- def set_axis_labels(self, axis_type, values):
- assert(axis_type in Axis.TYPES)
- values = [urllib.quote(str(a)) for a in values]
- axis_index = len(self.axis)
- axis = LabelAxis(axis_index, axis_type, values)
- self.axis.append(axis)
- return axis_index
-
- def set_axis_range(self, axis_type, low, high):
- assert(axis_type in Axis.TYPES)
- axis_index = len(self.axis)
- axis = RangeAxis(axis_index, axis_type, low, high)
- self.axis.append(axis)
- return axis_index
-
- def set_axis_positions(self, axis_index, positions):
- try:
- self.axis[axis_index].set_positions(positions)
- except IndexError:
- raise InvalidParametersException('Axis index %i has not been ' \
- 'created' % axis)
-
- def set_axis_style(self, axis_index, colour, font_size=None, \
- alignment=None):
- try:
- self.axis[axis_index].set_style(colour, font_size, alignment)
- except IndexError:
- raise InvalidParametersException('Axis index %i has not been ' \
- 'created' % axis)
-
- def axis_to_url(self):
- available_axis = []
- label_axis = []
- range_axis = []
- positions = []
- styles = []
- index = -1
- for axis in self.axis:
- available_axis.append(axis.axis_type)
- if isinstance(axis, RangeAxis):
- range_axis.append(repr(axis))
- if isinstance(axis, LabelAxis):
- label_axis.append(repr(axis))
- if axis.positions:
- positions.append(axis.positions_to_url())
- if axis.has_style:
- styles.append(axis.style_to_url())
- if not available_axis:
- return
- url_bits = []
- url_bits.append('chxt=%s' % ','.join(available_axis))
- if label_axis:
- url_bits.append('chxl=%s' % '|'.join(label_axis))
- if range_axis:
- url_bits.append('chxr=%s' % '|'.join(range_axis))
- if positions:
- url_bits.append('chxp=%s' % '|'.join(positions))
- if styles:
- url_bits.append('chxs=%s' % '|'.join(styles))
- return '&'.join(url_bits)
-
- # Markers, Ranges and Fill area (chm)
- # -------------------------------------------------------------------------
-
- def markers_to_url(self):
- return 'chm=%s' % '|'.join([','.join(a) for a in self.markers])
-
- def add_marker(self, index, point, marker_type, colour, size, priority=0):
- self.markers.append((marker_type, colour, str(index), str(point), \
- str(size), str(priority)))
-
- def add_horizontal_range(self, colour, start, stop):
- self.markers.append(('r', colour, '0', str(start), str(stop)))
-
- def add_data_line(self, colour, data_set, size, priority=0):
- self.markers.append(('D', colour, str(data_set), '0', str(size), str(priority)))
-
- def add_marker_text(self, string, colour, data_set, data_point, size, priority=0):
- self.markers.append((str(string), colour, str(data_set), str(data_point), str(size), str(priority)))
-
- def add_vertical_range(self, colour, start, stop):
- self.markers.append(('R', colour, '0', str(start), str(stop)))
-
- def add_fill_range(self, colour, index_start, index_end):
- self.markers.append(('b', colour, str(index_start), str(index_end), \
- '1'))
-
- def add_fill_simple(self, colour):
- self.markers.append(('B', colour, '1', '1', '1'))
-
- # Line styles
- # -------------------------------------------------------------------------
-
- def set_line_style(self, index, thickness=1, line_segment=None, \
- blank_segment=None):
- value = []
- value.append(str(thickness))
- if line_segment:
- value.append(str(line_segment))
- value.append(str(blank_segment))
- self.line_styles[index] = value
-
- # Grid
- # -------------------------------------------------------------------------
-
- def set_grid(self, x_step, y_step, line_segment=1, \
- blank_segment=0):
- self.grid = '%s,%s,%s,%s' % (x_step, y_step, line_segment, \
- blank_segment)
-
-
-class ScatterChart(Chart):
-
- def type_to_url(self):
- return 'cht=s'
-
- def annotated_data(self):
- yield ('x', self.data[0])
- yield ('y', self.data[1])
- if len(self.data) > 2:
- # The optional third dataset is relative sizing for point
- # markers.
- yield ('marker-size', self.data[2])
-
-
-class LineChart(Chart):
-
- def __init__(self, *args, **kwargs):
- if type(self) == LineChart:
- raise AbstractClassException('This is an abstract class')
- Chart.__init__(self, *args, **kwargs)
-
-
-class SimpleLineChart(LineChart):
-
- def type_to_url(self):
- return 'cht=lc'
-
- def annotated_data(self):
- # All datasets are y-axis data.
- for dataset in self.data:
- yield ('y', dataset)
-
-
-class SparkLineChart(SimpleLineChart):
-
- def type_to_url(self):
- return 'cht=ls'
-
-
-class XYLineChart(LineChart):
-
- def type_to_url(self):
- return 'cht=lxy'
-
- def annotated_data(self):
- # Datasets alternate between x-axis, y-axis.
- for i, dataset in enumerate(self.data):
- if i % 2 == 0:
- yield ('x', dataset)
- else:
- yield ('y', dataset)
-
-
-class BarChart(Chart):
-
- def __init__(self, *args, **kwargs):
- if type(self) == BarChart:
- raise AbstractClassException('This is an abstract class')
- Chart.__init__(self, *args, **kwargs)
- self.bar_width = None
- self.zero_lines = {}
-
- def set_bar_width(self, bar_width):
- self.bar_width = bar_width
-
- def set_zero_line(self, index, zero_line):
- self.zero_lines[index] = zero_line
-
- def get_url_bits(self, data_class=None, skip_chbh=False):
- url_bits = Chart.get_url_bits(self, data_class=data_class)
- if not skip_chbh and self.bar_width is not None:
- url_bits.append('chbh=%i' % self.bar_width)
- zero_line = []
- if self.zero_lines:
- for index in xrange(max(self.zero_lines) + 1):
- if index in self.zero_lines:
- zero_line.append(str(self.zero_lines[index]))
- else:
- zero_line.append('0')
- url_bits.append('chp=%s' % ','.join(zero_line))
- return url_bits
-
-
-class StackedHorizontalBarChart(BarChart):
-
- def type_to_url(self):
- return 'cht=bhs'
-
-
-class StackedVerticalBarChart(BarChart):
-
- def type_to_url(self):
- return 'cht=bvs'
-
- def annotated_data(self):
- for dataset in self.data:
- yield ('y', dataset)
-
-
-class GroupedBarChart(BarChart):
-
- def __init__(self, *args, **kwargs):
- if type(self) == GroupedBarChart:
- raise AbstractClassException('This is an abstract class')
- BarChart.__init__(self, *args, **kwargs)
- self.bar_spacing = None
- self.group_spacing = None
-
- def set_bar_spacing(self, spacing):
- """Set spacing between bars in a group."""
- self.bar_spacing = spacing
-
- def set_group_spacing(self, spacing):
- """Set spacing between groups of bars."""
- self.group_spacing = spacing
-
- def get_url_bits(self, data_class=None):
- # Skip 'BarChart.get_url_bits' and call Chart directly so the parent
- # doesn't add "chbh" before we do.
- url_bits = BarChart.get_url_bits(self, data_class=data_class,
- skip_chbh=True)
- if self.group_spacing is not None:
- if self.bar_spacing is None:
- raise InvalidParametersException('Bar spacing is required ' \
- 'to be set when setting group spacing')
- if self.bar_width is None:
- raise InvalidParametersException('Bar width is required to ' \
- 'be set when setting bar spacing')
- url_bits.append('chbh=%i,%i,%i'
- % (self.bar_width, self.bar_spacing, self.group_spacing))
- elif self.bar_spacing is not None:
- if self.bar_width is None:
- raise InvalidParametersException('Bar width is required to ' \
- 'be set when setting bar spacing')
- url_bits.append('chbh=%i,%i' % (self.bar_width, self.bar_spacing))
- elif self.bar_width:
- url_bits.append('chbh=%i' % self.bar_width)
- return url_bits
-
-
-class GroupedHorizontalBarChart(GroupedBarChart):
-
- def type_to_url(self):
- return 'cht=bhg'
-
-
-class GroupedVerticalBarChart(GroupedBarChart):
-
- def type_to_url(self):
- return 'cht=bvg'
-
- def annotated_data(self):
- for dataset in self.data:
- yield ('y', dataset)
-
-
-class PieChart(Chart):
-
- def __init__(self, *args, **kwargs):
- if type(self) == PieChart:
- raise AbstractClassException('This is an abstract class')
- Chart.__init__(self, *args, **kwargs)
- self.pie_labels = []
- if self.y_range:
- warnings.warn('y_range is not used with %s.' % \
- (self.__class__.__name__))
-
- def set_pie_labels(self, labels):
- self.pie_labels = [urllib.quote(a) for a in labels]
-
- def get_url_bits(self, data_class=None):
- url_bits = Chart.get_url_bits(self, data_class=data_class)
- if self.pie_labels:
- url_bits.append('chl=%s' % '|'.join(self.pie_labels))
- return url_bits
-
- def annotated_data(self):
- # Datasets are all y-axis data. However, there should only be
- # one dataset for pie charts.
- for dataset in self.data:
- yield ('x', dataset)
-
- def scaled_data(self, data_class, x_range=None, y_range=None):
- if not x_range:
- x_range = [0, sum(self.data[0])]
- return Chart.scaled_data(self, data_class, x_range, self.y_range)
-
-
-class PieChart2D(PieChart):
-
- def type_to_url(self):
- return 'cht=p'
-
-
-class PieChart3D(PieChart):
-
- def type_to_url(self):
- return 'cht=p3'
-
-
-class VennChart(Chart):
-
- def type_to_url(self):
- return 'cht=v'
-
- def annotated_data(self):
- for dataset in self.data:
- yield ('y', dataset)
-
-
-class RadarChart(Chart):
-
- def type_to_url(self):
- return 'cht=r'
-
-
-class SplineRadarChart(RadarChart):
-
- def type_to_url(self):
- return 'cht=rs'
-
-
-class MapChart(Chart):
-
- def __init__(self, *args, **kwargs):
- Chart.__init__(self, *args, **kwargs)
- self.geo_area = 'world'
- self.codes = []
-
- def type_to_url(self):
- return 'cht=t'
-
- def set_codes(self, codes):
- self.codes = codes
-
- def get_url_bits(self, data_class=None):
- url_bits = Chart.get_url_bits(self, data_class=data_class)
- url_bits.append('chtm=%s' % self.geo_area)
- if self.codes:
- url_bits.append('chld=%s' % ''.join(self.codes))
- return url_bits
-
-
-class GoogleOMeterChart(PieChart):
- """Inheriting from PieChart because of similar labeling"""
-
- def __init__(self, *args, **kwargs):
- PieChart.__init__(self, *args, **kwargs)
- if self.auto_scale and not self.x_range:
- warnings.warn('Please specify an x_range with GoogleOMeterChart, '
- 'otherwise one arrow will always be at the max.')
-
- def type_to_url(self):
- return 'cht=gom'
-
-
-class QRChart(Chart):
-
- def __init__(self, *args, **kwargs):
- Chart.__init__(self, *args, **kwargs)
- self.encoding = None
- self.ec_level = None
- self.margin = None
-
- def type_to_url(self):
- return 'cht=qr'
-
- def data_to_url(self, data_class=None):
- if not self.data:
- raise NoDataGivenException()
- return 'chl=%s' % urllib.quote(self.data[0])
-
- def get_url_bits(self, data_class=None):
- url_bits = Chart.get_url_bits(self, data_class=data_class)
- if self.encoding:
- url_bits.append('choe=%s' % self.encoding)
- if self.ec_level:
- url_bits.append('chld=%s|%s' % (self.ec_level, self.margin))
- return url_bits
-
- def set_encoding(self, encoding):
- self.encoding = encoding
-
- def set_ec(self, level, margin):
- self.ec_level = level
- self.margin = margin
-
-
-class ChartGrammar(object):
-
- def __init__(self):
- self.grammar = None
- self.chart = None
-
- def parse(self, grammar):
- self.grammar = grammar
- self.chart = self.create_chart_instance()
-
- for attr in self.grammar:
- if attr in ('w', 'h', 'type', 'auto_scale', 'x_range', 'y_range'):
- continue # These are already parsed in create_chart_instance
- attr_func = 'parse_' + attr
- if not hasattr(self, attr_func):
- warnings.warn('No parser for grammar attribute "%s"' % (attr))
- continue
- getattr(self, attr_func)(grammar[attr])
-
- return self.chart
-
- def parse_data(self, data):
- self.chart.data = data
-
- @staticmethod
- def get_possible_chart_types():
- possible_charts = []
- for cls_name in globals().keys():
- if not cls_name.endswith('Chart'):
- continue
- cls = globals()[cls_name]
- # Check if it is an abstract class
- try:
- a = cls(1, 1, auto_scale=False)
- del a
- except AbstractClassException:
- continue
- # Strip off "Class"
- possible_charts.append(cls_name[:-5])
- return possible_charts
-
- def create_chart_instance(self, grammar=None):
- if not grammar:
- grammar = self.grammar
- assert(isinstance(grammar, dict)) # grammar must be a dict
- assert('w' in grammar) # width is required
- assert('h' in grammar) # height is required
- assert('type' in grammar) # type is required
- chart_type = grammar['type']
- w = grammar['w']
- h = grammar['h']
- auto_scale = grammar.get('auto_scale', None)
- x_range = grammar.get('x_range', None)
- y_range = grammar.get('y_range', None)
- types = ChartGrammar.get_possible_chart_types()
- if chart_type not in types:
- raise UnknownChartType('%s is an unknown chart type. Possible '
- 'chart types are %s' % (chart_type, ','.join(types)))
- return globals()[chart_type + 'Chart'](w, h, auto_scale=auto_scale,
- x_range=x_range, y_range=y_range)
-
- def download(self):
- pass
-
diff --git a/snakegame/snake.py b/snakegame/snake.py
index 89e97da..539498f 100644
--- a/snakegame/snake.py
+++ b/snakegame/snake.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
from __future__ import division
import sys
@@ -14,10 +12,10 @@ import traceback
from common import *
-class SnakeEngine(object):
+class Engine(object):
def __init__(self, rows, columns, n_apples, wrap=False, results=False,
*args, **kwargs):
- super(SnakeEngine, self).__init__(*args, **kwargs)
+ super(Engine, self).__init__(*args, **kwargs)
self.wrap = wrap
self.bots = {}
diff --git a/snakegame/stats.py b/snakegame/stats.py
deleted file mode 100644
index aaacfcf..0000000
--- a/snakegame/stats.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-from collections import defaultdict
-from pngchart import SimpleLineChart
-#from pygooglechart import SimpleLineChart
-from colour import hash_colour
-
-WIDTH = 800
-HEIGHT = 300
-RESULTS_FILE = 'results.csv'
-
-def main():
- data = {}
- order = []
- snakes = []
- for line in open(RESULTS_FILE):
- game_id, name, length, life = line[:-1].split(',')
- game_id = int(game_id)
- length = int(length)
- life = float(life)
-
- if name not in data:
- snakes.append(name)
- data[name] = {}
-
- if game_id not in order:
- order.append(game_id)
-
- data[name][game_id] = (length, life)
-
- length_data = []
- time_data = []
- colours = []
- for name in snakes:
- time_series = []
- length_series = []
-
- for game_id in order:
- length, time = data[name].get(game_id, (None, None))
- time_series.append(time)
- length_series.append(length)
-
- colours.append('%02X%02X%02X' % hash_colour(name))
-
- time_data.append(time_series)
- length_data.append(length_series)
-
- for filename, data in (('length_chart.png', length_data),
- ('time_chart.png', time_data)):
- chart = SimpleLineChart(WIDTH, HEIGHT, colours=colours, legend=snakes)
- for series in data:
- chart.add_data(series)
- print 'Updating', filename, '... ',
- sys.stdout.flush()
- chart.download(filename)
- print 'done!'
-
-if __name__ == '__main__':
- main()
-