Posted in raspberrypi, led

header_lq.jpg(photo made by Sfecles Petru)

Did you ever create a game in Raspberry Pi? 

For me, it's the first one, and it's a zero-player game called Game Of Life, also known as Conway Game. 

The game starts with a pattern (initial input) being the only interaction that the user/creator will have with the game. Next, you sit back and relax observing how the initial pattern evolves over time.

Let's see how it evolves! Feel free to use the comment section for any question, curiosity or problem.

The hardware to play with

As the title suggests, you need a Raspberry Pi, you can use any variant you want, for this project I will use a Rasp Zero.

The Game Of Life works in an infinite 2D space but for simplicity of this project, I chose to use an 8x8 LED matrix. But you are not limited, feel free to use as many and any size you like. 

Raspberry Pi and MAX7219 LED Matrix connections

Because I'm using a matrix with a MAX7219 (SPI) the following table shows how to create the connections. If you are using WS2812 NeoPixels (DMA)/NeoSegments please use the following instructions to make the connections.

 MAX7219Raspberry Pi Zero
1VCC (5V)VCC 5V (PIN 2)
2GNDGND (PIN 4)
3DINGPIO10 with MOSI (PIN 19)
4CSGPIO8 with SPI CE0 (PIN 24)
5CLKGPIO11 with SPI CLK (PIN 23)

The code evolves

The game's universe is governed by 4 well-defined laws. The universe is formed by a grid of cells/dots which can be dead or alive. For this project, the dead cells represent turned off LEDs and the live cells are represented by bright LEDs. On each iteration, the laws are applied and the shape changes. The laws:

  1. Any live cell with fewer than two live neighbors dies as if caused by underpopulation.
  2. Any live cell with two or three live neighbors lives on to the next generation.
  3. Any live cell with more than three live neighbors dies, as if by overpopulation.
  4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.

We need to enable SPI, an easy tutorial can be found on luma.led_matrix documentation. If you don't want to open the link, just run sudo raspi-config and enable SPI in Interfacing Options. Install lume.led_matrix library in python by running the following commands and we are ready to go:

sudo usermod -a -G spi,gpio pi
sudo apt-get install build-essential python-dev python-pip libfreetype6-dev libjpeg-dev
sudo -H pip install --upgrade luma.led_matrix

In the code, we start with the initial universe, we draw it to our matrix and then are applied the laws of the universe. After this, a new universe is born and the process starts with displaying it.

When I created the device, I encountered some problems (still a mystery) with the initialization. The only way to make it work was to return it from a function and I don't have any idea why it works.

I created 3 main functions, one that is brightening the LEDs from the matrix (drawMatrix), one that applies the laws of the universe (gameOfLife) and another one that counts the neighbors of a cell from the matrix (countNeighbors).

Feel free to modify the UNIVERSE_WIDTH and UNIVERSE_HEIGHT constants for your project.

With a few modification, you can use other devices, feel free to adapt the code for your use case. A good start is to replace getDevice function, if you are not using MAX7219 with the respective code that can be easily obtained from the documentation of luma.

import time

from luma.led_matrix.device import max7219
from luma.core.interface.serial import spi, noop
from luma.core.render import canvas

UNIVERSE_WIDTH = 8
UNIVERSE_HEIGHT = 8

def isAlive(cell):
    return cell == 1

def drawMatrix(device, universe):
    with canvas(device) as draw:
        for y in range(UNIVERSE_WIDTH):
            for x in range(UNIVERSE_HEIGHT):
                fill = 'white' if isAlive(universe[x][y]) else 'black' 
                draw.point((x,y), fill=fill)

def countNeighbors(universe, cellX, cellY):
    count = 0
    neighborsRadius =[-1, 0, 1]
    for x in neighborsRadius:
        neighbordPositionX = cellX + x
        if neighbordPositionX >= UNIVERSE_WIDTH or neighbordPositionX < 0:
            continue

        for y in neighborsRadius:
            neighbordPositionY = cellY + y
            if neighbordPositionY >= UNIVERSE_HEIGHT or neighbordPositionY < 0:
                continue

            if cellX != neighbordPositionX or cellY != neighbordPositionY:
                count += universe[neighbordPositionX][neighbordPositionY]
    
    return count

def gameOfLife(universe):
    # copy the universe so that we can modify it without worry 
    newUniverse = [row[:] for row in universe] 
    for cellX in range(UNIVERSE_WIDTH):
        for cellY in range(UNIVERSE_HEIGHT):
            neighbors = countNeighbors(universe, cellX, cellY)

            if isAlive(newUniverse[cellX][cellY]):
                # Any live cell with fewer than two live neighbors dies, as if by under population.
                # Any live cell with more than three live neighbors dies, as if by overpopulation.
                if neighbors < 2 or neighbors > 3:
                    newUniverse[cellX][cellY] = 0
                # Any live cell with two or three live neighbors lives on to the next generation.
                elif neighbors == 2 or neighbors == 3:
                    newUniverse[cellX][cellY] = 1
            # Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
            elif neighbors == 3:
                newUniverse[cellX][cellY] = 1
    
    return newUniverse


def getDevice(n, block_orientation, rotate):
    # create matrix device
    serial = spi(port=0, device=0, gpio=noop())
    device = max7219(serial, cascaded=n or 1, block_orientation=block_orientation, rotate=rotate or 0)
    return device

if __name__ == '__main__':
    try:   

        device = getDevice(1, 0, 0)

        # 1/4 pulsar, feel free to replace/add different life forms
        universe = [
            [0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 1, 0, 0],
            [0, 0, 0, 0, 0, 1, 0, 0],
            [0, 0, 0, 0, 0, 1, 1, 0],
            [0, 0, 0, 0, 0, 0, 0, 0],
            [0, 1, 1, 1, 0, 0, 1, 1],
            [0, 0, 0, 1, 0, 1, 0, 1],
            [0, 0, 0, 0, 0, 1, 1, 0]
        ]

        while 1:
            drawMatrix(device, universe)
            time.sleep(1)
            universe = gameOfLife(universe)
 
        # close execution by pressing CTRL + C
    except KeyboardInterrupt:
        print("Intrerrupted by user")
        pass
    finally:
        print("Program stopped")

Run the code above will bright the LEDs for a quarter of a pulsar. I saved the above code in a led-matrix.py file, run it using the command below and press CTRL + C to cancel anytime.

python led-matrix.py

A quarter of a pulsar

Conclusion

Besides the obvious extension that can be made, adding more matrixes, another one is to make the 'universe round'. When the bounds are crossed, the game to continue from the opposite side of the border.

This game can be moved to different sources of light, disco ball, a room full of lights and many other to make custom patterns from an initial input, maybe by touching.

Thank you for reading and stay bright.

Comments

Be the first to post a comment

Post a comment