Compare commits
1 Commits
main
...
slime-grid
Author | SHA1 | Date | |
---|---|---|---|
186c5fac9a |
26
slime-exp/examples/rain.py
Normal file
26
slime-exp/examples/rain.py
Normal 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
118
slime-exp/src/slime/grid.py
Normal 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
|
51
slime-exp/src/slime/rules.py
Normal file
51
slime-exp/src/slime/rules.py
Normal 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
|
Loading…
Reference in New Issue
Block a user