Created
September 2, 2025 06:57
-
-
Save hdurdle/cfbf13d54cfbe8a9c14aba4e5fca2080 to your computer and use it in GitHub Desktop.
WOPR sim / Game of Life
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) | |
| button = Pin(0, Pin.IN, Pin.PULL_UP) | |
| prev_button_state = 1 | |
| 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) | |
| button_state = button.value() | |
| if prev_button_state == 0 and button_state == 1: | |
| print("Toggle Animation Type") | |
| RANDOM_MODE ^= 1 | |
| prev_button_state = button_state | |
| draw_grid(grid) | |
| if RANDOM_MODE == 1: | |
| randomise_in_place(next_grid) | |
| timeflip = random.randint(0,3) | |
| if (timeflip == 0): | |
| sleep(1-TICK_TIME) | |
| elif (timeflip == 1): | |
| sleep(1.5-TICK_TIME) | |
| elif (timeflip == 2): | |
| sleep(2-TICK_TIME) | |
| else: | |
| sleep(0.5-TICK_TIME) | |
| 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