Commits

Ian George  committed 191a9f2

initial commit

  • Participants

Comments (0)

Files changed (17)

+.pyc$
+.wav$
+.mp3$
+.svn
+.orig
+^parts/
+^bin/
+^build/
+^include/
+^lib/
+^src/
+^develop-eggs/
+^downloads/
+^eggs/
+^parts/
+^.installed.cfg
+db$
+^media/cache/
+^media/medialibrary/
+^static_serve/
+^website/quiet
+^front_end_build/
+^log/
+^logs/
+^www/
+^IMAGES_IMPORT/

File demo/fire.bmp

Added
New image

File demo/fire.py

+import random
+
+class Fire(object):
+    def __init__(self, grid, iterations):
+        self.grid = grid
+        self.iteration = iterations
+        self.palette = "fire"
+        self.palette_length = 254
+        for x in xrange(len(self.grid[0])):
+            self.grid[-1][x] = random.randint(0,self.palette_length)
+
+    def get_grid(self):
+        height = len(self.grid)
+        width = len(self.grid[0])
+        for y in xrange(height-1):
+            uy = (y+1) % (height)
+            uy2 = (y+2) % (height)
+            for x in xrange(width):
+                c1 = self.grid[uy][(x + width - 1) % width]
+                c2 = self.grid[uy][x]
+                c3 = self.grid[uy][(x+1) % width]
+                c4 = self.grid[uy2][x]
+                
+                self.grid[y][x] = int((c1+c2+c3+c4)/4.05)
+
+        for x in xrange(len(self.grid[0])):
+            self.grid[-1][x] = random.randint(0,self.palette_length)
+        
+        self.iteration -= 1
+
+        return self.grid
+
+ 

File demo/main.py

+from __future__ import division
+import os
+import sys
+import random
+import math
+import cProfile
+from optparse import OptionParser
+
+import pygame
+from pygame.locals import *
+from pygame import time
+
+from plasma import Plasma
+from fire import Fire
+from rotate import Rotate
+from matrix import get_2d_array
+
+APP_NAME = "Old school demo v0.1"
+TILE_SIZE = 10
+WINDOW_SIZE = (640,480)
+FILL_SCREEN = True
+PROFILE_ITERATIONS = 100
+
+FONT_SIZE = 16
+###################################################################
+# Problems
+# Black line on plasma
+# Rotate doesn't get back to flat
+###################################################################
+
+class DemoController(object):
+    def __init__(self, profile=False):
+        """If profile is set, the simulation will run for 100 iterations and quit displaying cProfile output"""
+        #pygame
+        if FILL_SCREEN:
+            self._screen = pygame.display.set_mode((0,0),FULLSCREEN)
+        else:
+            self._screen = pygame.display.set_mode((WINDOW_SIZE[0],WINDOW_SIZE[1]), FILL_SCREEN)
+        self._font = pygame.font.SysFont("DejaVu Sans Mono", FONT_SIZE)
+        
+        self.bg = pygame.display.get_surface().convert()
+
+        screen_size = self._screen.get_size()
+        self.grid_size = (int(screen_size[0]/TILE_SIZE), int(screen_size[1]/TILE_SIZE))
+        self.grid = get_2d_array(*self.grid_size)
+
+        self.paused = False
+        self.clock = time.Clock()
+        self.profile = profile
+        self.fps = 0
+
+        #cache spectrum colours, looking up on the fly is slow
+        self.fire_spectrum = []
+        spectrum = pygame.image.load('fire.bmp')
+        for i in xrange(0,255):
+            c = spectrum.get_at( (i, 1) )
+            h = pygame.Color(*c)
+            self.fire_spectrum.append(h)
+
+        # (Class, Iterations, Transition)
+        self.effects = [
+            (Plasma,300, {}),
+            (Rotate, 180, {'tile_size':TILE_SIZE}),
+            (Fire, 150, {}),
+            ]
+        self.iteration = 0
+
+        self.effect_index = 0
+        self.effect_object = self.get_effect()
+
+        self.render()
+
+    def get_effect(self):
+        effect = self.effects[self.effect_index]
+        return effect[0](self.grid, effect[1], **effect[2])
+
+    def update(self):
+        if not self.effect_object:
+            self.effect_object = self.get_effect()
+        self.grid = self.effect_object.get_grid()
+        self.render()
+
+        if self.effect_object.iteration < 1:
+            self.effect_index += 1
+            if self.effect_index > len(self.effects)-1:
+                self.effect_index = 0
+            self.effect_object = self.get_effect()
+
+    def render(self):
+        spec = self.fire_spectrum
+        grid = self.grid
+        surf = self.bg
+        rect = pygame.Rect(0,0,TILE_SIZE,TILE_SIZE)
+
+        for y, row in enumerate(grid):
+            rect.top = y*TILE_SIZE
+            for x, val in enumerate(row):
+                rect.left = x * TILE_SIZE
+                self._screen.fill(spec[val], rect)
+        #self._screen.blit(self.bg, (0,0))
+        pygame.display.flip()
+        self.iteration += 1
+
+    def input(self,events):
+        """Deals with kepxesses and mouse events"""
+        for event in events:
+            if hasattr(event, 'unicode') and event.unicode == 'q':
+                sys.exit(0)
+
+            if hasattr(event, 'unicode') and event.unicode == 'p':
+                self.paused = not self.paused
+                return
+
+            if hasattr(event, 'unicode') and event.unicode == 'n':
+                if self.paused:
+                    self.update()
+
+    def mainloop(self):
+        """Main loop!"""
+        done = False
+        while not done:
+            self.input(pygame.event.get())
+            if not self.paused:
+                self.clock.tick()
+                self.fps = self.clock.get_fps()            
+                self.update()
+            if self.profile and self.iteration > PROFILE_ITERATIONS:
+                done = True
+
+if __name__ == "__main__":
+    parser = OptionParser()
+    parser.add_option("--profile", action="store_true", dest="profile", default="False", help="Profile 100 iterations (default: False)")
+    (options, args) = parser.parse_args()
+
+    pygame.init()
+    pygame.display.set_caption(APP_NAME)
+    pc = DemoController()
+
+    if options.profile is True:
+        pc.profile = True
+        cProfile.run('pc.mainloop()')
+        pygame.quit()
+        raw_input("Press Enter to exit")        
+    else:
+        pc.mainloop()
+        pygame.quit()
+

File demo/matrix.py

