pimoroni-pico/micropython/examples/badger2040/conway.py

216 lines
6.1 KiB
Python

import math
import time
from random import random
import machine
import badger2040
# Overclock the RP2040 to run the sim faster
badger2040.system_speed(badger2040.SYSTEM_TURBO)
# ------------------------------
# Program setup
# ------------------------------
# Global constants
CELL_SIZE = 6 # Size of cell in pixels
INITIAL_DENSITY = 0.3 # Density of cells at start
# Create a new Badger and set it to update TURBO
screen = badger2040.Badger2040()
screen.led(128)
screen.update_speed(badger2040.UPDATE_TURBO)
restart = False # should sim be restarted
# ------------------------------
# Button functions
# ------------------------------
# Button handling function
def button(pin):
global restart
# if 'a' button is pressed, restart the sim
if pin == button_a:
restart = True
# Set up button
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
# ------------------------------
# Screen functions
# ------------------------------
# Remove everything from the screen
def init_screen():
screen.update_speed(badger2040.UPDATE_NORMAL)
screen.pen(15)
screen.clear()
screen.update()
screen.update_speed(badger2040.UPDATE_TURBO)
# ------------------------------
# Classes
# ------------------------------
# Define a 'cell'
class Cell:
def __init__(self):
self._alive = False
def make_alive(self):
self._alive = True
def make_dead(self):
self._alive = False
def is_alive(self):
return self._alive
# Define the whole board
class Board:
def __init__(self):
self._rows = math.floor(badger2040.WIDTH / CELL_SIZE)
self._columns = math.floor(badger2040.HEIGHT / CELL_SIZE)
self._grid = [[Cell() for _ in range(self._columns)] for _ in range(self._rows)]
self._initialise_board()
# Draw the board to the screen
def draw_board(self):
row_idx = 0
column_idx = 0
for row in self._grid:
column_idx = 0
for cell in row:
if cell.is_alive():
screen.pen(0)
else:
screen.pen(15)
screen.rectangle(
row_idx * CELL_SIZE, column_idx * CELL_SIZE, CELL_SIZE, CELL_SIZE
)
column_idx += 1
row_idx += 1
screen.update()
# Generate the first iteration of the board
def _initialise_board(self):
for row in self._grid:
for cell in row:
if random() <= INITIAL_DENSITY:
cell.make_alive()
# Get the neighbour cells for a given cell
def get_neighbours(self, current_row, current_column):
# Cells either side of current cell
neighbour_min = -1
neighbour_max = 2
neighbours = []
for row in range(neighbour_min, neighbour_max):
for column in range(neighbour_min, neighbour_max):
neighbour_row = current_row + row
neighbour_column = current_column + column
# Don't count the current cell
if not (
neighbour_row == current_row and neighbour_column == current_column
):
# It's a toroidal world so go all the way round if necessary
if (neighbour_row) < 0:
neighbour_row = self._rows - 1
elif (neighbour_row) >= self._rows:
neighbour_row = 0
if (neighbour_column) < 0:
neighbour_column = self._columns - 1
elif (neighbour_column) >= self._columns:
neighbour_column = 0
neighbours.append(self._grid[neighbour_row][neighbour_column])
return neighbours
# Calculate the next generation
def create_next_generation(self):
to_alive = []
to_dead = []
changed = False
for row in range(len(self._grid)):
for column in range(len(self._grid[row])):
# Get all the neighours that are alive
alive_neighbours = []
for neighbour_cell in self.get_neighbours(row, column):
if neighbour_cell.is_alive():
alive_neighbours.append(neighbour_cell)
current_cell = self._grid[row][column]
# Apply the Conway GoL rules (B3/S23)
if current_cell.is_alive():
if len(alive_neighbours) < 2 or len(alive_neighbours) > 3:
to_dead.append(current_cell)
if len(alive_neighbours) == 3 or len(alive_neighbours) == 2:
to_alive.append(current_cell)
else:
if len(alive_neighbours) == 3:
to_alive.append(current_cell)
for cell in to_alive:
if not cell.is_alive():
# The board has changed since the previous generation
changed = True
cell.make_alive()
for cell in to_dead:
if cell.is_alive():
# The board has changed since the previous generation
changed = True
cell.make_dead()
return changed
# ------------------------------
# Main program loop
# ------------------------------
def main():
global restart
init_screen()
board = Board()
board.draw_board()
time.sleep(0.5)
while True:
# The 'a' button has been pressed so restart sim
if restart:
init_screen()
restart = False
board = Board()
board.draw_board()
time.sleep(0.5)
# The board didn't update since the previous generation
if not board.create_next_generation():
screen.update_speed(badger2040.UPDATE_NORMAL)
board.draw_board()
screen.update_speed(badger2040.UPDATE_TURBO)
time.sleep(5)
restart = True
# Draw the next generation
else:
board.draw_board()
main()