Game of Life Demo Project

Backend-Demo
Python
Author

F.L

Published

January 6, 2024

Introduction

Following Realpython’s game of life to learn python packaging, command line interface, and more, “Pythonic” code style! This blog review will take apart some of the component evaluate them seperately.

The result being something like this!

Terminal View

Resources

from platform import python_version

py_version = python_version()
print("I'm using python version: %s"%(  py_version ) )
I'm using python version: 3.11.2

Create a Grid

collections object are used to createa a grid. This collection is similar to a dictionary. It looks like a dict, act like a dict.

import collections
neighbors = (
            (-1, -1),  # Above left
            (-1, 0),  # Above
            (-1, 1),  # Above right
            (0, -1),  # Left
            (0, 1),  # Right
            (1, -1),  # Below left
            (1, 0),  # Below
            (1, 1),  # Below right
        )
num_neighbors = collections.defaultdict(int)
num_neighbors[(0, 0)] =1
num_neighbors[(0, 1)] = 2
num_neighbors
defaultdict(int, {(0, 0): 1, (0, 1): 2})

If I place string here it don’t have any safeguard at all.

num_neighbors["absard"] = 'else'
num_neighbors
defaultdict(int, {(0, 0): 1, (0, 1): 2, 'absard': 'else'})
default_dict = {}
default_dict[(1,2)] = 1

The difference? > The difference is that a defaultdict will “default” a value if that key has not been set yet. If you didn’t use a defaultdict you’d have to check to see if that key exists, and if it doesn’t, set it to what you want.

The lambda is defining a factory for the default value. That function gets called whenever it needs a default value. You could hypothetically have a more complicated default function.

empty_dict = {}
num_neighbors['not exists']
try:
    empty_dict['not exists']
except KeyError as e:
    print(e)
'not exists'

Try out some of the pattern

from rplife import grid, patterns

blinker = patterns.Pattern("Blinker", {(2, 1), (2, 2), (2, 3)}) # create a Pattern data class
grid1 = grid.LifeGrid(blinker)

print(grid1.as_string([0, 0, 5, 5]))
print(grid1.evolve().as_string([0,0,5,5]))
 Blinker  
 · · · · ·
 · · · · ·
 · ♥ ♥ ♥ ·
 · · · · ·
 · · · · ·
 Blinker  
 · · · · ·
 · · ♥ · ·
 · · ♥ · ·
 · · ♥ · ·
 · · · · ·

Everytime you reexecute this code a pattern change!

for itr in range(0,2):
    print('%sth time:'%(itr + 1))
    print(grid1.evolve().as_string([0,0,5,5]))
1th time:
 Blinker  
 · · · · ·
 · · ♥ · ·
 · · ♥ · ·
 · · ♥ · ·
 · · · · ·
2th time:
 Blinker  
 · · · · ·
 · · · · ·
 · ♥ ♥ ♥ ·
 · · · · ·
 · · · · ·

Try something bigger!

import rplife.grid as grid
user_pattern = patterns.get_pattern("Glider Gun")
grid2 = grid.LifeGrid(user_pattern)
for itr in range(0, 2):
    print(grid2.evolve().as_string([0,0,20,20]))
               Glider Gun               
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · ♥ · · · · · · ·
 · · · · · · · · · · · ♥ ♥ · · · · · · ♥
 ♥ ♥ · · · · · · · · ♥ ♥ · · · · ♥ ♥ · ·
 ♥ ♥ · · · · · · · ♥ ♥ ♥ · · · · ♥ ♥ · ·
 · · · · · · · · · · ♥ ♥ · · · · ♥ ♥ · ·
 · · · · · · · · · · · ♥ ♥ · · · · · · ·
 · · · · · · · · · · · · ♥ · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
               Glider Gun               
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · ♥ ♥ · · · · · · ·
 · · · · · · · · · · ♥ · ♥ · · · · · · ♥
 ♥ ♥ · · · · · · · ♥ · · · · · · ♥ ♥ ♥ ·
 ♥ ♥ · · · · · · · ♥ · · ♥ · · ♥ · · ♥ ·
 · · · · · · · · · ♥ · · · · · · ♥ ♥ · ·
 · · · · · · · · · · ♥ · ♥ · · · · · · ·
 · · · · · · · · · · · ♥ ♥ · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·
 · · · · · · · · · · · · · · · · · · · ·

Command Line Interface

To make all the function avaible you need to set-up two things:

  • set up a “main.py” file
  • import argparse package to customise behaviour of this module;
import argparse
from rplife import __version__, patterns, views

def get_command_line_args():
    parser = argparse.ArgumentParser(
        prog="rplife",
        description="Conway's Game of Life in your terminal",
    )
    parser.add_argument(
        "--version", action="version", version=f"%(prog)s v{__version__}"
    )
    parser.add_argument(
        "-p",
        "--pattern", # add an attribute to this line
        choices=[pat.name for pat in patterns.get_all_patterns()],# this points python interface
        default="Blinker",
        help="take a pattern for the Game of Life (default: %(default)s)",
    )
    #... (more `add_argument` here)...
    return parser.parse_args()

“–pattern” will actuall became “pattern”. Once the parser.parse_args() object returned a result (call it arg), you can access this by doing “arg.pattern”.

However this don’t seem to work by itself

from rplife.cli import get_command_line_args
try:
    get_command_line_args()
except:
    print("`get_command_line_args()` don't seem to work outside package")
`get_command_line_args()` will not work without inside package
usage: rplife [-h] [--version] [-p {Blinker}] [-a] [-v {CursesView}]
              [-g NUM_GENERATIONS]
rplife: error: unrecognized arguments: --f=/Users/frankliang/Library/Jupyter/runtime/kernel-v3362f02a81df870f5337d5b5a338843c79cc3dd90.json