+from __future__ import division
+import math
+from array import array
+
+class Transform(object):
+    def __init__(self, grid):
+        self.original_grid = grid
+        self.h,self.w = len(grid),len(grid[0])    
+
+    def apply_matrix_wrap(self, transformation, cycle):
+        #get a new grid to map on to
+        new_grid = get_2d_array(self.w, self.h)
+        w = self.w
+        h = self.h
+        m1,m2,m3 = transformation[0]
+        m4,m5,m6 = transformation[1]
+        colour_index = (cycle*4) # 0-254
+
+        m3 = int(m3)
+        m6 = int(m6)
+
+        #map the x,y values to the new ones with a matrix multiplication
+        for y, row in enumerate(self.original_grid):
+            m2y = int(m2*y) + m3
+            m5y = int(m5*y) + m6
+            for x, val in enumerate(row):
+                nx = (int(m1*x) + m2y) % w
+                ny = (int(m4*x) + m5y) % h
+                new_grid[ny][nx] = (colour_index + val)%255 #map old val to new point, incrementing the colour index if it's > 0
+                
+        return new_grid
+
+    def apply_matrix_clip(self, transformation, cycle):
+         #get a new grid to map on to
+         new_grid = get_2d_array(self.w, self.h)
+         o_grid = self.original_grid
+         w = self.w
+         h = self.h
+         m1,m2,m3 = transformation[0]
+         m4,m5,m6 = transformation[1]
+         colour_index = cycle*4
+         
+         m3 = int(m3)
+         m6 = int(m6)
+
+         for y, row in enumerate(o_grid):
+             m2y = int(m2*y) + m3
+             m5y = int(m5*y) + m6
+             for x, val in enumerate(row):
+                 #map the x,y values to the new ones with a matrix multiplication
+                 nx = int(m1*x) + m2y
+                 ny = int(m4*x) + m5y
+
+                 if (nx > -1 and nx < w) and (ny > -1 and ny < h):
+                    new_grid[ny][nx] = (val + colour_index) % 255 #map old val to new point, incrementing the colour index if it's > 0
+
+         return new_grid
+
+def get_2d_array(w, h):
+    return [[0] * w for i in xrange(h)]
+
+def translate_shear_matrix(tx, ty, shearx, sheary):
+    M = (
+        (1, shearx, tx),
+        (sheary, 1, ty),
+        (0, 0, 1)
+        )
+    return M
+
+def scale_matrix(scalex, scaley):
+    M = (
+        (scalex,0,-scalex/2),
+        (0,scaley,-scaley/2),
+        (0,0,1)
+        )
+    return M
+
+def rotation_matrix(angle, centre_x, centre_y, scale_by):
+    angle = angle / (180/math.pi)
+    scale = scale_by
+    M = (
+        (math.cos(angle),           -math.sin(angle)*scale_by, centre_x - math.cos(angle) * centre_x + math.sin(angle) * centre_y ),
+        (math.sin(angle)*scale_by,  math.cos(angle),           centre_y - math.sin(angle) * centre_x - math.cos(angle) * centre_y ),
+        (0,                       0,                       1                                                                )
+        )
+    return M

File demo/plasma.py

+from __future__ import division
+import math
+from matrix import Transform, translate_shear_matrix
+
+
+class Plasma(object):
+    def __init__(self, grid, iterations):
+        self.grid = grid
+        self.iteration = iterations
+        self.iter_total = iterations
+        self.palette = "fire"
+        self.h, self.w = len(grid),len(grid[0])
+        degree_inc_w = math.radians((180 / self.w) * 2)
+        degree_inc_h = math.radians((180 / self.h) * 2)
+
+        sin = math.sin(1/(180 / math.pi))
+        sin2 = math.sin(2/(180 / math.pi))
+        tx, ty =  sin * self.w, 0
+        shearx, sheary = -sin2, 0
+        m = translate_shear_matrix(tx, ty, shearx, sheary)
+        self.T = Transform(self.grid) 
+
+        for y in xrange(self.h):
+            for x in xrange(self.w):
+                val = math.sin(x * degree_inc_w) + math.cos(y * degree_inc_h) 
+                self.grid[y][x] = int((val / 2) * 254)
+
+    def get_grid(self):
+        offset = self.iter_total-self.iteration
+        sin = math.sin(math.radians(offset))
+        sin2 = math.sin(math.radians(offset*2))
+        tx, ty =  sin * self.w, 0
+        shearx, sheary = -sin2, 0
+        m = translate_shear_matrix(tx, ty, shearx, sheary)
+        new_grid = self.T.apply_matrix_wrap(m, offset)
+
+        self.iteration -= 1
+        return new_grid

File demo/rotate.py

+from matrix import Transform, rotation_matrix
+
+class Rotate(object):
+    def __init__(self, grid, iterations, tile_size=1):
+        self.grid = grid 
+        self.w = len(grid[0])
+        self.h = len(grid)
+        self.ts = tile_size
+
+        self.iteration = iterations
+        self.iter_total = iterations
+
+        m = rotation_matrix(2.0, self.w/2.0, self.h/2.0, 2.5)
+        self.T = Transform(grid)
+
+    def get_grid(self):
+        offset = self.iter_total - self.iteration
+        m = rotation_matrix(offset*2.0, self.w/2.0, self.h/2.0, 2.5)
+        new_grid = self.T.apply_matrix_clip(m, offset)
+
+        self.iteration -= 1
+        return new_grid

File fractal/fire.bmp

Added
New image

File fractal/fractal.py

