(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.
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.
|1||VCC (5V)||VCC 5V (PIN 2)|
|2||GND||GND (PIN 4)|
|3||DIN||GPIO10 with MOSI (PIN 19)|
|4||CS||GPIO8 with SPI CE0 (PIN 24)|
|5||CLK||GPIO11 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:
- Any live cell with fewer than two live neighbors dies as if caused by underpopulation.
- Any live cell with two or three live neighbors lives on to the next generation.
- Any live cell with more than three live neighbors dies, as if by overpopulation.
- 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 (
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.
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.