0
\$\begingroup\$

This is Conway's Game of Life written in Python and using Tkinter for GUI.

Problems are:

  1. The "Play" button works so slowly. I can see how the canvas is redrawing from top to bottom. But when I quickly click on the "step" button - I see that I want to get from "play" button: I don't see redrawing process. How can I make it faster and proper?

  2. The __init__ method confuses me as it's so large. Are there a ways to reorganize methods or have I done it right?

  3. How can I make this code more pythonic?

gui.py

# -*- coding: utf-8 -*-
from Tkinter import *
from processor import *
from functools import partial
import threading


class LifeGUI:
    def __init__(self, height, width):
        self.height = height
        self.width = width
        self.prev_x = self.prev_y = None
        self.root = Tk()
        self.field = init_field(height=self.height, width=self.width)
        self.canvas = Canvas(self.root, width=10 * len(self.field[0]), height=10 * len(self.field))
        self.clear()
        self.next_button = Button(self.root, bg="blue", text="step", command=self.step)
        self.next_button.pack()
        self.clear_button = Button(self.root, bg="yellow", text="clean", command=self.clear)
        self.clear_button.pack()
        self.clear_button = Button(self.root, bg="green", text="play", command=partial(self.step, {'repeat': True}))
        self.clear_button.pack()
        self.clear_button = Button(self.root, bg="red", text="stop", command=self.stop)
        self.clear_button.pack()
        self.canvas.bind("<Button-1>", self.switch_cell)
        self.canvas.bind("<B1-Motion>", self.switch_cell)
        self.canvas.bind("<ButtonRelease-1>", self.forget)
        self.canvas.pack()
        self.timer = threading.Timer(0.1, partial(self.step, {'repeat': True}))
        self.root.mainloop()

    def switch_cell(self, event):
        x = event.x / 10
        y = event.y / 10
        if (self.prev_x, self.prev_y) != (x, y):
            self.field[y][x] = not self.field[y][x]
            self.redraw()
        self.prev_x, self.prev_y = x, y

    def forget(self, event):
        self.prev_y = self.prev_x = None

    def step(self, repeat=False):
        self.field = next_step(self.field)
        self.redraw()
        if repeat:
            self.timer = threading.Timer(0.1, partial(self.step, {'repeat': True}))
            self.timer.start()

    def stop(self):
        self.timer.cancel()
        self.timer = None

    def clear(self):
        self.field = init_field(height=self.height, width=self.width)
        self.redraw()

    def redraw(self):
        self.canvas.delete("all")
        for i in range(len(self.field)):
            for j in range(len(self.field[0])):
                self.canvas.create_rectangle(j * 10, i * 10, (j + 1) * 10 - 2, (i + 1) * 10 - 2, fill="blue" if self.field[i][j] else "orange")



def main():

    LifeGUI(40, 40)

if __name__ == '__main__':
    main()

processor.py

# -*- coding: utf-8 -*-
def init_field(height=10, width=10, alives=()):
    fld = [[False] * width for _ in range(height)]
    for alive in alives:
        fld[alive[0]][alive[1]] = True
    return fld

def alive_neighbours(field, x, y):
    result = []
    for i in range(-int(y != 0), int(y != len(field[0]) - 1) + 1):
        for j in range(-int(x != 0), int(x != len(field) - 1) + 1):
            if (i or j) and field[x + j][y + i]:
                result.append((x + j, y + i))
    return result

def next_step(fld):
    result = [row[:] for row in fld]
    for i in range(len(fld)):
        for j in range(len(fld[1])):
            if fld[i][j] and len(alive_neighbours(fld, i, j)) not in (2, 3):
                result[i][j] = False
            elif len(alive_neighbours(fld, i, j)) == 3:
                result[i][j] = True
    return result
\$\endgroup\$
2
\$\begingroup\$

A few things to make your code better/faster :


You can use tuple unpacking to rewrite :

def init_field(height=10, width=10, alives=()):
    fld = [[False] * width for _ in range(height)]
    for alive in alives:
        fld[alive[0]][alive[1]] = True
    return fld

like this :

def init_field(height=10, width=10, alives=()):
    fld = [[False] * width for _ in range(height)]
    for x, y in alives:
        fld[x][y] = True
    return fld

You could compute alive_neighbours less often by storing the result :

        nb_neighbours = len(alive_neighbours(fld, i, j))
        if fld[i][j] and nb_neighbours not in (2, 3):
            result[i][j] = False
        elif nb_neighbours == 3:
            result[i][j] = True

I must confess that I find alive_neighbours really complicated.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks. But my gui works slowly yet. Are there an alternatives of using threading.Timer? \$\endgroup\$ Jun 10 '15 at 6:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.