HOME/Articles/

game_of_life

Article Outline

Example Python program game_of_life.py Python version 3.x or newer. To check the Python version use:

python --version

Modules

  • from future import print_function

  • import tkinter as tk
  • from tkinter import *
  • from tkinter import messagebox
  • import numpy as np
  • import _thread
  • import time
  • import json
  • import sys

Classes

  • class GUI:
  • class Game:

Methods

  • def init(self, num_rows, num_cols, px_per_box, bg_color, cell_color, line_wid=0):
  • def on_closing(self):
  • def init_info(self):
  • def init_dimensions(self, num_rows, num_cols):
  • def init_canvas(self):
  • def create_grid(self):
  • def create_rectangles(self):
  • def update(self, config):
  • def make_info(self, config):
  • def update_info(self, config):
  • def update_canvas(self, config):
  • def init(self, num_rows, num_cols, config):
  • def init_config(self):
  • def load_config(self, config):
  • def get_neighbours(self, state):
  • def gen_next_state(self):
  • def step(self):
  • def get_config(file):
  • def load_preset(config, preset):

Code

Python tkinter example

# for Python 2 just uncomment the line below
# from __future__ import print_function
import tkinter as tk
from tkinter import *
from tkinter import messagebox

import numpy as np
import _thread
import time
import json
import sys

class GUI:
    def __init__(self, num_rows, num_cols, px_per_box, bg_color, cell_color, line_wid=0):
        self.root         = Tk()
        self.steps         = 0
        self.line_wid   = line_wid
        self.px_per_box = px_per_box
        self.bg_color     = bg_color
        self.cell_color = cell_color
        self.to_update    = True
        self.boxes         = np.zeros((num_rows, num_cols), dtype=object)
        self.init_dimensions(num_rows, num_cols)
        self.init_canvas()
        self.create_grid()
        self.create_rectangles()
        self.init_info()
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.root.title("Conway's Game of life")

    def on_closing(self):
        self.to_update = False
        if messagebox.askokcancel("Quit", "Do you want to quit?"):
            self.root.destroy()
        else:
            self.to_update = True

    def init_info(self):
        self.info        = Label(self.root, text="", height=2, width=self.num_cols)
        self.info.pack(side=BOTTOM)

    def init_dimensions(self, num_rows, num_cols):
        self.num_rows   = num_rows
        self.num_cols   = num_cols
        self.width      = self.num_cols*self.px_per_box + (self.num_cols + 1)*self.line_wid
        self.height     = self.num_rows*self.px_per_box + (self.num_rows + 1)*self.line_wid

    def init_canvas(self):
        self.canvas = Canvas(self.root, width=self.width, height=self.height)
        self.canvas.pack()

    def create_grid(self):
        for i in range(self.num_cols+1):
            col = (self.line_wid + self.px_per_box)*i + self.line_wid/2
            self.canvas.create_line(col, 0, col, self.height, width=self.line_wid)
        for i in range(self.num_rows+1):
            row = (self.line_wid + self.px_per_box)*i + self.line_wid/2
            self.canvas.create_line(0, row, self.width, row, width=self.line_wid)

    def create_rectangles(self):
        for i in range(self.num_rows):
            for j in range(self.num_cols):
                y1 = i*self.px_per_box + (i + 1)*self.line_wid
                x1 = j*self.px_per_box + (j + 1)*self.line_wid
                self.boxes[i,j] = self.canvas.create_rectangle(x1, y1, \
                    x1+self.px_per_box, y1+self.px_per_box, fill=self.bg_color)
    def update(self, config):
        self.steps += 1
        self.update_canvas(config)
        self.update_info(config)
        if self.to_update:
            self.root.update()

    def make_info(self, config):
        population = len(np.nonzero(config)[0])
        str = "Population:\t{}\nTime Steps:\t{}"
        str = str.format(population, self.steps)
        return str


    def update_info(self, config):
        info = self.make_info(config)
        self.info.configure(text=info)

    def update_canvas(self, config):
        for i in range(self.num_rows):
            for j in range(self.num_cols):
                color = self.bg_color
                if config[i,j]:
                    color = self.cell_color
                self.canvas.itemconfig(self.boxes[i,j], fill=color)

class Game:
    def __init__(self, num_rows, num_cols, config):
        self.num_rows = num_rows
        self.num_cols = num_cols
        self.init_config()
        self.load_config(config)

    def init_config(self):
        self.next_config = np.zeros((self.num_rows, self.num_cols), dtype=bool)
        self.prev_config = np.zeros((self.num_rows, self.num_cols), dtype=bool)

    def load_config(self, config):
        init_state = config
        st_r = (self.prev_config.shape[0] - init_state.shape[0])//2
        en_r = (self.prev_config.shape[0] + init_state.shape[0])//2
        st_c = (self.prev_config.shape[1] - init_state.shape[1])//2
        en_c = (self.prev_config.shape[1] + init_state.shape[1])//2
        self.prev_config[st_r:en_r, st_c: en_c] = init_state

    def get_neighbours(self, state):
        rolls = [np.roll(np.roll(state,i,0),j,1) \
                for i in [-1,0,1] for j in [-1,0,1] if i!=0 or j!=0]
        return sum(rolls)

    def gen_next_state(self):
        num_nbrs = self.get_neighbours(self.prev_config)
        statis = self.prev_config*(num_nbrs > 1)*(num_nbrs < 4)
        newgen = np.logical_not(self.prev_config)*(num_nbrs > 2)*(num_nbrs < 4)
        self.next_config = statis + newgen

    def step(self):
        self.gen_next_state()
        self.prev_config[:,:] = self.next_config

def get_config(file):
    f = open(file, 'r')
    try:
        return json.loads(f.read())
    except:
        print('Exception in JSON file')
        return

def load_preset(config, preset):
    try:
        init_state = config['Presets'][preset]
        return np.array(init_state)
    except KeyError as e:
        print("No Preset {} in config file.".format(e), file=sys.stderr)
        sys.exit(-1)

if __name__ == '__main__':
    config_file = sys.argv[1]
    config = get_config(config_file)
    if isinstance(config, type(None)):
        sys.exit(-1)

    num_rows     = config['Game']['Rows']
    num_cols     = config['Game']['Columns']
    time_step    = config['Game']['TimeStep']
    preset        = config['Game']['Preset']

    px_per_box     = config['GUI']['CellDimension']
    bg_color     = config['GUI']['BackgroundColor']
    cell_color    = config['GUI']['CellColor']
    init_state     = load_preset(config, preset)

    if np.any(np.array(init_state.shape) > np.array([num_rows, num_cols])):
        print("Preset {} shape {} is greater than grid {}"\
            .format(preset, init_state.shape,(num_rows, num_cols)))
        sys.exit(-1)

    game         = Game(num_rows, num_cols, init_state)
    gui         = GUI(num_rows, num_cols, px_per_box, bg_color, cell_color)
    gui.update(game.prev_config)

    while gui.to_update:
        time.sleep(time_step)
        game.step()
        gui.update(game.prev_config)