Conway’s Game of Life for micro:bit using MicroPython

by | Jan 15, 2018 | 0 comments

Conway’s Game of Life has been challenging programmers for many years. I treat it as a more advanced form of “Hello World” as it requires a reasonable knowledge of a programming language to complete.

This version is for the BBC micro:bit and uses MicroPython. The main restriction here is the limit of a 5 x 5 grid of LEDs as the display but this can be scaled up, perhaps to work on an external LED matrix.

Conway’s Game of Life

# Conway's Game of Life
# Richard Zealley
# 9th May 2016

from microbit import *
from random import randint

# Number of lives to start out with (12 seems to work pretty well)
numbugs = 12

# Whether to treat the sides as boundaries or wrap round to the other side
wrap = True

# We're stuck with a 5x5 array but may be useful on another platform
cols = 5
rows = 5

# Define 5x5 array full of 0s
bugs = [[0 for x in range(cols)] for y in range(rows)]


def clear_bugs():
    # Clears the array though this is not actually used as the definition above does this anyway but could be useful at some point'
    global bugs

    # Set all array elements to 0
    for x in range(5):
        for y in range(5):
            bugs[x][y] = 0
    return


def random_bugs(n):
    # Generate a set number of cells as populated (with 1)' 
    global bugs

    i = 0
    while i < n:
        x = randint(0, 4)
        y = randint(0, 4)
        # Check not already populated
        if bugs[x][y] != 1:
            # Populate it
            bugs[x][y] = 1
            # Count it
            i += 1
    return


def show_bugs():
    # Clear the display and then set any led which is populated'
    global bugs

    display.clear()
    for x in range(5):
        for y in range(5):
            if bugs[x][y] == 1:
                display.set_pixel(x, y, 9)
    return


def check_position(pos):
    # For wrapping - check if a position is in the 5x5 grid, 
    # and shift to opposite side if necessary
    if pos < 0:
        pos = 5 + pos
    if pos>4:
        pos = pos - 5
    return pos


def process_bug(x, y):
    # Check a position by counting each of its 8 surrounding neighbours and return the number populated'
    global bugs

    n = 0
    # Count each of 8 neighbours
    for i in (-1, 0, 1): # col
        for j in (-1, 0, 1): # row
            if not (i == 0 and j == 0): #don't count the current bug
                if wrap:
                    col = check_position(x+i)
                    row = check_position(y+j)
                    if bugs[col][row] == 1:
                        n += 1
                else:
                    if i in (0, 1, 2, 3, 4) and j in (0, 1, 2, 3, 4):
                        if bugs[i][j] == 1:
                            n += 1
    return n


def generate_bugs():
    # Check each position in grid, count neighbours and die or birth if needed
    global bugs

    # Track changes - if none then no point continuing
    changes = 0

    # Clone current bug positions - need to work from original before changing
    new_bugs = [list(row) for row in bugs]

    # Check each bug for neighbours
    for x in range(5):
        for y in range(5):
            n = process_bug(x, y)
            # Live cell
            if bugs[x][y] == 1:
                if n < 2 or n > 3: # dies (under or over population)
                    new_bugs[x][y] = 0
                    changes += 1
            elif n == 3: # dead cell with 3 neighbours
                    # New birth
                    new_bugs[x][y] = 1
                    changes += 1

    # Copy new population back to bugs
    bugs = [list(row) for row in new_bugs]

    return changes


def main():
    # Count the number of generations
    goes = 0

    # Create the game and show the initials LEDs
    random_bugs(numbugs)
    show_bugs()
    sleep(1000)

    # Continue until button B is pressed
    while not button_b.is_pressed():
        goes += 1
        # Process bugs and stop if no longer any changes
        changes = generate_bugs()
        show_bugs()

        # Slow things down
        sleep(1000)

        # If nothing changed, that's it
        if changes == 0:
            break

    display.scroll(str(goes)+" generations")
    display.scroll("The End")


# Run it 8-)
if __name__ == "__main__":
    main()