1

The following is an incomplete implementation for Conway's Game Of Life in Python 3.0 using Tkinter in PyCharm Community Edition.

The program consists of four files:

  • Cell.py : Contains a class for a Cell
  • Grid.py : Contains a class for a Game of Life Grid
  • Windows.py : Contains a class for the tkinter layout
  • main.py : Executes the script

Cell.py:

from random import randint
from tkinter import *

class Cell:
    def __init__(self, master, state=None):
        if state is None:
            state = randint(0,2)
        self.state = state
        self.next_state = state
        self.button = Button(master, width=2, height=1, bg = 'black' if self.state == 1 else 'white').pack(side=LEFT)

    def __del__(self):
        self.button.destroy()

    def set_next_state(self, neighbours):
        if self.state == 1:
            self.next_state = 0 if neighbours < 2 or neighbours > 3 else 1
        else:
            self.next_state = 1 if neighbours == 3 else 0

    def update(self):
        self.state = self.next_state
        self.button.config( bg = 'black' if self.state == 1 else 'white')

Grid.py:

from tkinter import *
from Cell import Cell

class Grid:
    def __init__(self, master, rows=15, cols=15, wraparound=True, pattern=None):
        self.master = master
        self.rows = rows
        self.cols = cols
        self.wraparound = wraparound

        if len(self.pattern) == rows*cols:  self.pattern = pattern
        else:  self.pattern = None

        self.create_frames()
        self.create_cells()

    def create_frames(self):
        self.frames = [Frame(self.master).pack(side=TOP) for r in range(self.rows)]

    def destroy_frames(self):
        for r in range(self.rows):
            self.frames[r].destroy()

    def create_cells(self):
        self.cells = {}
        for r in range(self.rows):
            for c in range(self.cols):
                if self.pattern is None:  self.cells[(r,c)] = Cell(self.frames[r])
                else:  self.cells[(r,c)] = Cell(self.frames[r], state=self.pattern[r*self.cols+c])

    def destroy_cells(self):
        for r in range(self.rows):
            for c in range(self.cols):
                del self.cells[(r,c)]

    def count_neighbours(self, r, c):
        offsets = [(i,j) for j in range(-1,2) for i in range(-1,2)]
        offsets.remove((0,0))
        neighbours = 0
        for offset in offsets:
            if self.wraparound:
                neighbours += self.cells[((r+offset[0])%self.rows, (c+offset[1])%self.cols)].state
            else:
                if r+offset[0] >= self.rows or r+offset[0] < 0 or c+offset[1] >= self.cols or c+offset[1] < 0:
                    neighbours += 0
                else:
                    neighbours += self.cells[(r+offset[0], c+offset[1])].state
        return neighbours

    def calculate_next_state(self):
        for r in range(self.rows):
            for c in range(self.cols):
                neighbours = self.count_neighbours(r,c)
                self.cells[(r,c)].set_next_state(neighbours)

    def update_cells(self):
        for r in range(self.rows):
            for c in range(self.cols):
                self.cells[(r,c)].update()

    def step(self):
        self.calculate_next_state()
        self.update_cells()

     def load_pattern(self, rows=10, cols=10, wraparound=True, pattern=None):
        self.destroy_cells()
        self.destroy_frames()

        self.rows = rows
        self.cols = cols
        self.wraparound = wraparound
        self.pattern = pattern

        self.create_frames()
        self.create_cells()

Windows.py:

from Grid import Grid
from tkinter import *

class MainWindow:
    def __init__(self, rows=10, cols=10, wraparound=True, pattern=None):
        self.root = Tk()

        grid_frame = Frame(self.root).pack(side=TOP)
        self.grid = Grid(grid_frame, rows=rows, cols=cols, wraparound=wraparound, pattern=pattern)

        button_frame = Frame(self.root).pack(side=BOTTOM)
        self.time_interval = 1000
        self.start_stop_button = Button(button_frame, text='START', command=self.start_stepper).pack(side=LEFT)
        self.step_button = Button(button_frame, text='STEP', command=self.grid.step)

        self.menubar = Menu(self.root)
        self.menubar.add_command(label='Change Time Interval', command=self.change_time_interval)
        self.root.config(menu=self.menubar)

        self.root.mainloop()

    def change_time_interval(self):
        # Incomplete
        pass

    def start_stepper(self):
        # Incomplete
        pass

    def stop_stepper(self):
        # Incomplete
        pass

main.py:

from Windows import MainWindow
game = MainWindow()

When I run main.py I get the following output:

Output:

Traceback (most recent call last):
  File "C:/Users/Tyler/PycharmProjects/GameOfLife/main.py", line 3, in <module>
    game = MainWindow()
  File "C:\Users\Tyler\PycharmProjects\GameOfLife\Windows.py", line 9, in __init__
    self.grid = Grid(grid_frame, rows=rows, cols=cols, wraparound=wraparound, pattern=pattern)
TypeError: object.__new__() takes no parameters

I am at my wits end with this as I never overwrite the default __new__ method. After checking all of the other stack overflow questions of this type most solutions were correcting some form of spelling __init__ to __init__. I am relatively new to python scripting so any help to debug this with an explenation as to why the error is produced would be most appreciated.

Chubzorz
  • 135
  • 1
  • 2
  • 7
  • All your problems come from *overriding* the `Tkinter.Grid()` class. You need then to rename your `Grid()` class to something else. – Billal Begueradj Jun 27 '16 at 02:43

1 Answers1

4

Tkinter has a class called Grid that is shadowing your own class of the same name, because you used from tkinter import * after importing your own class.

There are several possible solutions:

  1. Change your import to from Grid import Grid as MyGrid and then call MyGrid when you want to use your own Grid class.
  2. Move your from Grid import Grid import after the tkinter one (as long as you don't need to use the tkinter Grid class).
  3. Change your tkinter import to import tkinter and access tkinter classes (like Frame) with tkinter.Frame, etc.
  4. Change the name of your class to something else.
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • As always in hindsight this seems so obvious. Thank you very much. – Chubzorz Jun 27 '16 at 01:42
  • 1
    And that's why you don't use `import *`, even for tkinter. – user2357112 Jun 27 '16 at 01:44
  • 1
    @Chubzorz A common practice is to do `import tkinter as tk`. Please see http://stackoverflow.com/a/37511018/4014959 for other reasons to avoid star imports. – PM 2Ring Jun 27 '16 at 02:00
  • 1
    @user2357112: Doing a star import of tkinter brings in 135 Tkinter names. In Python 2, it's even worse: you get 175 names cluttering your namespace. That's quite a lot of potential for collisions. – PM 2Ring Jun 27 '16 at 02:03