Plugin Development

LifeGrid’s plugin system lets you add new automaton modes without modifying the core codebase. Drop a .py file in the plugins/ directory and it will be discovered automatically at startup.


Creating a Plugin

1. Subclass AutomatonPlugin

Every plugin must implement three properties and one factory method:

from plugin_system import AutomatonPlugin
from automata.base import CellularAutomaton
from core.boundary import BoundaryMode, convolve_with_boundary
import numpy as np


class MyAutomaton(CellularAutomaton):
    """Your automaton implementation."""

    def __init__(self, width: int, height: int) -> None:
        self.width = width
        self.height = height
        self.grid = np.zeros((height, width), dtype=int)
        super().__init__(width, height)

    def step(self) -> None:
        kernel = np.array([[1, 1, 1],
                           [1, 0, 1],
                           [1, 1, 1]])
        # Use convolve_with_boundary so the active boundary mode is respected
        mode = BoundaryMode.from_string(self.boundary)
        neighbors = convolve_with_boundary((self.grid > 0).astype(int), kernel, mode)
        # Apply your rules here
        new_grid = np.zeros_like(self.grid)
        birth = (self.grid == 0) & np.isin(neighbors, [3, 6, 7, 8])
        survive = (self.grid > 0) & np.isin(neighbors, [3, 4, 6, 7, 8])
        new_grid[birth] = 1
        new_grid[survive] = self.grid[survive]
        self.grid = new_grid

    def get_grid(self) -> np.ndarray:
        return self.grid

    def set_cell(self, x: int, y: int, value: int = 1) -> None:
        if 0 <= x < self.width and 0 <= y < self.height:
            self.grid[y, x] = value

    def reset(self) -> None:
        self.grid = np.zeros((self.height, self.width), dtype=int)


class MyPlugin(AutomatonPlugin):
    @property
    def name(self) -> str:
        return "My Custom Rule"

    @property
    def description(self) -> str:
        return "B368/S3468 — an example custom automaton"

    @property
    def version(self) -> str:
        return "1.0"

    def create_automaton(self, width: int, height: int) -> CellularAutomaton:
        return MyAutomaton(width, height)

2. Save the file

Save your plugin as plugins/my_custom_rule.py. The filename doesn’t matter — LifeGrid scans all .py files in the directory.

3. Restart LifeGrid

The new mode appears in the mode selector automatically.


Plugin API

AutomatonPlugin (abstract base class)

Member

Type

Description

name

property → str

Display name shown in the mode selector

description

property → str

Short description of the rule

version

property → str

Plugin version string

create_automaton(width, height)

method → CellularAutomaton

Factory that returns a configured automaton instance

CellularAutomaton (abstract base class)

Your automaton must implement:

Member

Signature

Description

step

() -> None

Advance one generation

get_grid

() -> np.ndarray

Return the current grid as a 2D numpy array

set_cell

(x, y, value) -> None

Set a single cell’s state

reset

() -> None

Clear the grid to all zeros

The base class also provides:

Member

Type

Description

boundary

str

Active boundary mode: "wrap" (default), "fixed", or "reflect"

the grid should be a 2D integer array of shape (height, width) where 0 means dead and positive integers represent live/coloured states.


Example: Day & Night Plugin

The included plugins/day_and_night.py implements B3678/S34678:

from plugin_system import AutomatonPlugin
from automata.lifelike import LifeLikeAutomaton
from automata.base import CellularAutomaton


class DayAndNightPlugin(AutomatonPlugin):
    @property
    def name(self) -> str:
        return "Day & Night"

    @property
    def description(self) -> str:
        return "B3678/S34678 — symmetric behavior for ON and OFF cells"

    @property
    def version(self) -> str:
        return "1.0"

    def create_automaton(self, width: int, height: int) -> CellularAutomaton:
        return LifeLikeAutomaton(width, height, birth={3, 6, 7, 8}, survival={3, 4, 6, 7, 8})

This plugin reuses LifeLikeAutomaton for the step logic, so only the plugin metadata and B/S parameters need to be defined.


Tips

  • Reuse LifeLikeAutomaton for any totalistic Life-like rule — just supply birth and survival sets. This automatically benefits from the cached kernel and boundary routing.

  • Always call super().__init__(width, height) after setting self.grid so the base class initialises self.boundary and the population helpers correctly.

  • Respect self.boundary: use BoundaryMode.from_string(self.boundary) and convolve_with_boundary() so users can switch between wrap/fixed/reflect from the GUI without restarting.

  • Multi-state automata can use integers > 1 in the grid. The GUI renders states 0–3 with distinct colours.

  • Test your plugin by instantiating it directly:

    plugin = MyPlugin()
    auto = plugin.create_automaton(50, 50)
    auto.set_cell(25, 25, 1)
    auto.step()
    print(auto.get_grid().sum())
    
  • Plugins are loaded via PluginManager.load_plugins_from_directory(). If a plugin fails to load (import error, missing methods), it is skipped with a warning — it won’t crash the application.