Created
September 1, 2025 07:19
-
-
Save hdurdle/753c258654f8fb91375f68c3bcd8518c to your computer and use it in GitHub Desktop.
Conway's Game of Life in MicroPython on Pico 2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from machine import Pin, SPI | |
| from time import sleep | |
| import max7219 | |
| import random | |
| import gc | |
| # --- hardware --- | |
| spi = SPI(0, sck=Pin(2), mosi=Pin(3)) | |
| cs = Pin(5, Pin.OUT) | |
| display = max7219.Matrix8x8(spi, cs, 12) | |
| display.brightness(0) | |
| # --- grid --- | |
| WIDTH, HEIGHT = 96, 8 | |
| SIZE = WIDTH * HEIGHT | |
| # --- options ### | |
| FRAME_RATE = 24 | |
| REPEAT_LIMIT = FRAME_RATE | |
| TICK_TIME = 1/FRAME_RATE | |
| RANDOM_MODE = 0 | |
| # reusable grid buffers | |
| grid = bytearray(SIZE) | |
| next_grid = bytearray(SIZE) | |
| def idx(x, y): | |
| return y * WIDTH + x | |
| def draw_grid(buf): | |
| for y in range(HEIGHT): | |
| base = y * WIDTH | |
| for x in range(WIDTH): | |
| display.pixel(x, y, 1 if buf[base + x] else 0) | |
| display.show() | |
| def randomise_in_place(buf): | |
| for i in range(SIZE): | |
| # MicroPython's random has getrandbits; alternativelly use randint(0,1) | |
| buf[i] = random.getrandbits(1) | |
| def update_in_place(src, dst): | |
| # Compute next generation into 'dst' using toroidal wrap | |
| for y in range(HEIGHT): | |
| y_up = (y - 1) % HEIGHT | |
| y_dn = (y + 1) % HEIGHT | |
| yb = y * WIDTH | |
| yub = y_up * WIDTH | |
| ydb = y_dn * WIDTH | |
| for x in range(WIDTH): | |
| xm = (x - 1) % WIDTH | |
| xp = (x + 1) % WIDTH | |
| # Sum 8 neighbors | |
| n = ( | |
| src[yub + xm] + src[yub + x] + src[yub + xp] + | |
| src[yb + xm] + src[yb + xp] + | |
| src[ydb + xm] + src[ydb + x] + src[ydb + xp] | |
| ) | |
| c = src[yb + x] | |
| dst[yb + x] = 1 if (c and (n == 2 or n == 3)) or (not c and n == 3) else 0 | |
| # --- lightweight cycle detection (fixed-size, hash-based) --- | |
| class LifeCycleDetector: | |
| def __init__(self, max_history=16): | |
| self.max_history = max_history | |
| self.seen = {} # hash -> generation index | |
| self.order = [] # FIFO of hashes | |
| self.generation = 0 | |
| @staticmethod | |
| def _hash32(buf): | |
| # FNV-1a 32-bit | |
| h = 2166136261 | |
| for b in buf: | |
| h ^= b | |
| h = (h * 16777619) & 0xFFFFFFFF | |
| return h | |
| def update(self, buf): | |
| h = self._hash32(buf) | |
| gen = self.generation | |
| self.generation += 1 | |
| if h in self.seen: | |
| period = gen - self.seen[h] | |
| if period > 0: | |
| return True, period | |
| self.seen[h] = gen | |
| self.order.append(h) | |
| if len(self.order) > self.max_history: | |
| old = self.order.pop(0) | |
| # Safe to remove; keeps dict bounded | |
| if old in self.seen and self.seen[old] <= gen - self.max_history: | |
| del self.seen[old] | |
| return False, None | |
| det = LifeCycleDetector(max_history=16) | |
| # --- main --- | |
| randomise_in_place(grid) | |
| while True: | |
| sleep(TICK_TIME) | |
| draw_grid(grid) | |
| if RANDOM_MODE == 1: | |
| randomise_in_place(next_grid) | |
| else: | |
| update_in_place(grid, next_grid) | |
| steady, period = det.update(next_grid) | |
| if steady and period and period > REPEAT_LIMIT: | |
| randomise_in_place(next_grid) | |
| # Swap buffers; no allocations | |
| grid, next_grid = next_grid, grid | |
| # Keep fragmentation under control | |
| if (det.generation & 31) == 0: | |
| gc.collect() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment