#!/usr/bin/env python # life.py ''' Conway's Game of Life ''' import random import itertools import pygame # Set Up pygame.init() colors = pygame.color.THECOLORS ON, OFF = 1, 0 # The Plan: # Put the cells in a dict from {cell : neighbor vectors} # That way neighbors can be looked-up easily in a flat list. # # # Helper Functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! def randcolor (lower=40, upper=200): cvals = range(lower, upper) r, g, b = [random.choice(cvals) for i in range(3)] return pygame.Color(r, g, b) def randgreen (lower=90, upper=250): cvals = range(lower, upper) g = random.choice(cvals) return pygame.Color(g-lower, g, 0) def randred (lower=90, upper=250): cvals = range(lower, upper) r = random.choice(cvals) return pygame.Color(r, r-lower, 0) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! class Cell (object): def __init__ (self, **kwargs): # defaults self.size = (25, 25) self.alpha = 90 self.oncolor = randgreen() self.offcolor = colors['black'] self.state = OFF # absolute topleft pixel position. self.position = None # unpack kwargs for k in kwargs: setattr(self, k, kwargs[k]) self._init_default_config() @property def width (self): return self.size[0] @property def height (self): return self.size[1] def _init_default_config (self): self.image = pygame.Surface(self.size) self.image.set_alpha(0) self.rect = self.image.get_rect() if self.state: self.image.fill(self.oncolor) else: self.image.fill(self.offcolor) if hasattr(self, 'position'): self.rect.topleft = self.position def update (self): if self.state: self.image.fill(self.oncolor) self.image.set_alpha(self.alpha) else: self.image.set_alpha(90) self.image.fill(self.offcolor) def scale_alpha(self, inc): next_step = self.alpha + inc if next_step < 255 and next_step > 0: self.alpha = next_step def __repr__ (self): return "" #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! class Grid (object): def __init__ (self, gridsize, cellsize): self.cell_w, self.cell_h = cellsize # cell w, h self.w, self.h = gridsize # grid w, h self.n = self.w * self.h # n cells self.cells = [] # a 2D array # Initial Configuration self._do_init_config() def _do_init_config (self): absx = absy = 0 for i in xrange(self.h): row = [] for j in xrange(self.w): if i % random.randint(2,8) == 0: initstate = random.choice([ON,OFF]) else: initstate = OFF nhood = self._get_neighbors(i, j) cell = Cell( nhood = [n for n in nhood], size=(self.cell_w, self.cell_h), position=(absx, absy), state=initstate, past=initstate ) row.append(cell) absx += cell.width self.cells.append(row) absy += cell.height absx = 0 def update (self, surface): newcolor = randgreen() for row in self.cells: for cell in row: cell.past = cell.state # stores previous state cell.update() # sets cell state color states = [1 for (i,j) in cell.nhood if self.cells[i][j].past] score = sum( states ) # set new cell state if score < 4 and score > 1 and cell.past: cell.oncolor = randred() cell.state = ON elif score == 3 and not cell.past: cell.state = ON cell.oncolor = newcolor else: cell.state = OFF # draw to screen surface.blit(cell.image, cell.position) def _get_neighbors (self, x, y): for point in itertools.product(range(-1,2), range(-1,2)): _x, _y = point[0]+x, point[1]+y if (x, y) == (_x, _y): continue _x %= self.w _y %= self.h yield (_x, _y) def __getitem__ (self, i): i, j = divmod(i, self.w) return self.cells[i][j] #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! class Automaton (object): def __init__ (self, grid_size, cell_size): self.grid = Grid(grid_size, cell_size) self.grid_size = grid_size self.cell_size = cell_size self.clock = pygame.time.Clock() self.running = False def seed (self): self.scrsize = [self.grid_size[i] * self.cell_size[i] for i in [0,1]] self.screen = pygame.display.set_mode(self.scrsize) self.running = True while self.running: self.clock.tick(36) for e in pygame.event.get(): if hasattr(e, 'pos') and e.type == pygame.MOUSEBUTTONDOWN: self._mouse_handler(e) elif hasattr(e, 'key') and e.type == pygame.KEYDOWN: self._key_handler(e) self.grid.update(self.screen) pygame.display.flip() def _mouse_handler (self, e): for cell in self.grid: # OH MY GOD if e.pos[0] > cell.rect.left and e.pos[0] < cell.rect.right and \ e.pos[1] > cell.rect.top and e.pos[1] < cell.rect.bottom: for pos in cell.nhood: self.grid.cells[pos[0]][pos[1]].state = ON def _key_handler (self, e): if e.key == pygame.K_q or e.key == pygame.K_ESCAPE: self.running = False elif e.key == pygame.K_UP: for cell in self.grid: cell.scale_alpha(10) elif e.key == pygame.K_DOWN: for cell in self.grid: cell.scale_alpha(-10) # ///////////////////////! if __name__ == '__main__': grid_size = (60, 60) # grid and cell sizes must be squares cell_size = (8, 8) automaton = Automaton( grid_size, cell_size ) automaton.seed()