+ #! /usr/bin/env python
+ # Plot random pixels on the screen.
+from __future__ import division 
+import pygame
+import random
+  
+ # Window dimensions
+screen_width = 600
+screen_height = 600
+max_iterations = 30
+
+screen = pygame.display.set_mode((screen_width, screen_height))
+clock = pygame.time.Clock()
+zoom_factor = 10
+
+spectrum_img = pygame.image.load('fire.bmp')
+spectrum = []
+for i in xrange(255):
+    c = spectrum_img.get_at( (i, 1) )
+    h = pygame.Color(c[0],c[1], c[2])
+    spectrum.append(h)
+
+
+class Brot(object):
+    def __init__(self, re, im, width, height):
+        self.set_range(re, im, width, height)
+        self.mouse_pos = (0,0)
+
+    def set_range(self, re, im, width, height):
+        aspect =  height/width
+        self.MinRe = re
+        self.MaxRe = re + (height * (1/aspect))
+        self.MinIm = im
+        self.MaxIm = im + (width * aspect)
+        self.Re_factor = (self.MaxRe-self.MinRe)/(screen_width-1);
+        self.Im_factor = (self.MaxIm-self.MinIm)/(screen_height -1);
+        print "Set Range (%s,%s) %s %s" % (re, im, width, height)
+
+    def zoom(self, x, y):
+        print x,y
+        print self.MinRe, self.MinIm
+        print self.MaxRe, self.MaxIm
+        old_w = self.MaxRe - self.MinRe
+        old_h = self.MaxIm - self.MinIm
+        x_pos = self.MinRe + (old_w/screen_width) * x
+        y_pos = self.MinIm + (old_h/screen_height) * (screen_height - y)
+
+        new_w = old_w/zoom_factor
+        new_h = old_h/zoom_factor
+
+        new_l = x_pos - new_w/2
+        new_t = y_pos - new_h/2
+
+        self.set_range(new_l,new_t,new_w,new_h)
+
+    def mandel(self, c):
+        z=0
+        inside = True
+        for h in range(0,max_iterations):
+            z = z**2 + c
+            if abs(z) > 4:
+                inside = False
+                break
+        if inside:
+            return 0
+        else:
+            return int(h*8.5)
+
+
+    def draw(self):
+        for y in xrange(screen_height):
+            c_im = self.MaxIm - y * self.Im_factor
+            for x in xrange(screen_width):
+                c_re = self.MinRe + x * self.Re_factor
+                screen.set_at((x, y), spectrum[self.mandel(complex(c_re,c_im))])
+            pygame.display.flip()
+
+
+fractal = Brot(-2.0, -2.0, 4, 4)
+running = True
+first = True
+while running:
+    for event in pygame.event.get():
+        if event.type == pygame.QUIT:
+            break
+        if event.type == pygame.MOUSEMOTION:
+            fractal.mouse_pos = event.pos
+        if event.type == pygame.MOUSEBUTTONUP:
+            fractal.zoom(*fractal.mouse_pos)
+            fractal.draw()
+
+    if first:
+        fractal.draw()
+        first = False
+
+    
+import random
+
+class LifeEngine(object):
+    """
+    dimensions: the number of cells to calculate for
+    percent_fill: the percentage of cells to randomise on init
+    """
+    def __init__(self, dimensions, percent_fill):
+        self._width = dimensions[0]
+        self._height = dimensions[1]
+        self.cells = self.get_2d_array()
+        self.heatmap = self.get_2d_array()
+        self.living_cells = []
+        self.iteration = 1
+
+        starting_cells = int(self._width * self._height * (percent_fill/100.0))
+        for i in xrange(starting_cells):
+            self.cell_born(self.cells, random.randint(0,self._width-1), random.randint(0,self._height-1))
+
+    def get_2d_array(self):
+        return [[0 for x in xrange(self._width)] for y in xrange(self._height)]
+
+    def cell_born(self, data, x, y):
+        left = x - 1
+        if left < 0:
+            left = self._width - 1
+		
+        right = x + 1
+        if right >= self._width:
+            right = 0
+		
+        top = y - 1
+        if top < 0:
+            top = self._height - 1
+		
+        bottom = y + 1
+        if bottom >= self._height:
+            bottom = 0
+
+        data[top][left] += 1
+        data[top][x] += 1
+        data[top][right] += 1
+        
+        data[y][left] += 1
+        data[y][x] += 32
+        data[y][right] += 1
+
+        data[bottom][left] += 1
+        data[bottom][x] += 1
+        data[bottom][right] += 1
+        
+        self.living_cells.append((x, y))
+        
+    def cell_dies(self, x, y):
+        if self.cells[x][y] >= 32:
+            left = x - 1
+            if left < 0:
+                left = self._width - 1
+			
+            right = x + 1
+            if right >= self._width:
+                right = 0
+			
+            top = y - 1
+            if top < 0:
+                top = self.height - 1
+			
+            bottom = y + 1
+            if bottom >= self._height:
+                bottom = 0
+
+
+            self.cells[top][left] -= 1
+            self.cells[top][x] -= 1
+            self.cells[top][right] -= 1
+            
+            self.cells[y][left] -= 1
+            self.cells[y][x] -= 32
+            self.cells[y][right] -= 1
+            
+            self.cells[bottom][left] -= 1
+            self.cells[bottom][x] -= 1
+            self.cells[bottom][right] -= 1
+
+            if (x, y) in self.living_cells:
+                self.living_cells.remove((x, y))
+
+    def next_iteration(self, cells):
+        """
+        """
+
+        next_gen = self.get_2d_array()
+        self.living_cells = []
+
+        hm = self.heatmap
+        for y in xrange(self._height):
+            row = self.cells[y]
+            for x in xrange(self._width):
+                cell = row[x]
+                if cell == 3 or cell == 34 or cell == 35:
+                    self.cell_born(next_gen, x, y)
+                    if hm[y][x] < 19:
+                        hm[y][x] += 2
+                else:
+                    if hm[y][x] > 0:
+                        hm[y][x] -= 1
+
+        self.cells = next_gen
+            
+
+    def update(self):
+        self.next_iteration(self.cells)
+        self.iteration += 1
+
+    def tile_range(self):
+        """Outputs the entire tile range"""
+        for y in xrange(self._height):
+            for x in xrange(self._width):
+                yield(x,y)    
+import os, sys
+import pygame
+import random
+from pygame.locals import *
+from pygame import time
+
+import cProfile
+from optparse import OptionParser
+
+from Life import LifeEngine
+"""
+Game of Life sim
+
+TODO:
+FPS limit
+"""
+
+APP_NAME = "Game of life v0.5"
+TILE_SIZE = 4
+WINDOW_SIZE = (640,480)
+FILL_SCREEN = True
+PERCENT_START_FILL = 5
+FPS_LIMIT = 5
+
+FONT_SIZE = 16
+TILE_HEAT_COLOUR = (75,75,255,200)
+TILE_COLOUR = (200,200,200)
+BG_HEAT_ALPHA = 100
+
+class LifeController(object):
+    """ Controller class for the simulation, handles I/O, drawing & feeding input to the engine"""
+    def __init__(self, profile=False):
+        """If profile is set, the simulation will run for 100 iterations and quit displaying cProfile output"""
+        #pygame
+        if FILL_SCREEN:
+            self._screen = pygame.display.set_mode((0,0),FULLSCREEN)
+        else:
+            self._screen = pygame.display.set_mode((WINDOW_SIZE[0],WINDOW_SIZE[1]))
+        self._font = pygame.font.SysFont("DejaVu Sans Mono", FONT_SIZE)
+        screen_size = self._screen.get_size()
+
+        #internal state
+        self.tiles = (screen_size[0]/TILE_SIZE, (screen_size[1]-FONT_SIZE)/TILE_SIZE)
+        self.life_engine = LifeEngine(self.tiles, PERCENT_START_FILL)
+        self.surface = pygame.display.get_surface()
+        self.mouse_pos = (0,0)
+        self.mouse_button = False
+        self.paused = True
+        self.clock = time.Clock()
+        self.profile = profile
+        self.heat = True
+
+        #gfx
+        self.tile_image = pygame.Surface((TILE_SIZE,TILE_SIZE), flags=SRCALPHA)
+        self.tile_image.fill(TILE_COLOUR)
+        self.tile_hm_image = pygame.Surface((TILE_SIZE,TILE_SIZE), flags=SRCALPHA)
+        self.tile_hm_image.fill(TILE_HEAT_COLOUR)
+
+        self.background = pygame.Surface(screen_size)
+        self.background = self.background.convert_alpha()
+        self.background.fill((0, 0, 0))
+
+        #cache spectrum colours, looking up on the fly is slow
+        self.heat_spectrum = []
+        spectrum = pygame.image.load('spectrum.bmp')
+        for i in xrange(0,255):
+            c = spectrum.get_at( (i, 1) )
+            h = pygame.Color(c[0],c[1], c[2], BG_HEAT_ALPHA)
+            self.heat_spectrum.append(h)
+
+        #stats
+        self.fps = 0
+        self._cell_count = 0
+
+        self.draw(False, self.paused)
+
+
+    def draw(self, update=True, draw_help=False, draw_bg=True):
+        """Handles the main part of the drawing function - putting the cells on the screen
+        if update=False then it will draw to the screen without updating to the next iteration
+        """
+        if update:
+            self.life_engine.update()
+
+        if draw_bg:
+            self._draw_bg()
+        
+        for (x, y) in self.life_engine.living_cells:
+            rect = ((x-1) * TILE_SIZE, (y-1) * TILE_SIZE, TILE_SIZE, TILE_SIZE)
+            if self.heat:
+                self.surface.blit(self.tile_hm_image, rect)
+            else:
+                self.surface.blit(self.tile_image, rect)
+
+        self._draw_stats()
+
+        if draw_help:
+            self.draw_help()
+
+        pygame.display.flip()
+
+    def draw_help(self):
+        """Draws the help text on the screen"""
+        lines = 7
+        if self.paused:
+            paused_text = "p - pauses simulation (currently paused)"
+        else:
+            paused_text = "p - pauses simulation"
+
+        if self.heat:
+            heat_text = "h - toggles heat map (currently on)"
+        else:
+            heat_text = "h - toggles heat map (currently off)"
+
+        self.__print_help_text(APP_NAME, 1, lines)
+        self.__print_help_text(paused_text, 2, lines)
+        self.__print_help_text("r - restarts simulation", 3, lines)
+        self.__print_help_text(heat_text, 4, lines)
+        self.__print_help_text("q - quits", 5, lines)
+        self.__print_help_text("", 6, lines)
+        self.__print_help_text("Drawing with the mouse when paused adds cells", 7, lines)
+        pygame.display.flip()
+
+    def _draw_stats(self):
+        """Draws the statistics to the screen"""
+        t_width = self.__print_status_text(APP_NAME, 5)
+        self.__print_status_text("Day %s" % self.life_engine.iteration, t_width + 100)
+        self.__print_status_text("%.2f FPS" % self.fps, t_width + 250)
+        self.__print_status_text("%s Cells" % len(self.life_engine.living_cells), t_width + 400)
+
+    def __print_help_text(self, txt, line, total_lines):
+        """Helper function to print a line of text for the help info"""
+        text_width = 200
+        line_height = 25
+        text_left = self.life_engine._width / 2 - text_width / 2
+        text_top = self.life_engine._height / 2 - total_lines * line_height / 2 + line * line_height
+        text = self._font.render(txt, True, (255, 255, 255), (0,0,0))
+        rect = text.get_rect()
+        rect.left = text_left
+        rect.top = text_top
+        self.surface.blit(text,rect)
+
+    def __print_status_text(self, txt, left):
+        """Helper function to print a block of text for status bar"""
+        text = self._font.render(txt, True, (200, 20, 20), (0, 0, 0))
+        rect = text.get_rect()
+        rect.bottom = self.surface.get_rect().height
+        rect.left = left
+        fill_rect = rect
+        fill_rect.width += 10
+        fill_rect.left -= 5
+        self.surface.fill((0,0,0), fill_rect)
+        self.surface.blit(text, rect)
+        return rect.right
+
+    def _draw_bg(self):
+        """Draws either the heatmap or just fills the bg in black in place"""
+        if self.heat:
+            hm = self.life_engine.heatmap
+            w = self.life_engine._width
+            h = self.life_engine._height
+            bg = self.background
+            bg.fill( (0,0,0) )
+
+            TILE_MULT = 2
+            t_size = TILE_SIZE*TILE_MULT
+            t_offset = (TILE_SIZE/2) - (t_size/2)
+
+            for y in xrange(h):
+                row = hm[y]
+                for x in xrange(w):
+                    r = (((x-1) * TILE_SIZE) + t_offset, ((y-1) * TILE_SIZE) + t_offset, t_size, t_size)
+                    ht = row[x]
+                    if ht > 0:
+                        bg.fill(self._get_heat(ht), r)
+                    
+        self.surface.blit(self.background, (0, 0))
+        
+
+    def _get_heat(self, heat):
+        """Returns the color for a specific heat map value"""
+        h = int((heat/20.0) * 254)
+        return self.heat_spectrum[h]
+
+    def create_current_cell(self):
+        """Creates a cell under the mouse pointer"""
+        if self.mousepos:
+            tilex = self.mousepos[0]/TILE_SIZE
+            tiley = self.mousepos[1]/TILE_SIZE
+            if not (tilex,tiley) in self.life_engine.living_cells:
+                self.life_engine.cell_born(self.life_engine.cells, tilex, tiley)
+
+    def input(self,events):
+        """Deals with kepresses and mouse events"""
+        for event in events:
+            if hasattr(event, 'unicode') and event.unicode == 'q':
+                sys.exit(0)
+
+            if hasattr(event, 'unicode') and event.unicode == 'r':
+                self.life_engine.__init__(self.tiles, PERCENT_START_FILL)
+                self.paused = True
+                self.draw(False, True)
+
+            if hasattr(event, 'unicode') and event.unicode == 'h':
+                self.background.fill( (0,0,0) )
+                self.heat = not self.heat
+                if self.paused:
+                    self.draw(False, True)
+
+            if hasattr(event, 'unicode') and event.unicode == 'p':
+                self.paused = not self.paused
+                if self.paused:
+                    self.draw_help()
+
+            if hasattr(event, 'unicode') and event.unicode == 'n':
+                if self.paused:
+                    self.draw()
+
+            if event.type == pygame.MOUSEMOTION:
+                self.mousepos = event.pos
+                b1,b2,b3 = pygame.mouse.get_pressed()
+
+                if self.paused and b1:
+                    self.create_current_cell()
+                    self.draw(False, False, False)
+
+    def mainloop(self):
+        """Main loop!"""
+        done = False
+        while not done:
+            self.input(pygame.event.get())
+            if not self.paused:
+                self.clock.tick()
+                self.fps = self.clock.get_fps()            
+                self.draw()
+            if self.profile and self.life_engine.iteration > 100:
+                done = True
+
+
+if __name__ == "__main__":
+    parser = OptionParser()
+    parser.add_option("--profile", action="store_true", dest="profile", default="False", help="Profile 100 iterations (default: False)")
+    (options, args) = parser.parse_args()
+
+    pygame.init()
+    pygame.display.set_caption(APP_NAME)
+    lc = LifeController()
+
+    if options.profile is True:
+        lc.profile = True
+        cProfile.run('lc.mainloop()')
+        pygame.quit()
+        raw_input("Press Enter to exit")        
+    else:
+        lc.mainloop()
+        pygame.quit()
+

