GNU/Linux xterm-256color zsh 138 views

import sys
import random
import time
import string

class AsciiCanvas:
    width: int
    height: int
    fill_char: str
    canvas: list[list[str]]
    def __init__(self, width, height, fill_char=' '):
        self.width = width
        self.height = height
        self.fill_char = fill_char
        self.canvas = [
            [fill_char for _ in range(width)] for _ in range(height)]

    def set_pixel(self, x, y, char):
        if 0 <= x < self.width and 0 <= y < self.height:
            self.canvas[int(y)][int(x)] = char

    def __str__(self):
        return '\n'.join([''.join(row) for row in self.canvas])

    def clear(self):
        sys.stdout.write("\033[2J")  # Clear the screen
        
    def move_cursor_to_origin(self):
        sys.stdout.write("\033[H")  # Move cursor to the top-left corner
        sys.stdout.flush()

    def update(self):
        self.move_cursor_to_origin()
        sys.stdout.write(str(self))
        sys.stdout.flush()

class RDModel:
    dA: float
    dB: float
    f: float
    k: float
    def __init__(self, dA=1.0, dB=0.5, f=0.055, k=0.062):
        self.dA = dA
        self.dB = dB
        self.f = f
        self.k = k

class ReactionDiffusion:
    width: int
    height: int
    model: RDModel
    grid: list[list[dict[str, float]]]
    dt: float

    def __init__(self, width, height, model, dt=1.0):
        self.width = width
        self.height = height
        self.model = model
        self.grid = [[{'a': 1, 'b': random.random() * 0.1} for _ in range(width)] for _ in range(height)]
        self.dt = dt

    def laplace(self, x, y, chem):
        val = -self.grid[y][x][chem]
        for j in range(-1, 2):
            for i in range(-1, 2):
                if 0 <= x + i < self.width and 0 <= y + j < self.height:
                    val += self.grid[y + j][x + i][chem] * (0.2 if i == 0 or j == 0 else 0.05)
        return val

    def step(self):
        new_grid = [[{'a': 0., 'b': 0.} for _ in range(self.width)] for _ in range(self.height)]
        for y in range(self.height):
            for x in range(self.width):
                a = self.grid[y][x]['a']
                b = self.grid[y][x]['b']
                ab2 = a * b * b
                new_a = a + self.dt * (self.model.dA * self.laplace(x, y, 'a') - ab2 + self.model.f * (1 - a))
                new_b = b + self.dt * (self.model.dB * self.laplace(x, y, 'b') + ab2 - (self.model.k + self.model.f) * b)
                new_grid[y][x]['a'] = new_a
                new_grid[y][x]['b'] = new_b
        self.grid = new_grid
        
    def render(self, canvas, ascii_chars):
        for y in range(self.height):
            for x in range(self.width):
                b = self.grid[y][x]['b']
                index = int(b * (len(ascii_chars) - 1)) % len(ascii_chars)
                canvas.set_pixel(x, y, ascii_chars[index])


def main():
    width, height = 80, 40
    canvas = AsciiCanvas(width, height)
    m = RDModel(dA=1.0, dB=0.5, f=0.055, k=0.062)
    rd = ReactionDiffusion(width, height, m, dt=0.05)

    ascii_chars = string.ascii_letters + string.digits + string.punctuation + ' '
    ascii_chars = sorted(set(ascii_chars))

    canvas.clear()

    while True:
        rd.step()
        rd.render(canvas, ascii_chars)
        canvas.update()
        time.sleep(1/144)

if __name__ == "__main__":
    main()

More recordings by samclane

Reaction Diffusion 0:27

by samclane