add 3 files from slime grid experiment
Some checks failed
Python Linting / build (pull_request) Has been cancelled
Some checks failed
Python Linting / build (pull_request) Has been cancelled
This commit is contained in:
parent
b020a9d18f
commit
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