File gol/main0.1.py

+import os, sys
+import pygame
+from pygame.locals import *
+
+"""
+Game of Life sim
+"""
+pygame.init()
+
+pygame.display.set_caption('Game of life')
+
+tile_image = pygame.Surface((15,15))
+tile_image.fill((50,255,100))
+tile_size = 15
+
+class GameOfLife(object):
+    def __init__(self):        
+        self._screen = pygame.display.set_mode((0,0), FULLSCREEN)
+        screen_size = self._screen.get_size()
+        self.tiles_total = (screen_size[0]/tile_size, screen_size[1]/tile_size)
+        self.background = pygame.Surface(screen_size)
+        self.background = self.background.convert()
+        self.mousepos = (0,0)
+
+        self.initial_state = [
+            (16,15),
+            (16,16),
+            (16,17),
+            (16,20),
+            (20,19),
+            (20,18),
+            (20,16),
+            (21,19),
+            (22,19),
+            (23,18),
+            (20,17),
+            (18,17),
+            (17,17),
+        ]
+        self.state = {True:[],False:[]}
+        self.active = True
+        self.paused = True
+
+        for y in range(self.tiles_total[1]):
+            self.state[self.active].append([])
+            self.state[not self.active].append([])
+            for x in range(self.tiles_total[0]):
+                if (x,y) in self.initial_state:
+                    self.state[self.active][y].append(True)
+                else:
+                    self.state[self.active][y].append(False)
+                    
+                self.state[not self.active][y].append(False)
+                
+        self.draw_state()
+
+    def update(self):
+        future_state = self.state[not self.active]
+        for x,y in self._tile_range():
+            future_state[y][x] = self.get_future_state(x,y)
+        self.active = not self.active
+        self.reset_inactive()
+        self.draw_state()
+
+    def reset_inactive(self):
+        inactive_state = self.state[not self.active]
+        for x,y in self._tile_range():
+            inactive_state[y][x] = False
+                
+    def draw_state(self):
+        self._screen.blit(self.background, (0, 0))
+        current_state = self.state[self.active]
+        for x,y in self._tile_range():
+            if current_state[y][x]:
+                self._screen.blit(tile_image, (x*tile_size,y*tile_size))
+        pygame.display.flip()
+
+    def get_future_state(self, x, y):
+        """
+           1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
+           2. Any live cell with more than three live neighbours dies, as if by overcrowding.
+           3. Any live cell with two or three live neighbours lives on to the next generation.
+           4. Any dead cell with exactly three live neighbours becomes a live cell.
+        """        
+        c_state = self.state[self.active]
+        live = c_state[y][x]
+        neighbours = 0
+        
+        for fx,fy in self._update_coordinate_range(x,y):
+            if c_state[fy][fx]:
+                neighbours += 1
+        if live:
+            if neighbours > 3: return False
+            if neighbours < 2: return False
+            if neighbours in [2,3]: return True
+        else:
+            if neighbours == 3: return True
+        return False
+
+    def toggle_current_tile(self):
+        tile_x, tile_y =self.mousepos[0]/tile_size, self.mousepos[1]/tile_size
+        current_state = self.state[self.active]
+        current_state[tile_y][tile_x] = not current_state[tile_y][tile_x]
+        self.draw_state()
+
+    def _update_coordinate_range(self, xval, yval):
+        for rngy in range(yval-1,yval+2):
+            for rngx in range(xval-1, xval+2):
+                #join edges of grid together to make a toroidal space
+                y_x, y_y = rngx, rngy                
+                if y_x < 0: y_x = self.tiles_total[0]-1
+                if y_y < 0: y_y = self.tiles_total[1]-1
+                if y_x > self.tiles_total[0]-1: y_x = 0
+                if y_y > self.tiles_total[1]-1: y_y = 0
+                if not (rngx == xval and rngy == yval):
+                    yield (y_x, y_y)
+
+    def _tile_range(self):
+        for y in range(self.tiles_total[1]):
+            for x in range(self.tiles_total[0]):
+                yield(x,y)
+
+
+def input(events, obj):
+    for event in events:
+        if hasattr(event, 'unicode') and event.unicode == 'q':            
+            sys.exit(0)
+        if hasattr(event, 'unicode') and event.unicode == 'p':            
+            obj.paused = not obj.paused
+        if hasattr(event, 'unicode') and event.unicode == 'n' and obj.paused:
+            obj.update()
+        if hasattr(event, 'pos'):
+            obj.mousepos = event.pos
+        if hasattr(event, 'button') and event.type == pygame.MOUSEBUTTONUP:
+            obj.toggle_current_tile()
+            
+gol = GameOfLife()
+
+while True:
+    input(pygame.event.get(), gol)
+    if not gol.paused:
+        gol.update()

