-
Notifications
You must be signed in to change notification settings - Fork 0
/
gui.py
executable file
·538 lines (441 loc) · 20.6 KB
/
gui.py
1
# -*- coding: utf-8 -*- ############################################################################# Copyright Robert-Gabor Varga## http://robert-varga.com#### 210CT - Programming, Algorithms and Data Structures - Challenge 2###########################################################################import wxfrom wx.lib.buttons import GenBitmapTextButtonimport json as simplejsonimport randomimport os############################################################################# Class GUI_mainFrame###########################################################################class GUI_mainFrame ( wx.Frame ): def __init__(self, parent): """ :param parent: """ # window configuration self.windowW = 330 #680 self.windowH = 160 #380 wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"Conway's Game of Life :: Robert-Gabor Varga", pos = wx.DefaultPosition, size = wx.Size( self.windowW, self.windowH ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize ) bSizer1 = wx.BoxSizer( wx.VERTICAL ) self.m_panel1 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) bSizer2 = wx.BoxSizer( wx.HORIZONTAL ) fgSizer1 = wx.FlexGridSizer( 2, 4, 0, 0 ) fgSizer1.SetFlexibleDirection( wx.BOTH ) fgSizer1.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) # sizer 3 bSizer3 = wx.BoxSizer( wx.VERTICAL ) self.sizeLabel = wx.StaticText( self.m_panel1, wx.ID_ANY, u"Size:", wx.DefaultPosition, wx.DefaultSize, 0 ) self.sizeLabel.Wrap( -1 ) bSizer3.Add( self.sizeLabel, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.EXPAND|wx.ALL, 5 ) self.presetLabel = wx.StaticText( self.m_panel1, wx.ID_ANY, u"Preset:", wx.DefaultPosition, wx.DefaultSize, 0 ) self.presetLabel.Wrap( -1 ) bSizer3.Add( self.presetLabel, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.EXPAND|wx.ALL, 5 ) self.stepsCountLabel = wx.StaticText( self.m_panel1, wx.ID_ANY, u"Steps:", wx.DefaultPosition, wx.DefaultSize, 0 ) self.stepsCountLabel.Wrap( -1 ) bSizer3.Add( self.stepsCountLabel, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.EXPAND|wx.ALL, 5 ) self.speedLabel = wx.StaticText( self.m_panel1, wx.ID_ANY, u"Speed:", wx.DefaultPosition, wx.DefaultSize, 0 ) self.speedLabel.Wrap( -1 ) bSizer3.Add( self.speedLabel, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.EXPAND|wx.ALL, 5 ) fgSizer1.Add( bSizer3, 1, wx.EXPAND, 5 ) # sizer 4 bSizer4 = wx.BoxSizer( wx.VERTICAL ) self.sizeTextCtrl = wx.TextCtrl( self.m_panel1, wx.ID_ANY, u"25", wx.DefaultPosition, wx.DefaultSize, 0 ) self.sizeTextCtrl.SetMaxLength( 3 ) bSizer4.Add( self.sizeTextCtrl, 0, wx.ALL, 5 ) selectionPresetChoices = [ u"Select", u"Blinker", u"Glider", u"R-pentomino", u"Toad", u"*Random" ] self.selectionPreset = wx.Choice( self.m_panel1, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, selectionPresetChoices, 0 ) self.selectionPreset.SetSelection( 0 ) bSizer4.Add( self.selectionPreset, 0, wx.ALL, 0 ) # sizer7 inside sizer4 bSizer7 = wx.BoxSizer( wx.HORIZONTAL ) self.stepsCount = wx.StaticText( self.m_panel1, wx.ID_ANY, u"0", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_LEFT ) self.stepsCount.Wrap( -1 ) bSizer7.Add( self.stepsCount, 0, wx.ALL, 8 ) self.infoText = wx.StaticText( self.m_panel1, wx.ID_ANY, u"", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT ) self.infoText.Wrap( -1 ) bSizer7.Add( self.infoText, 1, wx.ALL|wx.EXPAND, 8 ) bSizer4.Add( bSizer7, 0, wx.EXPAND, 8 ) # speed ranges from 1 milisecond to 2 seconds self.speedSlider = wx.Slider(self.m_panel1, wx.ID_ANY, 10, 1, 20, style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS ) self.speedSlider.SetTickFreq(5, 1) self.speedSlider.SetDimensions(100, 330, 250, -1) bSizer4.Add( self.speedSlider, 0, wx.ALL, -6 ) fgSizer1.Add( bSizer4, 1, wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL, 5 ) # sizer 5 bSizer5 = wx.BoxSizer( wx.VERTICAL ) bSizer5.SetMinSize( wx.Size( 20,-1 ) ) self.line = wx.StaticLine(self.m_panel1, -1, size=(2,127), style=wx.LI_VERTICAL) self.line.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_CAPTIONTEXT ) ) bSizer5.Add( self.line, 0, wx.ALIGN_CENTER_HORIZONTAL, 5 ) fgSizer1.Add( bSizer5, 0, 0, 5 ) # sizer 6 bSizer6 = wx.BoxSizer( wx.VERTICAL ) # buttons self.displayButton = wx.Button( self.m_panel1, wx.ID_ANY, u"Display", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer6.Add( self.displayButton, 0, wx.ALL, 5 ) self.startButton = wx.Button( self.m_panel1, wx.ID_ANY, u"Start", wx.Point( -1,-1 ), wx.DefaultSize, 0 ) bSizer6.Add( self.startButton, 0, wx.ALL, 5 ) self.stepButton = wx.Button( self.m_panel1, wx.ID_ANY, u"Step", wx.Point( -1,-1 ), wx.DefaultSize, 0 ) bSizer6.Add( self.stepButton, 0, wx.ALL, 5 ) self.clearButton = wx.Button( self.m_panel1, wx.ID_ANY, u"Clear", wx.Point( -1,-1 ), wx.DefaultSize, 0 ) bSizer6.Add( self.clearButton, 0, wx.ALL, 5 ) # END buttons fgSizer1.Add( bSizer6, 1, wx.ALIGN_RIGHT|wx.EXPAND, 5 ) bSizer2.Add( fgSizer1, 1, wx.EXPAND, 5 ) self.m_panel1.SetSizer( bSizer2 ) self.m_panel1.Layout() bSizer2.Fit( self.m_panel1 ) bSizer1.Add( self.m_panel1, 1, wx.EXPAND, 5 ) self.SetSizer( bSizer1 ) self.Layout() # menu bar self.menuBar = wx.MenuBar( 0 ) self.menu1 = wx.Menu() self.menuItemOpen = wx.MenuItem( self.menu1, wx.ID_ANY, u"Open\tCtrl+O", wx.EmptyString, wx.ITEM_NORMAL ) #Ctrl+O self.menu1.AppendItem( self.menuItemOpen ) self.menuItemSaveAs = wx.MenuItem( self.menu1, wx.ID_ANY, u"Save As\tCtrl+Shift+S", wx.EmptyString, wx.ITEM_NORMAL ) #Ctrl+Shift+S self.menu1.AppendItem( self.menuItemSaveAs ) self.menuItemSave = wx.MenuItem( self.menu1, wx.ID_ANY, u"Save\tCtrl+S", wx.EmptyString, wx.ITEM_NORMAL ) #Ctrl+S self.menu1.AppendItem( self.menuItemSave ) self.menuItemQuit = wx.MenuItem( self.menu1, wx.ID_ANY, u"Quit\tCtrl+Q", wx.EmptyString, wx.ITEM_NORMAL ) #Ctrl+Q self.menu1.AppendItem( self.menuItemQuit ) self.menuBar.Append( self.menu1, u"File" ) self.SetMenuBar( self.menuBar ) self.Centre( wx.BOTH ) #timer self.timer = wx.Timer(self) # Connect Events self.displayButton.Bind( wx.EVT_BUTTON, self.Grid ) self.stepButton.Bind( wx.EVT_BUTTON, self.OnStepEnter ) self.clearButton.Bind( wx.EVT_BUTTON, self.OnClearEnter ) self.startButton.Bind( wx.EVT_BUTTON, self.OnStartEnter ) self.selectionPreset.Bind(wx.EVT_CHOICE, self.Choice) self.Bind( wx.EVT_TIMER, self.OnStepEnter, self.timer ) self.Bind(wx.EVT_TOOL, self.OnOpenFile, self.menuItemOpen) self.Bind(wx.EVT_TOOL, self.OnSaveAsFile, self.menuItemSaveAs) self.Bind(wx.EVT_TOOL, self.OnSaveFile, self.menuItemSave) self.Bind(wx.EVT_TOOL, self.OnQuit, self.menuItemQuit) # Default grid start 25x25 cells self.Grid(False) # fresh start self.last_name_saved = '' self.modify = False # --- MENU Bar --- # open file def OnOpenFile(self, event): file_name = os.path.basename(self.last_name_saved) # prompt user to save the current file before opening a new one if self.modify: dlg = wx.MessageDialog(self, 'Save changes?', '', wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION) val = dlg.ShowModal() if val == wx.ID_YES: self.OnSaveFile(False) self.DoOpenFile() elif val == wx.ID_CANCEL: dlg.Destroy() else: self.DoOpenFile() else: self.DoOpenFile() # import file data def DoOpenFile(self): wcd='Text format (*.txt)|*.txt|' dir = os.getcwd() open_dlg = wx.FileDialog(self, message='Choose a file', defaultDir=dir, defaultFile='', wildcard=wcd, style=wx.OPEN|wx.CHANGE_DIR) if open_dlg.ShowModal() == wx.ID_OK: path = open_dlg.GetPath() # open file try: file = open(path, 'r') data = file.read() data = eval(data) #convert string into list of bool file.close() #reconfigure cell grid size and cells state self.sizeTextCtrl.SetValue(str(len(data))) self.Grid(False) for i in range(0,self.cells): for j in range(0,self.cells): #save state of each cell self.shapes[i][j].state = data[i][j] self.ColorChanger() self.last_name_saved = path self.modify = False # error handling except IOError, error: dlg = wx.MessageDialog(self, 'Error opening file\n' + str(error)) dlg.ShowModal() except UnicodeDecodeError, error: dlg = wx.MessageDialog(self, 'Error opening file\n' + str(error)) dlg.ShowModal() #close dialog open_dlg.Destroy() # save data def OnSaveFile(self, event): if self.last_name_saved: try: file = open(self.last_name_saved, 'w') #temporary data array data = [[False for row in range(0,self.cells)] for col in range(0,self.cells)] for i in range(0,self.cells): for j in range(0,self.cells): #save state of each cell data[i][j] = self.shapes[i][j].state #write data to file file.write(str(data)) file.close() self.modify = False except IOError, error: dlg = wx.MessageDialog(self, 'Error saving file\n' + str(error)) dlg.ShowModal() else: self.OnSaveAsFile(event) # save as data def OnSaveAsFile(self, event): # file format wcd='Text format (*.txt)|*.txt|' dir = os.getcwd() # dialog settings save_dlg = wx.FileDialog(self, message='Save file as...', defaultDir=dir, defaultFile='', wildcard=wcd, style=wx.SAVE | wx.OVERWRITE_PROMPT) if save_dlg.ShowModal() == wx.ID_OK: path = save_dlg.GetPath() # try wrtiting the file try: file = open(path, 'w') #temporary data array data = [[False for row in range(0,self.cells)] for col in range(0,self.cells)] for i in range(0,self.cells): for j in range(0,self.cells): #save state of each cell data[i][j] = self.shapes[i][j].state #write data to file file.write(str(data)) file.close() self.last_name_saved = path #os.path.basename(path) self.modify = False # if error occured except IOError, error: dlg = wx.MessageDialog(self, 'Error saving file\n' + str(error)) dlg.ShowModal() save_dlg.Destroy() #Quit def OnQuit(self, event): self.Close() # --- Preset choice --- def Choice(self, event): choice = self.selectionPreset.GetStringSelection() #add presets only if grid greater than 5x5 if self.cells > 5 and choice!='Select': if choice == '*Random': #random number of cells to activate activeNumber = int(round(random.uniform(1,self.cells*2))) for i in range(0,activeNumber): #get random cells, exclude border line = random.choice( [sublist for sublist in self.shapes[1:-3]] ) row = random.choice( [sublist for sublist in line[1:-3]] ) row.state = True else: #read from presets file based on user selection data = self.DataRead(choice) for location in data: #coordinates for each cell y=self.cells/2 + location[0] x=self.cells/2 + location[1] #activate cell self.shapes[y][x].state = True #change color of active cells self.ColorChanger() elif choice!='Select': # alert user dlg = wx.MessageDialog(self, 'Grid to small\n', 'Warning', wx.OK) dlg.ShowModal() # --- reads presets from FILE --- def DataRead(self, option): #open file with open("presets.json") as f: #load as json format temp = simplejson.load(f) #returns preset return temp[0][option] #close file f.close() # --- clear the grid (REMOVE buttons) --- def ClearGrid(self): #set numer of steps to 0 self.stepsCount.SetLabel('0') # delete all cells try: for i in range(0,self.cells): for j in range(0,self.cells): self.shapes[i][j].Destroy() except: pass # --- draw grid (buttons) --- def Grid(self, event): #clear the grid first self.ClearGrid() coordiantes=self.BoundingRect() self.w=13 self.h=13 #number of cells (from input) self.cells = int(self.sizeTextCtrl.GetValue()) # create empty adjacency lists self.shapes = [[False for row in range(0,self.cells)] for col in range(0,self.cells)] self.temp = [[False for row in range(0,self.cells)] for col in range(0,self.cells)] #drawing _x=0 _y=0 _id=0 for i in range(0,self.cells): for j in range(0,self.cells): # try: self.shapes[i][j].Destroy() # except: pass self.square = GenBitmapTextButton(self, _id, None, '', (coordiantes['x']+_x, coordiantes['y']+_y), (self.w, self.h)) #initial cell color and border if i==0 or i==self.cells-1 or j==0 or j==self.cells-1: #border self.square.SetBackgroundColour('grey') self.square.SetBezelWidth(1) self.square.Disable() else: #cell self.square.SetBackgroundColour('white') self.square.SetBezelWidth(4) self.square.state = False #not active self.square.Bind( wx.EVT_BUTTON, self.OnBShapeEnter ) #stores all objects into a list self.shapes[i][j] = self.square _x=_x+self.w _id=_id+1 _y=_y+self.h _x=0 # resize window depending on how large is the grid newWindowW = self.windowW+_y+15 newWindowH = self.windowH+_y-120 # prevents reducing window over the limit if newWindowW > self.windowW and newWindowH > self.windowH: self.SetSize((self.windowW+_y+15, self.windowH+_y-120)) # --- CELL is PRESSED --- def OnBShapeEnter(self, event): self.modify = True obj = event.GetEventObject() # print obj.GetBackgroundColour() #delete # print obj.GetId() #delete # print obj.state #delete #change button state and colour on press obj.state = not obj.state if obj.state == False: color="white" else: color="red" obj.SetBackgroundColour(color) obj.Refresh() # --- STEP BUTTON --- def OnStepEnter(self, event): self.modify = True self.stepsCount.SetLabel ( str(int(self.stepsCount.GetLabelText())+1) ) self.Compute() self.ColorChanger() # --- CLEAR BUTTON --- def OnClearEnter(self, event): #clear cells self.Grid(False) #clear info label self.infoText.SetLabel('') #stop timer if Active self.OnStartEnter(False, True) # --- SART BUTTON --- def OnStartEnter(self, event, state=False): speed = self.speedSlider.GetValue() * 100 # button label btnLabel = self.startButton.GetLabel() if btnLabel == "Start" and not state: # print "starting timer..." self.timer.Start(speed) self.startButton.SetLabel("Stop") else: # print "timer stopped!" self.timer.Stop() self.startButton.SetLabel("Start") # --- compute the next state --- def Compute(self): # iterate except border for y in range(1,self.cells-1): for x in range(1,self.cells-1): #number of live Neighbours numNeighbours = self.Neighbours(y,x) # Based on rules, compute NEXT STATE # Rule 1 - Any live cell with fewer than two live neighbours dies, as if caused by under-population. if self.shapes[y][x].state == True and numNeighbours < 2: self.temp[y][x] = False # Rule 2 - Any live cell with two or three live neighbours lives on to the next generation. if self.shapes[y][x].state == True and (numNeighbours == 2 or numNeighbours == 3): self.temp[y][x] = True # Rule 3 - Any live cell with more than three live neighbours dies, as if by overcrowding. if self.shapes[y][x].state == True and numNeighbours > 3: self.temp[y][x] = False # Rule 4 - Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. if self.shapes[y][x].state == False and numNeighbours == 3: self.temp[y][x] = True # detect if shapes are stable self.StateDetection() #update shapes based on temporary adjacency list for y in range(1,self.cells-1): for x in range(1,self.cells-1): self.shapes[x][y].state = self.temp[x][y] # --- counts the live neighbours --- def Neighbours(self, a, b): lives = 0 if self.shapes[a-1][b+1].state == True: lives += 1 if self.shapes[a][b+1].state == True: lives += 1 if self.shapes[a+1][b+1].state == True: lives += 1 if self.shapes[a-1][b].state == True: lives += 1 if self.shapes[a+1][b].state == True: lives += 1 if self.shapes[a-1][b-1].state == True: lives += 1 if self.shapes[a][b-1].state == True: lives += 1 if self.shapes[a+1][b-1].state == True: lives += 1 return lives # --- Frame size --- def BoundingRect(self): coordiantes = {"x":332, "y":6} return coordiantes # --- Change cell color based on state --- def ColorChanger(self): for y in range(1,self.cells-1): for x in range(1,self.cells-1): if self.shapes[y][x].state == True: color="red" else: color="white" self.shapes[y][x].SetBackgroundColour(color) self.shapes[y][x].Refresh() # --- Check if configuration is stable --- def StateDetection(self): # if grid is modified if self.modify == True: totalCount = (self.cells-2)*(self.cells-2) matchCount = 0 emptyCount = 0 # count the number of matches between previous grid and current for y in range(1,self.cells-1): for x in range(1,self.cells-1): if self.shapes[y][x].state == self.temp[y][x]: matchCount+=1 if self.temp[y][x] == False: emptyCount+=1 # grid is modified and empty if emptyCount == totalCount: self.infoText.SetLabel('\t\tEmpty') # if the grid is stable the number of cells in the modified grid = total number of cells elif matchCount == totalCount: self.infoText.SetLabel('\t\tStable') else: self.infoText.SetLabel('')