add 3 files from slime grid experiment
Some checks failed
Python Linting / build (pull_request) Has been cancelled

This commit is contained in:
James Turk 2024-12-16 21:42:03 -06:00
parent b020a9d18f
commit 186c5fac9a
3 changed files with 195 additions and 0 deletions

View File

@ -0,0 +1,26 @@
from slime.grid import Grid
from slime.rules import SimpleMapping, Movement, Combine
grid = Grid(10, 10, wrap_y=True)
grid[0, 0, "state"] = 1
grid[1, 1, "state"] = 1
grid[2, 0, "state"] = 1
grid[3, 1, "state"] = 1
grid[4, 0, "state"] = 1
grid[5, 1, "state"] = 1
grid[6, 0, "state"] = 1
grid[7, 1, "state"] = 1
grid[8, 0, "state"] = 1
grid[9, 1, "state"] = 1
# grid.rules.append(SimpleMapping("state", {0: 1, 1: 0}))
# grid.rules.append(Movement("state", 0, 1))
grid.rules.append(
Combine(SimpleMapping("state", {0: 1, 1: 0}), Movement("state", 0, 1))
)
while True:
# grid.render_text("state")
grid.render_text(lambda cell: {0: "|", 1: "-"}[cell["state"]])
input()
grid.step()

118
slime-exp/src/slime/grid.py Normal file
View File

@ -0,0 +1,118 @@
from enum import Enum
from collections import defaultdict
from .rules import Rule
class Connected(Enum):
FOUR_WAYS = 4
EIGHT_WAYS = 8
class Grid:
def __init__(
self,
width,
height,
*,
connected=Connected.FOUR_WAYS,
wrap_x=False,
wrap_y=False,
default_value=dict,
):
self.width = width
self.height = height
self.connected = connected
self.wrap_x = wrap_x
self.wrap_y = wrap_y
self.grid = defaultdict(default_value)
self.rules = []
def neighbors(self, x, y):
x1 = x - 1
x2 = x + 1
y1 = y - 1
y2 = y + 1
if x1 < 0:
x1 = self.width - 1 if self.wrap_x else None
if x2 >= self.width:
x2 = 0 if self.wrap_x else None
if y1 < 0:
y1 = self.height - 1 if self.wrap_y else None
if y2 >= self.height:
y2 = 0 if self.wrap_y else None
if x1 is not None:
yield (x1, y)
if x2 is not None:
yield (x2, y)
if y1 is not None:
yield (x, y1)
if y2 is not None:
yield (x, y2)
if self.connected == Connected.EIGHT_WAYS:
if x1 is not None and y1 is not None:
yield (x1, y1)
if x1 is not None and y2 is not None:
yield (x1, y2)
if x2 is not None and y1 is not None:
yield (x2, y1)
if x2 is not None and y2 is not None:
yield (x2, y2)
def __getitem__(self, item):
x, y, prop = item
if self.wrap_x:
x %= self.width
if self.wrap_y:
y %= self.height
if x < 0 or x >= self.width or y < 0 or y >= self.height:
raise IndexError
return self.grid[item][prop]
def __setitem__(self, item, value):
x, y, prop = item
if self.wrap_x:
x %= self.width
if self.wrap_y:
y %= self.height
if x < 0 or x >= self.width or y < 0 or y >= self.height:
raise IndexError
self.grid[x, y][prop] = value
def __iter__(self):
return iter(self.grid.items())
def render_text(self, prop_or_func):
if callable(prop_or_func):
func = prop_or_func
else:
func = lambda x: x[prop_or_func]
for y in range(self.height):
for x in range(self.width):
if (x, y) in self.grid:
print(func(self.grid[(x, y)]), end="")
else:
print(" ", end="")
print()
def add_rule(self, rule):
self.rules.append(rule)
def step(self):
new_grid = defaultdict(dict)
for (x, y), cell in self.grid.items():
for rule in self.rules:
for update in rule.step(x, y, cell, self):
# TODO: two rules shouldn't be able to update the same cell
if update.x < 0 or update.x >= self.width:
if not self.wrap_x:
continue
else:
update.x %= self.width
if update.y < 0 or update.y >= self.height:
if not self.wrap_y:
continue
else:
update.y %= self.height
new_grid[update.x, update.y].update(update.data)
self.grid = new_grid

View File

@ -0,0 +1,51 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Generator
@dataclass
class CellUpdate:
x: int
y: int
data: dict
class Rule(ABC):
@abstractmethod
def step(
self, x: int, y: int, cell: dict, grid: "Grid"
) -> Generator[CellUpdate, None, None]:
pass
class SimpleMapping(Rule):
def __init__(self, prop, mapping):
self.mapping = mapping
self.prop = prop
def step(self, x, y, cell, grid):
yield CellUpdate(x, y, {self.prop: self.mapping.get(cell.get(self.prop))})
class Movement(Rule):
def __init__(self, prop, dx, dy):
self.prop = prop
self.dx = dx
self.dy = dy
def step(self, x, y, cell, grid):
yield CellUpdate(x + self.dx, y + self.dy, cell)
class Combine(Rule):
def __init__(self, *rules):
self.rules = rules
def step(self, x, y, cell, grid):
updates = list(self.rules[0].step(x, y, cell, grid))
for rule in self.rules[1:]:
next_updates = []
for upd in updates:
next_updates.extend(rule.step(upd.x, upd.y, upd.data, grid))
updates = next_updates
yield from updates