File gol/main0.2.py

+import os, sys
+import pygame
+from pygame.locals import *
+from pygame import time
+from collections import deque
+from multiprocessing import Process
+"""
+Game of Life sim
+
+"""
+
+# Life engine that handles all of the life calcs implementing:
+#    Random and sequential access for state data
+#    Acts as a generator so that next() that calculates the next iteration of the game
+
+# LifeController class that handles updating the life engine and drawing to screen
+# Will implement animation and heat mapping
+
+TILE_SIZE = 5
+CELL_HISTORY = 10
+
+INITIAL_STATE = [
+    (16,15),
+    (16,16),
+    (16,17),
+    (20,19),
+    (20,18),
+    (20,16),
+    (21,19),
+    (22,19),
+    (23,18),
+    (20,17),
+    (18,17),
+    (17,17),
+    ]
+
+
+
+class Life(object):
+    """
+    _width: the width of the grid
+    _height: the height of the grid
+    cells: {(int x, int y) : (bool current, bool changed)}
+    iteration: number of times update has been called
+    """
+    def __init__(self, dimensions):
+        self._width = dimensions[0]
+        self._height = dimensions[1]
+        self.cells = {}
+        self.iteration = 1
+
+        for (x,y) in self.tile_range():
+            if (x,y) in INITIAL_STATE:
+                self.cells[(x,y)] = (True, True)
+            else:
+                self.cells[(x,y)] = (False, False)
+
+    def get_future_state(self):
+        """
+        Returns a new dictionary {(x,y):(state,changed)} containing all available 
+        cells. Each cell checks the 9 cells surrounding it for existing cells.
+        We have already checked 1,2,4,5,7,8 in the previous iteration 
+        so we store them in old_neighbours and pre-add them in the 
+        check, requiring us only to check 3,6,9
+        1 2 3
+        4 5 6
+        7 8 9
+        This is only done on a row-by-row basis
+        """
+
+        future_state = {}
+        
+        for y in range(self._height):
+            neighbours = []
+            for x in range(self._width):
+                for (nx,ny,cellno) in self.update_range(x,y):
+                    if self.cells[(nx, ny)][0]:
+                        neighbours.append(cellno)
+                nlen = len(neighbours)
+                new_state = False
+                if 5 in neighbours: nlen -= 1
+        
+                if self.cells[(x,y)][0]:
+                    if nlen > 3: new_state = False
+                    if nlen < 2: new_state = False 
+                    if nlen in [2,3]: new_state = True
+                else:
+                    if nlen == 3: new_state = True
+
+                future_state[(x,y)] = (new_state, new_state != self.cells[(x,y)][0])
+
+                #store current neighbours as matches for next iteration
+                neighbours = [x-1 for x in neighbours if x not in [1,4,7]]
+        self.cells = future_state
+
+    def update_range(self, x, y):
+        r_edge = self._width - 1
+        b_edge = self._height - 1
+        x += 1
+        if x < 0: x = r_edge
+        if x > r_edge: x = 0
+        
+        i = 1
+        #TODO if x is at left edge loop thru all 9
+        for yy in range(y-1, y+2):
+            if yy < 0: yy = b_edge
+            if yy > b_edge: yy = 0
+            yield (x,yy,i*3)
+            i += 1
+
+    def update(self):
+        self.get_future_state()
+        self.iteration += 1
+
+    def tile_range(self):
+        """Outputs the entire tile range"""
+        for y in range(self._height):
+            for x in range(self._width):
+                yield(x,y)    
+    
+
+class LifeController(object):
+    def __init__(self):
+        #pygame
+        self._screen = pygame.display.set_mode((640,480))#, FULLSCREEN)
+        self._font = pygame.font.SysFont("DejaVu Sans Mono", 16)
+        screen_size = self._screen.get_size()
+
+        #internal state
+        self.tiles = (screen_size[0]/TILE_SIZE, screen_size[1]/TILE_SIZE)
+        self.surface = pygame.display.get_surface()
+        self.mouse_pos = (0,0)
+        self.paused = True
+        self._clock = time.Clock()
+        self._life_engine = Life(self.tiles)
+
+        #gfx
+        self.tile_images = {}
+        self.tile_images[True] = pygame.Surface((TILE_SIZE,TILE_SIZE))
+        self.tile_images[True].fill((50,255,100))
+        self.tile_images[False] = pygame.Surface((TILE_SIZE,TILE_SIZE))
+        self.tile_images[False].fill((0,0,0))
+
+        #stats
+        self.fps = 0
+        self._cell_count = 0
+
+    def draw(self):
+        self._life_engine.update()
+
+        self._cell_count = 0
+        for (x,y) in self._life_engine.tile_range():
+            rect = ((x-1) * TILE_SIZE, (y-1) * TILE_SIZE, TILE_SIZE, TILE_SIZE)
+            cell = self._life_engine.cells[(x,y)]
+            if cell[1]:
+                self.surface.blit(self.tile_images[cell[0]], rect)
+
+            if cell[0]:
+                self._cell_count += 1
+
+        self._draw_stats()
+        pygame.display.flip()
+
+    def _draw_stats(self):
+        t_width = self.__print_status_text("Game of Life 0.3", 5)
+        self.__print_status_text("Day %s" % self._life_engine.iteration, t_width + 100)
+        self.__print_status_text("%.2f FPS" % self.fps, t_width + 250)
+        self.__print_status_text("%s Cells" % self._cell_count, t_width + 400)
+
+    def __print_status_text(self, txt, left):
+        text = self._font.render(txt, True, (200, 20, 20), (0, 0, 0))
+        rect = text.get_rect()
+        rect.bottom = self.surface.get_rect().height
+        rect.left = left
+        fill_rect = rect
+        fill_rect.width += 10
+        fill_rect.left -= 5
+        self.surface.fill((0,0,0), fill_rect)
+        self.surface.blit(text, rect)
+        return rect.right
+
+    def toggle_current_tile():
+        pass
+
+    def input(self,events):
+        """Deals with kepresses and mouse events"""
+        for event in events:
+            if hasattr(event, 'unicode') and event.unicode == 'q':
+                sys.exit(0)
+            if hasattr(event, 'unicode') and event.unicode == 'p':
+                self.paused = not self.paused
+            if hasattr(event, 'pos'):
+                self.mousepos = event.pos
+            if hasattr(event, 'button') and event.type == pygame.MOUSEBUTTONUP:
+                self.toggle_current_tile()
+
+if __name__ == "__main__":
+    pygame.init()
+    pygame.display.set_caption('Game of life 0.3')
+
+    gol = LifeController()
+
+    while True:
+        gol.input(pygame.event.get())
+        if not gol.paused:
+            gol._clock.tick()
+            gol.fps = gol._clock.get_fps()            
+            gol.draw()

