2018-12-18 18:07:16 +00:00
#!/usr/bin/env python
# John Conway's "Game of Life" using a GUI.
# Copyright (C) 2018 PySimpleGUI.org
# GUI provided by PySimpleGUI.
# Core game engine provied by Christian Jacobs
# An implementation of Conway's Game of Life in Python.
# Copyright (C) 2013 Christian Jacobs.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import numpy
2019-05-08 18:52:56 +00:00
import PySimpleGUI as sg # Take your pick! Tkinter
# import PySimpleGUIWeb as sg # Or the Web! (Remi!)
2018-12-18 18:07:16 +00:00
BOX_SIZE = 15
class GameOfLife :
def __init__ ( self , N = 20 , T = 200 ) :
""" Set up Conway ' s Game of Life. """
# Here we create two grids to hold the old and new configurations.
# This assumes an N*N grid of points.
# Each point is either alive or dead, represented by integer values of 1 and 0, respectively.
self . N = N
self . old_grid = numpy . zeros ( N * N , dtype = ' i ' ) . reshape ( N , N )
self . new_grid = numpy . zeros ( N * N , dtype = ' i ' ) . reshape ( N , N )
self . T = T # The maximum number of generations
# Set up a random initial configuration for the grid.
for i in range ( 0 , self . N ) :
for j in range ( 0 , self . N ) :
self . old_grid [ i ] [ j ] = 0
self . init_graphics ( )
self . manual_board_setup ( )
def live_neighbours ( self , i , j ) :
""" Count the number of live neighbours around point (i, j). """
s = 0 # The total number of live neighbours.
# Loop over all the neighbours.
for x in [ i - 1 , i , i + 1 ] :
for y in [ j - 1 , j , j + 1 ] :
if ( x == i and y == j ) :
continue # Skip the current point itself - we only want to count the neighbours!
if ( x != self . N and y != self . N ) :
s + = self . old_grid [ x ] [ y ]
# The remaining branches handle the case where the neighbour is off the end of the grid.
# In this case, we loop back round such that the grid becomes a "toroidal array".
elif ( x == self . N and y != self . N ) :
s + = self . old_grid [ 0 ] [ y ]
elif ( x != self . N and y == self . N ) :
s + = self . old_grid [ x ] [ 0 ]
else :
s + = self . old_grid [ 0 ] [ 0 ]
return s
def play ( self ) :
""" Play Conway ' s Game of Life. """
# Write the initial configuration to file.
self . t = 1 # Current time level
while self . t < = self . T : # Evolve!
# print( "At time level %d" % t)
# Loop over each cell of the grid and apply Conway's rules.
for i in range ( self . N ) :
for j in range ( self . N ) :
live = self . live_neighbours ( i , j )
if ( self . old_grid [ i ] [ j ] == 1 and live < 2 ) :
self . new_grid [ i ] [ j ] = 0 # Dead from starvation.
elif ( self . old_grid [ i ] [ j ] == 1 and ( live == 2 or live == 3 ) ) :
self . new_grid [ i ] [ j ] = 1 # Continue living.
elif ( self . old_grid [ i ] [ j ] == 1 and live > 3 ) :
self . new_grid [ i ] [ j ] = 0 # Dead from overcrowding.
elif ( self . old_grid [ i ] [ j ] == 0 and live == 3 ) :
self . new_grid [ i ] [ j ] = 1 # Alive from reproduction.
# Output the new configuration.
# The new configuration becomes the old configuration for the next generation.
self . old_grid = self . new_grid . copy ( )
self . draw_board ( )
# Move on to the next time level
self . t + = 1
def init_graphics ( self ) :
2018-12-18 18:11:37 +00:00
self . graph = sg . Graph ( ( 600 , 600 ) , ( 0 , 0 ) , ( 450 , 450 ) , key = ' _GRAPH_ ' , change_submits = True , drag_submits = False , background_color = ' lightblue ' )
2018-12-18 18:07:16 +00:00
layout = [
[ sg . Text ( ' Game of Life ' , font = ' ANY 15 ' ) , sg . Text ( ' ' , key = ' _OUTPUT_ ' , size = ( 30 , 1 ) , font = ' ANY 15 ' ) ] ,
[ self . graph ] ,
[ sg . Button ( ' Go! ' , key = ' _DONE_ ' ) ,
2019-09-20 21:31:00 +00:00
sg . Text ( ' Delay (ms) ' ) , sg . Slider ( [ 0 , 800 ] , orientation = ' h ' , key = ' _SLIDER_ ' , enable_events = True , size = ( 15 , 15 ) ) , sg . T ( ' ' , size = ( 3 , 1 ) , key = ' _S1_OUT_ ' ) ,
sg . Text ( ' Num Generations ' ) , sg . Slider ( [ 0 , 20000 ] , default_value = 4000 , orientation = ' h ' , size = ( 15 , 15 ) , enable_events = True , key = ' _SLIDER2_ ' ) , sg . T ( ' ' , size = ( 3 , 1 ) , key = ' _S2_OUT_ ' ) ]
2018-12-18 18:07:16 +00:00
]
self . window = sg . Window ( ' Window Title ' , ) . Layout ( layout ) . Finalize ( )
event , values = self . window . Read ( timeout = 0 )
self . delay = values [ ' _SLIDER_ ' ]
2019-05-08 18:52:56 +00:00
self . window . Element ( ' _S1_OUT_ ' ) . Update ( values [ ' _SLIDER_ ' ] )
self . window . Element ( ' _S2_OUT_ ' ) . Update ( values [ ' _SLIDER2_ ' ] )
2018-12-18 18:07:16 +00:00
def draw_board ( self ) :
BOX_SIZE = 15
self . graph . Erase ( )
for i in range ( self . N ) :
for j in range ( self . N ) :
if self . old_grid [ i ] [ j ] :
self . graph . DrawRectangle ( ( i * BOX_SIZE , j * BOX_SIZE ) ,
( i * BOX_SIZE + BOX_SIZE , j * ( BOX_SIZE ) + BOX_SIZE ) ,
line_color = ' black ' , fill_color = ' yellow ' )
event , values = self . window . Read ( timeout = self . delay )
if event in ( None , ' _DONE_ ' ) :
exit ( )
self . delay = values [ ' _SLIDER_ ' ]
self . T = int ( values [ ' _SLIDER2_ ' ] )
2019-05-08 18:52:56 +00:00
self . window . Element ( ' _S1_OUT_ ' ) . Update ( values [ ' _SLIDER_ ' ] )
self . window . Element ( ' _S2_OUT_ ' ) . Update ( values [ ' _SLIDER2_ ' ] )
2018-12-18 18:07:16 +00:00
self . window . Element ( ' _OUTPUT_ ' ) . Update ( ' Generation {} ' . format ( self . t ) )
2019-05-08 18:52:56 +00:00
2018-12-18 18:07:16 +00:00
def manual_board_setup ( self ) :
ids = [ ]
for i in range ( self . N ) :
ids . append ( [ ] )
for j in range ( self . N ) :
ids [ i ] . append ( 0 )
while True : # Event Loop
event , values = self . window . Read ( )
if event is None or event == ' _DONE_ ' :
break
2019-05-08 18:52:56 +00:00
self . window . Element ( ' _S1_OUT_ ' ) . Update ( values [ ' _SLIDER_ ' ] )
self . window . Element ( ' _S2_OUT_ ' ) . Update ( values [ ' _SLIDER2_ ' ] )
2018-12-18 18:07:16 +00:00
mouse = values [ ' _GRAPH_ ' ]
if event == ' _GRAPH_ ' :
if mouse == ( None , None ) :
continue
box_x = mouse [ 0 ] / / BOX_SIZE
box_y = mouse [ 1 ] / / BOX_SIZE
if self . old_grid [ box_x ] [ box_y ] == 1 :
id = ids [ box_x ] [ box_y ]
self . graph . DeleteFigure ( id )
self . old_grid [ box_x ] [ box_y ] = 0
else :
id = self . graph . DrawRectangle ( ( box_x * BOX_SIZE , box_y * BOX_SIZE ) ,
( box_x * BOX_SIZE + BOX_SIZE , box_y * ( BOX_SIZE ) + BOX_SIZE ) ,
line_color = ' black ' , fill_color = ' yellow ' )
ids [ box_x ] [ box_y ] = id
self . old_grid [ box_x ] [ box_y ] = 1
self . window . Element ( ' _DONE_ ' ) . Update ( text = ' Exit ' )
if ( __name__ == " __main__ " ) :
game = GameOfLife ( N = 35 , T = 200 )
game . play ( )
2019-05-08 18:52:56 +00:00
game . window . Close ( )