Last active
August 29, 2015 13:58
-
-
Save speezepearson/10014982 to your computer and use it in GitHub Desktop.
A Game of Life simulator in PyGUI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python | |
| # | |
| # A package that runs Conway's Game of Life. Simple GUI interface. | |
| # Requires PyGUI and srptools (a small collection of libraries I wrote, available via my Github account, speezepearson. | |
| # | |
| import pickle | |
| from GUI import (Application, View, Document, Window, Task, Frame, | |
| Slider, Label, Button, ViewBase) | |
| from GUI.Files import FileType | |
| from GUI.Geometry import pt_in_rect | |
| from GUI.StdColors import black, white, red, blue | |
| from srptools.pyguiutils import (StateChangingButton, TitledItemFrame, | |
| ControlWindow) | |
| from math import log | |
| CELL_SIZE = 8 # Size of a grid cell in pixels | |
| # These layout options probably shouldn't be changed. Things look funny | |
| # if they're not at good values. Possibly a surmountable problem, but | |
| # not one I feel like doing at the moment. | |
| SPEED_FRAME_HEIGHT = 50 | |
| WINDOW_PADDING = 20 | |
| SLIDER_WIDTH = 300 | |
| LABEL_WIDTH = 50 | |
| BUTTON_WIDTH = 50 | |
| DOC_W = 100 | |
| DOC_H = DOC_W | |
| def coordinates_around( i, j, imax, jmax ): | |
| """Returns the neighbor coordinates to (i,j).""" | |
| for ox in (-1,0,1): | |
| for oy in (-1,0,1): | |
| yield ((i+ox)%imax, (j+oy)%jmax) | |
| def _suppress( *args ): | |
| pass | |
| ViewBase.setup_menus = _suppress | |
| # Kluuuuuuuudge. | |
| # Keystrokes just give some weird error message about ignoring an exception | |
| # related to ViewBase having no 'setup_menus' attribute. So... we do this. | |
| class SpeedSlider( Slider ): | |
| def __init__( self, document, label=None ): | |
| self.label = label | |
| self.document = document | |
| Slider.__init__( self, orient='h', max_value=10, live=False, | |
| discrete=False, width=300, value=10*log(4*document.speed,240) ) | |
| def action( self ): | |
| speed = 1/4. * 240**(self.value/10) | |
| self.document.set_speed( speed ) | |
| self.label.text = 'Speed: %.2f' %speed | |
| class ConwayApp( Application ): | |
| def __init__( self ): | |
| Application.__init__( self ) | |
| self.file_type = FileType( name="Conway Document", suffix="cny" ) | |
| def open_app( self ): | |
| self.new_cmd() | |
| def make_document( self, fileref ): | |
| return ConwayDocument() | |
| def make_window( self, document ): | |
| """Makes a window associated with a document, and a control window.""" | |
| # First, let's make the control window. | |
| startstop_frame = TitledItemFrame( 'Start/Stop', | |
| StateChangingButton( (('Start', document.start), | |
| ('Stop', document.stop)) ) ) | |
| slider_frame = TitledItemFrame( 'Speed: 1.00', SpeedSlider(document) ) | |
| slider_frame.label.width += 30 | |
| slider_frame.item.label = slider_frame.label | |
| control_window = ControlWindow( omnipresent=[startstop_frame, slider_frame] ) | |
| control_window.show() | |
| win = Window( document=document, resizable=False ) | |
| cell_view = CellView( document ) | |
| win.place( cell_view, left=WINDOW_PADDING, top=WINDOW_PADDING ) | |
| win.shrink_wrap() | |
| win.show() | |
| class ConwayDocument( Document ): | |
| def __init__( self ): | |
| Document.__init__( self ) | |
| self.grid = None | |
| self.speed = None | |
| self.updater = None | |
| self.going = False | |
| def new_contents( self, width=DOC_W, height=DOC_H ): | |
| self.grid = Grid( width, height ) | |
| self.set_speed( 1 ) | |
| def read_contents( self, from_file ): | |
| states = pickle.load(from_file) | |
| self.grid = Grid(len(states), len(states[0])) | |
| for i in range(self.grid.width): | |
| for j in range(self.grid.height): | |
| if states[i][j]: | |
| self.grid.toggle( i, j ) | |
| self.set_speed( 1 ) | |
| def write_contents( self, to_file ): | |
| # We'd like to just pickle self.cells, but neighbor lists are circular, so Pickle will explode. | |
| # We'll just record the states instead. | |
| states = [ [self.grid[i,j].on for j in range(self.grid.height)] for i in range(self.grid.width) ] | |
| pickle.dump( states, to_file ) | |
| def start( self ): | |
| self.going = True | |
| self.updater.start() | |
| def stop( self ): | |
| self.going = False | |
| self.updater.stop() | |
| def set_speed( self, speed ): | |
| if self.going: | |
| self.updater.stop() | |
| self.speed = speed | |
| self.updater = Task( self.step, interval=1./speed, repeat=True, start=self.going ) | |
| def toggle( self, i, j ): | |
| self.grid.toggle( i, j ) | |
| self.notify_views('cell_toggled', i, j ) | |
| self.changed() | |
| def step( self ): | |
| self.grid.step() | |
| for i, j in self.grid.recently_toggled: | |
| self.notify_views('cell_toggled', i, j ) | |
| self.changed() | |
| class CellView( View ): | |
| def __init__( self, document ): | |
| w,h = document.grid.width*CELL_SIZE, document.grid.height*CELL_SIZE | |
| self.first_draw = True | |
| View.__init__( self, model=document, size=(w,h), border=True ) | |
| def draw( self, canvas, update_rect ): | |
| if self.first_draw: | |
| print "Drawing." | |
| self.first_draw = False | |
| canvas.pencolor = red | |
| canvas.newpath() | |
| for x in range( CELL_SIZE, self.model.grid.width*CELL_SIZE+1, CELL_SIZE ): | |
| # print "x =", x | |
| canvas.moveto( x, 0 ) | |
| canvas.lineto( x, self.model.grid.height*CELL_SIZE ) | |
| for y in range( CELL_SIZE, self.model.grid.height*CELL_SIZE+1, CELL_SIZE ): | |
| # print "y =", y | |
| canvas.moveto( 0, y ) | |
| canvas.lineto( self.model.grid.width*CELL_SIZE, y ) | |
| canvas.stroke() | |
| for i in range(self.model.grid.width): | |
| for j in range(self.model.grid.height): | |
| draw_cell( i, j, canvas, (black if self.model.grid[i,j].on else white) ) | |
| for (i,j) in self.model.grid.recently_toggled: | |
| draw_cell( i, j, canvas, (black if self.model.grid[i,j].on else white) ) | |
| def cell_toggled( self, model, i, j ): | |
| # print "Cell %f, %f toggled!" %changed_cell | |
| left = i * CELL_SIZE | |
| top = j * CELL_SIZE | |
| right = left + CELL_SIZE-1 | |
| bottom = top + CELL_SIZE-1 | |
| # print "Passing (%.2f,%.2f,%.2f,%.2f)" %(left,top,right,bottom) | |
| self.invalidate_rect( (left,top,right,bottom) ) | |
| def mouse_down( self, event ): | |
| """Toggles the cell that was clicked on.""" | |
| x,y = event.position | |
| i,j = int((x / CELL_SIZE)), int((y / CELL_SIZE)) | |
| # print "Click! ", x,y, i,j | |
| self.model.toggle(i,j) | |
| def draw_cell( i, j, canvas, fillcolor ): | |
| canvas.fillcolor = fillcolor | |
| canvas.newpath() | |
| left,right = i*CELL_SIZE, (i+1)*CELL_SIZE - 1 | |
| top,bottom = j*CELL_SIZE, (j+1)*CELL_SIZE - 1 | |
| canvas.rect( (left,top,right,bottom) ) | |
| canvas.fill() | |
| #canvas.newpath() | |
| #canvas.moveto( right, top ) | |
| #canvas.lineto( right, bottom ) | |
| #canvas.lineto( left, bottom ) | |
| #canvas.stroke() | |
| class Grid( object ): | |
| def __init__( self, width, height ): | |
| self.width = width | |
| self.height = height | |
| self.cells = [[Cell() for j in range(height)] for i in range(width)] | |
| for i in range(width): | |
| for j in range(height): | |
| for k,l in coordinates_around(i, j, width, height): | |
| if (k==i) and (l==j): | |
| continue | |
| self.cells[i][j].neighbors.append( self.cells[k][l] ) | |
| self.interesting_cells = set([]) | |
| self.recently_toggled = set([]) | |
| def __getitem__( self, ij ): | |
| i, j = ij | |
| return self.cells[i][j] | |
| def toggle( self, i, j ): | |
| # print 'Toggled %d, %d!' %(i,j) | |
| c = self.cells[i][j] | |
| c.toggle() | |
| for k,l in coordinates_around(i, j, self.width, self.height): | |
| self.interesting_cells.add( (k,l) ) | |
| self.recently_toggled.add( (i,j) ) | |
| def step( self ): | |
| """Steps forward by one generation.""" | |
| toggle_cells = [ (i,j) for (i,j) in self.interesting_cells if self.cells[i][j].should_toggle() ] | |
| self.interesting_cells = set([]) | |
| self.recently_toggled = set([]) | |
| for i,j in toggle_cells: | |
| self.toggle( i, j ) | |
| class Cell( object ): | |
| def __init__( self, on=False ): | |
| self.on = on | |
| self.neighbors = [] | |
| def on_neighbors( self ): | |
| result = 0 | |
| for n in self.neighbors: | |
| if n.on: | |
| result += 1 | |
| return result | |
| def toggle( self ): | |
| # print "Toggled! Going from",self.on,"to",not self.on | |
| self.on = not self.on | |
| def should_toggle( self ): | |
| """Returns whether the cell should toggle next generation.""" | |
| if self.on: | |
| return self.on_neighbors() not in (2,3) | |
| else: | |
| return self.on_neighbors() == 3 | |
| if __name__ == '__main__': | |
| app = ConwayApp() | |
| app.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment