Welcome, guest | Sign In | My Account | Store | Cart
__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()

History