File gol/main0.3.py

+import os, sys
+import pygame
+from pygame.locals import *
+from pygame import time
+"""
+Game of Life sim
+
+"""
+
+# Life engine that handles all of the life calcs implementing:
+#    Random and sequential access for state data
+#    Acts as a generator so that next() that calculates the next iteration of the game
+
+# LifeController class that handles updating the life engine and drawing to screen
+# Will implement animation and heat mapping
+
+TILE_SIZE = 4
+WINDOW_SIZE = (400,400)
+FONT_SIZE = 10
+
+INITIAL_STATE = [
+    (16,15),
+    (16,16),
+    (16,17),
+    (20,19),
+    (20,18),
+    (20,16),
+    (21,19),
+    (22,19),
+    (23,18),
+    (20,17),
+    (18,17),
+    (17,17),
+    ]
+
+
+
+class Life(object):
+    """
+    _width: the width of the grid
+    _height: the height of the grid
+    cells: {(int x, int y) : (bool current, bool changed)}
+    iteration: number of times update has been called
+    """
+    def __init__(self, dimensions):
+        self._width = dimensions[0]
+        self._height = dimensions[1]
+        self.cells = {}
+        self.changed = {}
+        self.iteration = 1
+
+        for (x,y) in self.tile_range():
+            if (x,y) in INITIAL_STATE:
+                self.cells[(x,y)] = True
+                self.changed[(x,y)] = True
+            else:
+                self.cells[(x,y)] = False
+                self.changed[(x,y)] = False
+
+    def get_future_state(self):
+        """
+        Each cell checks the 9 cells surrounding it for existing cells.
+        We have already checked 1,2,4,5,7,8 in the previous iteration 
+        so we store them in old_neighbours and pre-add them in the 
+        check, requiring us only to check 3,6,9
+        1 2 3
+        4 5 6
+        7 8 9
+        This is only done on a row-by-row basis
+        """
+
+        future_state = {}
+
+        for y in range(self._height):
+            neighbours = []
+            for x in range(self._width):
+                for (nx,ny,cellno) in self.update_range(x,y):
+                    if self.cells[(nx, ny)]:
+                        neighbours.append(cellno)
+                nlen = len(neighbours)
+
+                if 5 in neighbours: nlen -= 1
+        
+                if self.cells[(x,y)]:
+                    if nlen > 3: 
+                        future_state[(x,y)] = False
+                        self.changed[(x,y)] = True
+                    elif nlen < 2: 
+                        future_state[(x,y)] = False
+                        self.changed[(x,y)] = True
+                    elif nlen in [2,3]: 
+                        future_state[(x,y)] = True
+                        self.changed[(x,y)] = False
+                else:
+                    if nlen == 3:
+                        future_state[(x,y)] = True
+                        self.changed[(x,y)] = True
+                    else:
+                        future_state[(x,y)] = False
+                        self.changed[x,y] = False
+
+                #store current neighbours as matches for next iteration
+                neighbours = [x-1 for x in neighbours if x not in [1,4,7]]
+        self.cells = future_state
+
+    def update_range(self, x, y):
+        r_edge = self._width - 1
+        b_edge = self._height - 1
+
+        i = 1
+        #if x is at left edge loop thru all 9
+        if x == 0:
+            for yy in range(y-1, y+2):
+                for xx in range(-1, 2):
+                    if yy > b_edge: 
+                        yy -= b_edge
+                    elif yy < 0: 
+                        yy += b_edge
+                    if xx < 0: 
+                        xx += r_edge
+                    yield (xx,yy,i)
+                    i += 1
+        else:
+            x += 1
+            for yy in range(y-1, y+2):
+                if yy > b_edge: 
+                    yy -= b_edge
+                elif yy < 0: 
+                    yy += b_edge
+                if x > r_edge:
+                    x -= r_edge
+                yield (x,yy,i*3)
+                i += 1
+
+    def update(self):
+        self.get_future_state()
+        self.iteration += 1
+
+    def tile_range(self):
+        """Outputs the entire tile range"""
+        for y in range(self._height):
+            for x in range(self._width):
+                yield(x,y)    
+
+class LifeController(object):
+    def __init__(self):
+        #pygame
+        self._screen = pygame.display.set_mode((WINDOW_SIZE[0],WINDOW_SIZE[1]))#,FULLSCREEN)
+        self._font = pygame.font.SysFont("DejaVu Sans Mono", FONT_SIZE)
+        screen_size = self._screen.get_size()
+
+        #internal state
+        self.tiles = (screen_size[0]/TILE_SIZE, (screen_size[1]-FONT_SIZE)/TILE_SIZE)
+        self.surface = pygame.display.get_surface()
+        self.mouse_pos = (0,0)
+        self.paused = True
+        self.clock = time.Clock()
+        self.life_engine = Life(self.tiles)
+
+        #gfx
+        self.tile_images = {}
+        self.tile_images[True] = pygame.Surface((TILE_SIZE,TILE_SIZE))
+        self.tile_images[True].fill((50,255,100))
+        self.tile_images[False] = pygame.Surface((TILE_SIZE,TILE_SIZE))
+        self.tile_images[False].fill((0,0,0))
+
+        #stats
+        self.fps = 0
+        self._cell_count = 0
+
+        self.draw(False)
+
+    def draw(self, update=True):
+        if update:
+            self.life_engine.update()
+
+        self._cell_count = 0
+
+        for (x,y) in self.life_engine.tile_range():
+            cell = self.life_engine.cells[(x,y)]
+            if cell:
+                self._cell_count += 1
+
+            if self.life_engine.changed[(x,y)]:
+                rect = ((x-1) * TILE_SIZE, (y-1) * TILE_SIZE, TILE_SIZE, TILE_SIZE)
+                self.surface.blit(self.tile_images[cell], rect)
+
+        self._draw_stats()
+        pygame.display.flip()
+
+    def _draw_stats(self):
+        t_width = self.__print_status_text("Game of Life 0.3", 5)
+        self.__print_status_text("Day %s" % self.life_engine.iteration, t_width + 100)
+        self.__print_status_text("%.2f FPS" % self.fps, t_width + 250)
+        self.__print_status_text("%s Cells" % self._cell_count, t_width + 400)
+
+    def __print_status_text(self, txt, left):
+        text = self._font.render(txt, True, (200, 20, 20), (0, 0, 0))
+        rect = text.get_rect()
+        rect.bottom = self.surface.get_rect().height
+        rect.left = left
+        fill_rect = rect
+        fill_rect.width += 10
+        fill_rect.left -= 5
+        self.surface.fill((0,0,0), fill_rect)
+        self.surface.blit(text, rect)
+        return rect.right
+
+    def toggle_current_tile(self):
+        if self.mousepos:
+            tilex = self.mousepos[0]/TILE_SIZE
+            tiley = self.mousepos[1]/TILE_SIZE
+            self.life_engine.cells[tilex, tiley] = not self.life_engine.cells[tilex, tiley]
+            self.life_engine.changed[tilex, tiley] = True
+            self.draw(False)
+
+    def input(self,events):
+        """Deals with kepresses and mouse events"""
+        for event in events:
+            if hasattr(event, 'unicode') and event.unicode == 'q':
+                sys.exit(0)
+            if hasattr(event, 'unicode') and event.unicode == 'p':
+                self.paused = not self.paused
+            if hasattr(event, 'unicode') and event.unicode == 'n':
+                if self.paused:
+                    self.draw()
+            if hasattr(event, 'pos'):
+                self.mousepos = event.pos
+            if hasattr(event, 'button') and event.type == pygame.MOUSEBUTTONUP:
+                self.toggle_current_tile()
+
+if __name__ == "__main__":
+    pygame.init()
+    pygame.display.set_caption('Game of life 0.3')
+
+    gol = LifeController()
+
+    while True:
+        gol.input(pygame.event.get())
+        if not gol.paused:
+            gol.clock.tick()
+            gol.fps = gol.clock.get_fps()            
+            gol.draw()

