#!/usr/bin/env pythonw # # Copyright 2008 by Timothy (rhacer) Grant # # This code is released under the terms of the GNU GPL v3 # import time import random import wx import wx.lib.colourdb as wxc # import wx.lib.inspection DEBUG_LEVEL = 0 GRIDSIZE = 10 RANDOM_START_PCT = 0.1 CELL_LONELY = 2 CELL_OVERCROWDED = 3 CELL_BORN = 3 ID_GO_BUTTON = wx.NewId() ID_RESET_BUTTON = wx.NewId() ID_SINGLESTEP_BUTTON = wx.NewId() ID_DENSITY_SPINNER = wx.NewId() ID_TIMER = wx.NewId() class Model: def __init__(self, width, height, density = RANDOM_START_PCT): if DEBUG_LEVEL > 0: print "Model Init" self.width = width self.height = height self.generation = 0 self.population = 0 self.deaths = 0 self.births = 0 self.gen_deaths = 0 self.gen_births = 0 self.density = density self.cells = [ [ 'D' for i in range(self.width) ] for j in range(self.height) ] def reset_cell_population(self, event=None): if DEBUG_LEVEL > 0: print "Model: reset_cell_population" self.generation = 0 self.deaths = 0 self.births = 0 self.gen_deaths = 0 self.gen_births = 0 self.cells = [ [ 'D' for i in range(self.width) ] for j in range(self.height) ] for cell in range(int(self.width * self.height * self.density)): while True: x = random.randint(0, self.height-1) y = random.randint(0, self.width-1) if self.cells[x][y] <> 'L': self.cells[x][y] = 'L' self.population += 1 break def resize_grid(self, new_width, new_height): if DEBUG_LEVEL > 0: print "Model: resize_grid" if new_width > self.width: for r in range(self.height): self.cells[r].extend( [ 'D' for i in range(new_width - self.width) ]) if new_width < self.width: for r in range(self.height): while len(self.cells[r]) > new_width: self.cells[r] = self.cells[r][:-1] self.width = len(self.cells[0]) if new_height > self.height: self.cells.extend([ [ 'D' for i in range(self.width) ] for j in range(new_height - self.height) ]) if new_height < self.height: while len(self.cells) > new_height: self.cells = self.cells[:-1] self.height = len(self.cells) def reset_cell(self, width, height): if DEBUG_LEVEL > 0: print "Model: reset_cell" current_state = self.cells[height][width] if current_state <> 'L': self.cells[height][width] = 'L' else: self.cells[height][width] = 'D' def iterate_model(self): if DEBUG_LEVEL > 0: print "Model: iterate_model" self.generation += 1 self.gen_deaths = 0 self.gen_births = 0 for r in range(self.height): for c in range(self.width): neighbours = self.__neighbours(r, c) # Will Cell Die? if ((self.cells[r][c] == 'L') and ((neighbours < CELL_LONELY) or (neighbours > CELL_OVERCROWDED))): self.cells[r][c] = 'WD' self.deaths += 1 self.gen_deaths += 1 # Will Cell Be Born? if (self.cells[r][c] == 'D') and (neighbours == CELL_BORN): self.cells[r][c] = 'WL' self.births += 1 self.gen_births += 1 for r in range(self.height): for c in range(self.width): if self.cells[r][c] == 'WD': self.cells[r][c] = 'D' self.population -= 1 if self.cells[r][c] == 'WL': self.cells[r][c] = 'L' self.population += 1 def __neighbours(self, r, c): n = 0 if (r-1 >= 0) and (self.cells[r-1][c] in ['L', 'WD']): n += 1 if (r+1 < self.height) and (self.cells[r+1][c] in ['L', 'WD']): n += 1 if (c-1 >= 0) and (self.cells[r][c-1] in ['L', 'WD']): n += 1 if (c+1 < self.width) and (self.cells[r][c+1] in ['L', 'WD']): n += 1 if (r-1 >= 0) and (c-1 >= 0) and (self.cells[r-1][c-1] in ['L', 'WD']): n += 1 if ((r-1 >= 0) and (c+1 < self.width) and (self.cells[r-1][c+1] in ['L', 'WD'])): n += 1 if ((r+1 < self.height) and (c-1 >= 0) and (self.cells[r+1][c-1] in ['L', 'WD'])): n += 1 if ((r+1 < self.height) and (c+1 < self.width) and (self.cells[r+1][c+1] in ['L', 'WD'])): n += 1 return n class Ecosystem(wx.Panel): def __init__(self, parent): if DEBUG_LEVEL > 0: print "Ecosystem Init" wx.Panel.__init__(self, parent) self.parent = parent self.set_grid_size_from_window_size() self.model = Model(self.gw, self.gh) self.model.reset_cell_population() self.SetBackgroundColour(wx.WHITE) self.running = False self.Bind(wx.EVT_LEFT_DOWN, self.OnClick) self.Bind(wx.EVT_PAINT, self.OnPaint) self.SetMinSize(wx.Size(400,360)) def OnClick(self, event): if DEBUG_LEVEL > 0: print "Ecosystem: OnClick" self.model.reset_cell(event.GetX() / GRIDSIZE, event.GetY() / GRIDSIZE) self.Refresh() def OnPaint(self, event): event.Skip() dc = wx.PaintDC(self) dc.BeginDrawing() self.repaint_ecosystem(dc) self.update_status_info() dc.EndDrawing() def reset_cell_population(self, event=None): if DEBUG_LEVEL > 0: print "Ecosystem: reset_cell_population" self.model.density = self.parent.controls.DensityCtrl.GetValue() / 100.0 self.model.reset_cell_population() self.Refresh() def set_grid_size_from_window_size(self): if DEBUG_LEVEL > 0: print "Ecosystem: set_grid_size_from_window_size" self.pw, self.ph = self.parent.GetClientSizeTuple() self.gw = self.pw / GRIDSIZE self.gh = self.ph / GRIDSIZE try: self.model.resize_grid(self.gw, self.gh) except AttributeError: # This gets called before the Model is created early on. # It can pass silently. pass def update_status_info(self): if DEBUG_LEVEL > 0: print "Ecosystem: update_status_info" try: self.parent.SetStatusText('Generation %i' % self.model.generation, 0) self.parent.SetStatusText('Pop %i (%i)' % (self.model.population, self.model.gen_births - self.model.gen_deaths), 1) self.parent.SetStatusText('Births %i' % self.model.births, 2) self.parent.SetStatusText('Deaths %i' % self.model.deaths, 3) except wx.PyAssertionError: pass def repaint_ecosystem(self, dc): if DEBUG_LEVEL > 0: print "Ecosystem: repaint_ecosystem" alive = [] dead = [] for h in range(self.model.height): for w in range(self.model.width): if self.model.cells[h][w] == 'D': dead.append([(w*GRIDSIZE), (h*GRIDSIZE), GRIDSIZE, GRIDSIZE]) if self.model.cells[h][w] == 'L': alive.append([(w*GRIDSIZE), (h*GRIDSIZE), GRIDSIZE, GRIDSIZE]) self.paint_cells(alive, dead, dc) def paint_cells(self, alive, dead, dc): if DEBUG_LEVEL > 0: print "Ecosystem: paint_cells" dc.SetPen(wx.Pen('LIGHTGREY', 1)) dc.SetBrush(wx.Brush('WHITE')) dc.DrawRectangleList(dead) dc.SetBrush(wx.Brush('BLUE')) dc.DrawRectangleList(alive) def run_simulation(self, event): if DEBUG_LEVEL > 0: print "Ecosystem: run_simulation" self.model.iterate_model() self.Refresh() class Controls(wx.Panel): def __init__(self, parent): if DEBUG_LEVEL > 0: print "Controls Init" wx.Panel.__init__(self, parent) self.parent = parent self.GoButton = wx.Button(self, ID_GO_BUTTON, 'Go') self.ResetButton = wx.Button(self, ID_RESET_BUTTON, 'Reset') self.SingleStepButton = wx.Button(self, ID_SINGLESTEP_BUTTON, 'Step') self.DensityText = wx.StaticText(self, -1, "Pop Density") self.DensityText.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL)) self.DensityCtrl = wx.SpinCtrl( self, wx.ID_ANY) self.DensityCtrl.SetValue( 10 ) hbox = wx.BoxSizer(wx.HORIZONTAL) hbox.Add(self.GoButton, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) hbox.Add(self.ResetButton, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) hbox.Add(self.SingleStepButton, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) hbox.Add(self.DensityText, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) hbox.Add(self.DensityCtrl, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) self.SetSizer(hbox) class GenePool(wx.Frame): def __init__(self, parent, ID, title): if DEBUG_LEVEL > 0: print "GenePool Init" wx.Frame.__init__(self, parent, ID, title, wx.DefaultPosition, wx.Size(400, 400)) self.statusbar = self.CreateStatusBar() self.statusbar.SetFieldsCount(4) self.statusbar.SetStatusWidths([-1, -1, -1, -1]) self.controls = Controls(self) self.ecosystem = Ecosystem(self) vbox = wx.BoxSizer(wx.VERTICAL) vbox.Add(self.ecosystem, 1, wx.EXPAND) vbox.Add(self.controls, 0, wx.EXPAND) self.SetSizer(vbox) self.Fit() class App(wx.App): def OnInit(self): if DEBUG_LEVEL > 0: print "App Init" self.frame = GenePool(None, -1, "life") self.snap_to_size() self.frame.Show(True) self.SetTopWindow(self.frame) self.Bind(wx.EVT_SIZE, self.snap_to_size) self.Bind(wx.EVT_BUTTON, self.toggle_simulation, id=ID_GO_BUTTON) self.Bind(wx.EVT_BUTTON, self.singlestep_simulation, id=ID_SINGLESTEP_BUTTON) self.Bind(wx.EVT_BUTTON, self.frame.ecosystem.reset_cell_population, id=ID_RESET_BUTTON) wxc.updateColourDB() return True def snap_to_size(self, event=None): """Does Snap to grid so all grids are the same size. it always snaps down in size, never up""" if DEBUG_LEVEL > 0: print "App: snap_to_size" w, h = self.frame.GetClientSizeTuple() print w, (w / GRIDSIZE) * GRIDSIZE print h, (h / GRIDSIZE) * GRIDSIZE self.frame.SetClientSizeWH(((w / GRIDSIZE) * GRIDSIZE), ((h / GRIDSIZE) * GRIDSIZE)) self.frame.ecosystem.set_grid_size_from_window_size() def toggle_simulation(self, event): if DEBUG_LEVEL > 0: print "App: toggle_simulation" if self.frame.ecosystem.running: self.__turn_simulation_off() else: self.frame.ecosystem.running = True self.frame.controls.GoButton.SetLabel('Stop') self.timer = wx.Timer(self, ID_TIMER) # message will be sent to # the panel self.timer.Start(100) # x100 milliseconds wx.EVT_TIMER(self, ID_TIMER, self.frame.ecosystem.run_simulation) def singlestep_simulation(self, event): if DEBUG_LEVEL > 0: print "App: singlestep_simulation" if self.frame.ecosystem.running: self.__turn_simulation_off() self.frame.ecosystem.run_simulation(event) def __turn_simulation_off(self): if DEBUG_LEVEL > 0: print "App: __turn_simulation_off" self.frame.ecosystem.running = False self.frame.controls.GoButton.SetLabel('Go') self.timer.Stop() def main(): app = App(0) # wx.lib.inspection.InspectionTool().Show() app.MainLoop() if __name__ == "__main__": """ Immediate mode commands go here """ main()