Conway’s Game of Life (pygame version)

Example script (scamp_extensions): examples/Composition & form/Algorithmic approaches/conway.py

Download: conway.py

Requires the scamp_extensions package (pip install scamp_extensions).

Conway’s Game of Life sonified, with a pygame window used for visualization instead of matplotlib. Original by Raphael Radna; visualization ported to pygame.

Topics: Interactivity & visualization › Visualization, Composition & form › Algorithmic approaches

"""
SCAMP Example: Conway's Game of Life (pygame version)

Conway's Game of Life sonified, with a pygame window used for visualization
instead of matplotlib. Original by Raphael Radna; visualization ported to
pygame.
"""

import numpy
import pygame
from scamp import *
from scamp_extensions.pitch import Scale
import math


scale = Scale.melodic_minor(59)

WIDTH = 24
HEIGHT = 24
CELL_SIZE = 24  # pixels per cell in the pygame window
FPS = 20

ALIVE_COLOR = (255, 255, 255)
DEAD_COLOR = (0, 0, 0)
BG_COLOR = (0, 0, 0)

s = Session()
scamp1 = s.new_part("piano")


def bark_to_hz(bark):  # Traunmüller formula
    return 1960 / (26.81 / (bark + 0.53) - 1)


def ftom(hz, base=440):
    return 12 * math.log(hz / base) / math.log(2) + 69


def init_grid(x, y):
    return numpy.random.choice([0, 1], (x, y))


def wrap(val, lo, hi):
    if val < lo:
        val += hi
    if val >= hi:
        val -= hi
    return val


def get_cell(a, x, y, dx, dy):
    return a[wrap((x + dx), 0, WIDTH), wrap((y + dy), 0, HEIGHT)]


def sum_neighbors(a, x, y):
    running_sum = 0
    for i in range(3):
        for j in range(3):
            if i == 1 and j == 1:
                continue
            running_sum += get_cell(a, x, y, i - 1, j - 1)
    return running_sum


def apply_rules(a, b, x, y):
    state = a[x, y]
    neighbor_count = sum_neighbors(a, x, y)
    if state == 1 and (neighbor_count < 2 or neighbor_count > 3):
        b[x, y] = 0
    if state == 0 and neighbor_count == 3:
        b[x, y] = 1


current_grid = init_grid(WIDTH, HEIGHT)
next_grid = numpy.array(current_grid)
note_grid = numpy.zeros((WIDTH, HEIGHT), dtype=object)


def grid_play(a, x, y):
    pan = y / HEIGHT
    pitch = ftom(bark_to_hz((x / WIDTH) * 20 + pan))
    cell_state = a[x, y]
    note_state = note_grid[x, y]

    if cell_state == 1 and note_state == 0:
        note_grid[x, y] = scamp1.start_note(scale.round(pitch), 0.125, "param_10:{}".format(pan))

    if cell_state == 0 and note_state != 0:
        note_grid[x, y].end()
        note_grid[x, y] = 0


def update_grid():
    global current_grid
    global next_grid
    for x in range(WIDTH):
        for y in range(HEIGHT):
            grid_play(current_grid, x, y)
            apply_rules(current_grid, next_grid, x, y)
    current_grid[:, :] = next_grid[:, :]


def randomize_grid():
    global current_grid
    global next_grid
    # stop any currently sounding notes before scrambling the grid
    for x in range(WIDTH):
        for y in range(HEIGHT):
            if note_grid[x, y] != 0:
                note_grid[x, y].end()
                note_grid[x, y] = 0
    current_grid = init_grid(WIDTH, HEIGHT)
    next_grid = numpy.array(current_grid)


def draw_grid(screen):
    screen.fill(BG_COLOR)
    for x in range(WIDTH):
        for y in range(HEIGHT):
            color = ALIVE_COLOR if current_grid[x, y] == 1 else DEAD_COLOR
            rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
            pygame.draw.rect(screen, color, rect)


def main():
    pygame.init()
    screen = pygame.display.set_mode((WIDTH * CELL_SIZE, HEIGHT * CELL_SIZE))
    pygame.display.set_caption("Conway's Game of Life (SCAMP)")
    clock = pygame.time.Clock()

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                running = False
            elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                randomize_grid()

        update_grid()
        draw_grid(screen)
        pygame.display.flip()
        clock.tick(FPS)

    # Clean up any still-sounding notes on exit
    for x in range(WIDTH):
        for y in range(HEIGHT):
            if note_grid[x, y] != 0:
                note_grid[x, y].end()

    pygame.quit()


if __name__ == "__main__":
    main()