File gol/main0.4.py

+import os, sys
+import pygame
+from pygame.locals import *
+from pygame import time
+
+import cProfile
+from optparse import OptionParser
+"""
+Game of Life sim
+
+"""
+
+# Life engine that handles all of the life calcs implementing:
+#    Random and sequential access for state data
+#    Acts as a generator so that next() that calculates the next iteration of the game
+
+# LifeController class that handles updating the life engine and drawing to screen
+# Will implement animation and heat mapping
+
+TILE_SIZE = 5
+WINDOW_SIZE = (640,480)
+FONT_SIZE = 16
+
+INITIAL_STATE = [
+    (16,15),
+    (16,16),
+    (16,17),
+    (20,19),
+    (20,18),
+    (20,16),
+    (21,19),
+    (22,19),
+    (23,18),
+    (20,17),
+    (18,17),
+    (17,17),
+    ]
+
+
+
+class Life(object):
+    """
+    _width: the width of the grid
+    _height: the height of the grid
+    cells: {(int x, int y) : (bool current, bool changed)}
+    iteration: number of times update has been called
+    """
+    def __init__(self, dimensions):
+        self._width = dimensions[0]
+        self._height = dimensions[1]
+        self.cells = {}
+        self.changed = {}
+        self.iteration = 1
+
+        for (x,y) in self.tile_range():
+            if (x,y) in INITIAL_STATE:
+                self.cells[(x,y)] = True
+                self.changed[(x,y)] = True
+            else:
+                self.cells[(x,y)] = False
+                self.changed[(x,y)] = False
+
+    def next_iteration(self, cells, changed):
+        """
+        Each cell checks the 9 cells surrounding it for existing cells.
+        We have already checked 1,2,4,5,7,8 in the previous iteration 
+        so we store them in neighbours[] requiring only a check of 3,6,9
+        1 2 3
+        4 5 6
+        7 8 9
+        This is only done on a row-by-row basis
+        """
+
+        future_state = {}
+
+        for y in xrange(self._height):
+            neighbours = []
+            for x in xrange(self._width):
+                for (nx,ny,cellno) in self.gen_neighbours(x,y):
+                    if cells[(nx, ny)]:
+                        neighbours.append(cellno)
+                nlen = len(neighbours)
+
+                if 5 in neighbours: nlen -= 1
+        
+                if cells[(x,y)]:
+                    if nlen > 3: 
+                        future_state[(x,y)] = False
+                        changed[(x,y)] = True
+                    elif nlen < 2: 
+                        future_state[(x,y)] = False
+                        changed[(x,y)] = True
+                    elif nlen in [2,3]: 
+                        future_state[(x,y)] = True
+                        changed[(x,y)] = False
+                else:
+                    if nlen == 3:
+                        future_state[(x,y)] = True
+                        changed[(x,y)] = True
+                    else:
+                        future_state[(x,y)] = False
+                        changed[x,y] = False
+
+                #store current neighbours as matches for next iteration
+                neighbours = [x-1 for x in neighbours if x not in [1,4,7]]
+        self.cells = future_state
+
+    def gen_neighbours(self, x, y):
+        r_edge = self._width - 1
+        b_edge = self._height - 1
+
+        i = 1
+        #if x is at left edge loop thru all 9
+        if x == 0:
+            for yy in xrange(y-1, y+2):
+                for xx in xrange(-1, 2):
+                    if yy > b_edge: 
+                        yy -= b_edge
+                    elif yy < 0: 
+                        yy += b_edge
+                    if xx < 0: 
+                        xx += r_edge
+                    yield (xx,yy,i)
+                    i += 1
+        #otherwise, just the rightmost 3 of the 9 neighbours
+        else:
+            x += 1
+            for yy in xrange(y-1, y+2):
+                if yy > b_edge: 
+                    yy -= b_edge
+                elif yy < 0: 
+                    yy += b_edge
+                if x > r_edge:
+                    x -= r_edge
+                yield (x,yy,i*3)
+                i += 1
+
+    def update(self):
+        self.next_iteration(self.cells, self.changed)
+        self.iteration += 1
+
+    def tile_range(self):
+        """Outputs the entire tile range"""
+        for y in xrange(self._height):
+            for x in xrange(self._width):
+                yield(x,y)    
+
+class LifeController(object):
+    def __init__(self, profile=False):
+        #pygame
+        self._screen = pygame.display.set_mode((WINDOW_SIZE[0],WINDOW_SIZE[1]))#,FULLSCREEN)
+        self._font = pygame.font.SysFont("DejaVu Sans Mono", FONT_SIZE)
+        screen_size = self._screen.get_size()
+
+        #internal state
+        self.tiles = (screen_size[0]/TILE_SIZE, (screen_size[1]-FONT_SIZE)/TILE_SIZE)
+        self.life_engine = Life(self.tiles)
+        self.surface = pygame.display.get_surface()
+        self.mouse_pos = (0,0)
+        self.paused = True
+        self.clock = time.Clock()
+        self.profile = profile
+
+        #gfx
+        self.tile_images = {}
+        self.tile_images[True] = pygame.Surface((TILE_SIZE,TILE_SIZE))
+        self.tile_images[True].fill((50,255,100))
+        self.tile_images[False] = pygame.Surface((TILE_SIZE,TILE_SIZE))
+        self.tile_images[False].fill((0,0,0))
+
+        #stats
+        self.fps = 0
+        self._cell_count = 0
+
+        self.draw(False)
+
+    def draw(self, update=True):
+        if update:
+            self.life_engine.update()
+
+        self._cell_count = 0
+
+        for (x,y) in self.life_engine.tile_range():
+            cell = self.life_engine.cells[(x,y)]
+            if cell:
+                self._cell_count += 1
+
+            if self.life_engine.changed[(x,y)]:
+                rect = ((x-1) * TILE_SIZE, (y-1) * TILE_SIZE, TILE_SIZE, TILE_SIZE)
+                self.surface.blit(self.tile_images[cell], rect)
+
+        self._draw_stats()
+        pygame.display.flip()
+
+    def _draw_stats(self):
+        t_width = self.__print_status_text("Game of Life 0.3", 5)
+        self.__print_status_text("Day %s" % self.life_engine.iteration, t_width + 100)
+        self.__print_status_text("%.2f FPS" % self.fps, t_width + 250)
+        self.__print_status_text("%s Cells" % self._cell_count, t_width + 400)
+
+    def __print_status_text(self, txt, left):
+        text = self._font.render(txt, True, (200, 20, 20), (0, 0, 0))
+        rect = text.get_rect()
+        rect.bottom = self.surface.get_rect().height
+        rect.left = left
+        fill_rect = rect
+        fill_rect.width += 10
+        fill_rect.left -= 5
+        self.surface.fill((0,0,0), fill_rect)
+        self.surface.blit(text, rect)
+        return rect.right
+
+    def toggle_current_tile(self):
+        if self.mousepos:
+            tilex = self.mousepos[0]/TILE_SIZE
+            tiley = self.mousepos[1]/TILE_SIZE
+            self.life_engine.cells[tilex, tiley] = not self.life_engine.cells[tilex, tiley]
+            self.life_engine.changed[tilex, tiley] = True
+            self.draw(False)
+
+    def input(self,events):
+        """Deals with kepresses and mouse events"""
+        for event in events:
+            if hasattr(event, 'unicode') and event.unicode == 'q':
+                sys.exit(0)
+            if hasattr(event, 'unicode') and event.unicode == 'p':
+                self.paused = not self.paused
+            if hasattr(event, 'unicode') and event.unicode == 'n':
+                if self.paused:
+                    self.draw()
+            if hasattr(event, 'pos'):
+                self.mousepos = event.pos
+            if hasattr(event, 'button') and event.type == pygame.MOUSEBUTTONUP:
+                self.toggle_current_tile()
+
+    def mainloop(self):
+        done = False
+        while not done:
+            self.input(pygame.event.get())
+            if not self.paused:
+                self.clock.tick()
+                self.fps = self.clock.get_fps()            
+                self.draw()
+            if self.profile and self.life_engine.iteration > 100:
+                done = True
+
+
+if __name__ == "__main__":
+    parser = OptionParser()
+    parser.add_option("--profile", action="store_true", dest="profile", default="False", help="Profile 100 iterations (default: False)")
+    (options, args) = parser.parse_args()
+
+    pygame.init()
+    pygame.display.set_caption('Game of life 0.4')
+    lc = LifeController()
+
+    if options.profile is True:
+        lc.profile = True
+        cProfile.run('lc.mainloop()')
+        pygame.quit()
+        raw_input("Press Enter to exit")        
+    else:
+        lc.mainloop()
+        pygame.quit()
+

