I took this well-know game to design my own solution, and make a GUI for it.
To run the program, you will need to create a txt file with rows of dots, so like this:
...... ...... ...... ......
and name it empty_board.gol (I have tested this program withwith 55x55)
It is functional and not object oriented.
My game of life algorithm is probably not the best one around (suggestions welcome), but I think this program is a nice demo of with tkinter can do and how easy it is to program GUI's with tkinter.
It has been written and tested in python 3.4 but I guess any version 3 should work, and probably only minor changes are needed to make it work in 2.6 or 2.7 (e.g. change tkinter to Tkinter).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | __author__ = 'Peter'
'''
This is a simulator for Conway's game of life (google to find the rules:
v4 with additional functionality over v3:
- load or save boards to/from files: OK
- random fill (with variable density): OK
- improved layout and added menu bar: OK
- warp yes/no: OK
- show / hide grid: OK
- increase / decrease speed: OK
- improved code (PEP 8, and logical improvements, especially in modifying mutable objects in functions): OK
'''
import tkinter as tk
import tkinter.filedialog as tkf
import tkinter.font as tkfont
import tkinter.simpledialog as tkd
import sys
import random
def load_board_from_file(filename=None):
if filename is None:
filename = tkf.askopenfilename(defaultextension='.gol',
filetypes=(('game of life files', '*.gol'), ('All files', '*.*')))
board_f = open(filename, 'r')
row = board_f.readline().strip('\n')
y = 1
bd = []
while row != "":
bd.append(list(row))
y += 1
row = board_f.readline().strip('\n')
board_f.close()
# TODO insert some tests here to check if the selected file meets the required file
return bd, filename
def create_random_board(density):
for row in range(len(board)):
for col in range(len(board[0])):
r = random.randrange(100)
if r > density:
board[row][col] = "."
else:
board[row][col] = "#"
def clear():
global step
rows = len(board)
cols = len(board[0])
for row in range(rows):
for col in range(cols):
board[row][col] = "."
step = 0
display_board(board)
def count_surrounding(row, col, bd):
count = 0
rows = len(bd)
cols = len(bd[0])
w = warp.get()
for rr in range(row-1, row+2):
if rr < 0:
r = rows-1
elif rr > rows-1:
r = 0
else:
r = rr
for cc in range(col-1, col+2):
if cc < 0:
c = cols-1
elif cc > cols-1:
c = 0
else:
c = cc
if not (r == row and c == col): # de cel in kwestie overslaan!
if bd[r][c] == "#":
if not ((rr != r or cc != c) and w == 0):
count += 1
# if warping is off, then cells at the other side shouldn't be counted
return count
def lifecycle(before):
rows = len(before)
cols = len(before[0])
after = []
for row in range(rows): # initialize after
after.append(list("."*cols))
for row in range(rows):
for col in range(cols):
cs = count_surrounding(row, col, before)
if before[row][col] == "#" and cs < 2:
after[row][col] = "."
elif before[row][col] == "#" and cs > 3:
after[row][col] = "."
elif before[row][col] == "." and cs == 3:
after[row][col] = "#"
else:
after[row][col] = before[row][col]
return after
def countliving():
return str(alive)
def switch_cell(event): # turn cell on or off with mouse click
global alive
cx = event.x
cy = event.y
bx = cx//sz
by = cy//sz
if bx < len(board[0]) and by < len(board):
if board[by][bx] == ".":
board[by][bx] = "#"
else:
board[by][bx] = "."
display_board(board)
def display_board(bd):
MyCanvas.delete(tk.ALL)
cols = len(bd[0])
rows = len(bd)
counter = 0
colors = ['grey', 'orange']
ol_color = colors[showgrid.get()]
for x in range(cols):
for y in range(rows):
rect = (x*sz, y*sz, (x+1)*sz, (y+1)*sz)
if bd[y][x] == "#":
MyCanvas.create_rectangle(rect, outline="black", fill="orange")
counter += 1
else:
MyCanvas.create_rectangle(rect, outline=ol_color)
stats = "living cells: " + str(counter) + "\n\ngeneration: " + str(step)
MyCanvas.create_text((10, 10), text=stats, fill='white', anchor='nw', ) # show stats on canvas
statreport.configure(text=stats) # update stats in widget
def life(from_startbutton=False, from_stepbutton=False):
global step
global board
global pauze
global alive
step += 1
board = lifecycle(board)
display_board(board) # draw board and count living cells
if from_startbutton is True: # function has been called from start button and not from recursion (root.after...)
pauze = False # necessary for restart the startbutton is pressed after the pauzebutton
checkpauze(pauze)
if step <= steps and not pauze and not from_stepbutton:
delay = speedcontrol.get()
root.after(delay, life) # root.after(delay, life()) is WRONG: The function life needs to be passed as argument
# life() will pass the result of life, i.e. execute it, before root.mainloop()
def checkpauze(p=False):
global pauze
pauze = p
def save_board_to_file(bd):
filename = tkf.asksaveasfilename(defaultextension='.gol',
filetypes=(('game of life files', '*.gol'), ('All files', '*.*')))
if filename:
f = open(filename, 'w')
for row in range(len(bd)):
f.write(''.join(board[row]) + '\n')
f.close()
def load_board(file=None): # filename passed when reopening (resetting) same file
global board
global step
global openfile
board = []
board, openfile = load_board_from_file(file)
step = 1
display_board(board)
def rand_board():
global board
global step
global openfile
density = tkd.askinteger('Density', 'enter a cell density between 0 and 100')
create_random_board(density)
step = 1
openfile = 'empty_board.gol'
display_board(board)
def makemenu(win):
top = tk.Menu(win) # win=top-level window
win.config(menu=top) # set its menu option
filemenu = tk.Menu(top)
filemenu.add_command(label='Open...', command=load_board, underline=0)
filemenu.add_command(label='Save...', command=lambda: save_board_to_file(board), underline=0)
filemenu.add_command(label='Quit', command=sys.exit, underline=0)
top.add_cascade(label='File', menu=filemenu, underline=0)
edit = tk.Menu(top, tearoff=False)
edit.add_command(label='Clear', command=clear, underline=0)
edit.add_command(label='Randomize', command=rand_board, underline=0)
edit.add_separator()
top.add_cascade(label='Edit', menu=edit, underline=0)
######### main program ###########
alive = 0
sz = 12 # cell size for visualization
steps = 1000 # run a number of steps
# milliseconds
step = 1
board, openfile = load_board_from_file('empty_board.gol')
#warp = 0
#warp = tk.IntVar() # warp around the edges ?
pauze = False # p = pauze status false at program start
root = tk.Tk()
root.title("Conway's Game of Life")
makemenu(root)
ui = tk.Frame(root, bg='white') # user interface
ui2 = tk.Frame(root, bg='white')
# Define the user interaction widgets
MyCanvas = tk.Canvas(root, width=len(board[0])*sz + 1, height=len(board)*sz+1, highlightthickness=0, bd=0, bg='grey')
warp = tk.IntVar() # warp around the edges ?
showgrid = tk.IntVar()
# quitbutton = tk.Button(ui, text='QUIT', width=10, command=sys.exit)
startbutton = tk.Button(ui, text='START', width=10, command=lambda: life(from_startbutton=True))
pauzebutton = tk.Button(ui, text='PAUSE', width=10, command=lambda: checkpauze(p=True))
stepbutton = tk.Button(ui, text='STEP', width=10, command=lambda: life(from_stepbutton=True))
clearbutton = tk.Button(ui, text='CLEAR', width=10, command=clear)
# savebutton = tk.Button(ui, text='SAVE', width=10, command=lambda: save_board_to_file(board))
# loadbutton = tk.Button(ui, text='LOAD', width=10, command=load_board)
restartbutton = tk.Button(ui, text='RESET', width=10, command=lambda: load_board(openfile))
randombutton = tk.Button(ui, text='RANDOMIZE', width=10, command=rand_board)
statreport = tk.Label(root, text=" ", bg='white', justify=tk.LEFT, relief=tk.GROOVE,
font=tkfont.Font(weight='bold'))
speedcontrol = tk.Spinbox(ui2, width=5, values=(5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 1000))
speedcontrol_label = tk.Label(ui2, text='Delay between steps:', bg='white')
warpcontrol = tk.Checkbutton(ui2, text='Warp around edges? ', variable=warp, bg='white')
showgridcontrol = tk.Checkbutton(ui2, text='show grid? ', variable=showgrid, bg='white',
command=lambda: display_board(board))
# board must be redrawn, because toggle grid will not be shown before start is pressed
# because display_board(board) sits inside life()
showgridcontrol.select()
# quitbutton.grid(row=0, column=0, padx=10, pady=10)
startbutton.grid(row=1, column=0, padx=10, pady=10)
pauzebutton.grid(row=2, column=0, padx=10, pady=10)
stepbutton.grid(row=3, column=0, padx=10, pady=10)
clearbutton.grid(row=4, column=0, padx=10, pady=10)
# savebutton.grid(row=5, column=0, padx=10, pady=10)
# loadbutton.grid(row=6, column=0, padx=10, pady=10)
restartbutton.grid(row=7, column=0, padx=10, pady=10)
randombutton.grid(row=8, column=0, padx=10, pady=10)
speedcontrol_label.grid(row=0, column=0, columnspan=2, padx=10, pady=5, sticky=tk.NW)
speedcontrol.grid(row=0, column=2, padx=10, pady=5, sticky=tk.NW)
warpcontrol.grid(row=1, column=3, columnspan=3, padx=10, ipadx=5, sticky=tk.NW)
showgridcontrol.grid(row=0, column=3, columnspan=2, padx=10, pady=5, ipadx=5, sticky=tk.NW)
# Put everything on the screen
display_board(board)
MyCanvas.bind("<Button-1>", switch_cell)
ui.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH)
MyCanvas.pack(side=tk.TOP, expand=tk.YES, fill=tk.BOTH)
ui2.pack(side=tk.LEFT, expand=tk.YES, fill=tk.BOTH, anchor=tk.W)
statreport.pack(side=tk.RIGHT, expand=tk.NO, fill=tk.NONE)
root.mainloop()
|
I think is more usable to generate the first
empty_board.gol
Newbies: To generate the file you can paste in Python IDLE, or in the code (e.g. after imports):