git.fiddlerwoaroof.com
game.py
e0a25cb7
 from __future__ import print_function
 #  
 #  Copyright (c) 2012 Edward Langley
 #  All rights reserved.
 #  
 #  Redistribution and use in source and binary forms, with or without
 #  modification, are permitted provided that the following conditions
 #  are met:
 #  
 #  Redistributions of source code must retain the above copyright notice,
 #  this list of conditions and the following disclaimer.
 #  
 #  Redistributions in binary form must reproduce the above copyright
 #  notice, this list of conditions and the following disclaimer in the
 #  documentation and/or other materials provided with the distribution.
 #  
 #  Neither the name of the project's author nor the names of its
 #  contributors may be used to endorse or promote products derived from
 #  this software without specific prior written permission.
 #  
 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 #  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 #  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 #  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 #  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 #  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 #  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 #  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 #  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 #  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 #  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #  
 #  
 
 import sys
 import collections
 import pygame
 
 class Cell(object):
 	north = None
 	south = None
 	east = None
 	west = None
 	rules = {}
 
 
 	def rule(self, *a):
 		'''The rule used to determine the new value for the current cell, add_rule reassigns this based on the current value'''
 		return self.value
 
 	@classmethod
 	def add_rule(self, value):
 		'''Add a rule to correspond to the current value of the cell'''
 		def _inner(rule):
 			self.rules[value] = rule
 			return rule
 		return _inner
 
 	def __init__(self):
 		self.value = None
 		self.update_neighbors()
 
 	def set_value(self, v):
 		'''Change the cells value and select the appropriate rule'''
 		if v != self.value:
 			self.value = v
 			if v is not None:
 				self.rule = self.rules[v]
 
 	def neighbor_value(self):
 		'''Get the total of the values of neighbouring cells'''
 		result = 0
 		for c in self.neighbors:
 			if c is None: continue
 			elif c.value is None: continue
 			result += c.value
 		return result
 
 	def update_neighbors(self):
 		'''Change the neighbors attribute to reflect the current situation'''
 		self.neighbors = (self.north, self.south, self.east, self.west, self.northwest, self.northeast, self.southwest, self.southeast)
 
 	def autoconnect(self):
 		'''Set up the reverse links appropriate to the current situation'''
 		updated = []
 
 		if self.south and self.south.north is not self:
 			self.south.north = self
 			updated.append(self.south)
 		if self.north and self.north.south is not self:
 			self.north.south = self
 			updated.append(self.north)
 		if self.east and self.east.west is not self:
 			self.east.west = self
 			updated.append(self.east)
 		if self.west and self.west.east is not self:
 			self.west.east = self
 			updated.append(self.west)
 
 		for up in updated:
 			up.autoconnect()
 
 		self.update_neighbors()
 
 	@property
 	def northeast(self):
 		r = self.north
 		if r is not None:
 			return r.east
 
 	@property
 	def northwest(self):
 		r = self.north
 		if r is not None:
 			return r.west
 
 	@property
 	def southeast(self):
 		r = self.south
 		if r is not None:
 			return r.east
 
 	@property
 	def southwest(self):
 		r = self.south
 		if r is not None:
 			return r.west
 
 
 	def __repr__(self):
 		return '%s().set_value(%s)' % (self.__name__, self.value)
 
 	def __str__(self):
 		return str(self.value)
 
 class CellSprite(pygame.sprite.DirtySprite):
 	'''Display code for :py:class:`Cell`'''
 
 	def __init__(self, cell, row, col, *a):
 		self.image = pygame.surface.Surface( (6,6) )
 		self.cell = cell
 		self.row = row
 		self.col = col
 		cell.sprite = self
 		self.spacing = 0
 
 		row, col = self.row, self.col
 		spacing = self.spacing
 		size = width, height = self.image.get_size()
 		self.rect = pygame.rect.Rect( (width*(col)+spacing*(col), (height*(row)+spacing*(row))), size )
 
 		pygame.sprite.DirtySprite.__init__(self, *a)
 		self.start_update(self.cell.value)
 
 
 	colors = {
 		None: (128,128,128),
 		0: (0,0,0),
 		1: (0,255,0),
 		2: (128,64,0),
 		3: (255,0,0)
 	}
 
 	def start_update(self, value):
 		'''Begin to update the cell: store the new value and mark the sprite "dirty"'''
 		self.new_value = value
 		self.dirty = 1
 		self.ud = True
 
 	def update(self):
 		'''Update the cell: set the cell's value and paint the new image'''
 		if self.ud:
 			self.cell.set_value(self.new_value)
 			self.image.fill( self.colors[self.cell.value] )
 
 
 class Board(object):
 	'''Set up and store the cells'''
 
 	def __init__(self, width, height):
 		self.board_group = pygame.sprite.LayeredDirty(_use_update=True)
 		self.rect = pygame.Rect(0,0,0,0)
 		self.dct = {}
 
 		for col in xrange(width):
 			for row in xrange(0,height):
 				cell = self.dct[row, col] = Cell()
 				sprite = CellSprite(cell, row, col, self.board_group)
 
 		for col in xrange(width):
 			for row in xrange(height):
 				cell = self.dct[row,col]
 				cell.east = self.dct.get((row,col+1))
 				cell.south = self.dct.get((row+1,col))
 				cell.autoconnect()
 
 		self.init = self.dct[0,0]
 		self.rect.unionall_ip(list(self.board_group))
 
 
 	def __str__(self):
 		current = self.init
 		result = []
 		while current.east != None:
 			top = current
 			result.append([])
 			while top.south != None:
 				result[-1].append(str(top))
 				top = top.south
 			current = current.east
 		result = zip(*result)
 		return '\n'.join(
 			' '.join(r) for r in result
 		)
 
 import random
 
 
 
 class Controller(object):
 	'''Initialize cell values and run the simulation'''
 	def __init__(self, board):
 		self.board = board
 		self.observers = []
 
 		column = self.board.init
 		for sprite in self.board.board_group:
 			sprite.start_update( random.choice([1]*7+[0]*5 +[2]) )
 		self.board.board_group.update()
 
 
 	def step(self):
 		'''Run one iteration of the simulation'''
 		for observer in self.observers:
 			observer.act()
 
 		c_sprites = pygame.sprite.Group()
 		for sprite in self.board.board_group:
 			cell = sprite.cell
 			nvalue = cell.rule
 			if callable(nvalue):
 				nvalue = nvalue(cell)
 			if nvalue is None:
 				nvalue = cell.value
 			if nvalue != cell.value:
 				sprite.start_update(nvalue)
 				c_sprites.add(sprite)
 		c_sprites.update()
 		return self
 
 	def add_observer(self, cls, *args, **kwargs):
 		'''add an observer to a cell'''
 		observer = cls(*args, **kwargs)
 		self.observers.append(observer)
 		return observer
 
 	def init(self, cb):
 		self.init = cb
 		return self
 
 	def add_event(self, cb):
 		self.events.append(cb)
 		return self
 
 	def loop(self):
 		variables = {}
 		variables.update(self.init())
 		clck = pygame.time.Clock()
 		variables['running'] = True
 
 class Presence(object):
 	'''Observer interface'''
 	def __init__(self, locale):
 		self.locale = locale
 
 	def act(self):
 		'''Override to implement behavior'''
 		pass