File gol/spectrum.bmp

Added
New image

File siganal/main.py

+ #! /usr/bin/env python
+#display
+from __future__ import division
+import pygame
+import random
+from pygame.time import get_ticks
+
+#analysis
+from scikits import audiolab
+import scipy
+  
+ # Window dimensions
+screen_width = 600
+screen_height = 600
+line_colour = 100,100,100
+
+screen = pygame.display.set_mode((screen_width, screen_height))
+clock = pygame.time.Clock()
+zoom_factor = 10
+
+class SignalVis(object):
+    def __init__(self, filename, frame_interval, points):
+        """
+        filename - pretty obvious :) currently only working with wavs
+        frame_interval - frame size for analysis in ms
+        """
+        self.data, self.fs, self.encoding = audiolab.wavread(filename)
+        self.frame_bits = int(frame_interval/1000 * self.fs)
+        self.points = points
+        self.interval = int(round(self.frame_bits /self.points))
+
+    def next(self):
+        while len(self.data) > 0:
+            X = scipy.fft(self.data[:self.frame_bits])
+            Xdb = 20*scipy.log10(scipy.absolute(X))
+            self.data = self.data[self.frame_bits:]
+            yield [int(x) for i, x in enumerate(Xdb) if (i % self.interval == 0)]
+
+def get_time_since(ticks):
+    return get_ticks() - ticks
+
+running = True
+interval = 10
+vis = SignalVis('louis.wav', interval, screen_width)
+pygame.mixer.init()
+pygame.mixer.music.load('postal.wav')
+local_time = get_ticks()
+pygame.mixer.music.play()
+
+for line_data in vis.next():
+    #blank screen
+    screen.fill((0,0,0))
+
+    for x in xrange(len(line_data)):
+        screen.set_at((x, screen_height-(300 + int(round(line_data[x]*2)))), line_colour)
+    
+    while get_time_since(local_time) < interval:
+        pass
+    local_time = get_ticks()
+    pygame.display.flip()
+    
+