TutorialΒΆ
The tutorial is coming soon. Meanwhile, you may check the official Game Of Life example.
"""
A collection of models derived from Conway's Game Of Life.
Experiment classes included.
"""
import importlib
from xentica import core
from xentica import seeds
from xentica.core import color_effects
from xentica.tools.rules import LifeLike
class GameOfLife(core.CellularAutomaton):
"""
The classic CA built with Xentica framework.
It has only one property called ``state``, which is positive
integer with max value of 1.
"""
state = core.IntegerProperty(max_val=1)
class Topology:
"""
Mandatory class for all ``CellularAutomaton`` instances.
All class variables below are also mandatory.
Here, we declare the topology as a 2-dimensional orthogonal
lattice with Moore neighborhood, wrapped to a 3-torus.
"""
dimensions = 2
lattice = core.OrthogonalLattice()
neighborhood = core.MooreNeighborhood()
border = core.TorusBorder()
def emit(self):
"""
Implement the logic of emit phase.
Statements below will be translated into C code as emit kernel
at the moment of class creation.
Here, we just copy main state to surrounding buffers.
"""
for buf in self.buffers:
buf.state = self.main.state
def absorb(self):
"""
Implement the logic of absorb phase.
Statements below will be translated into C code as well.
Here, we sum all neigbors buffered states and apply Conway
rule to modify cell's own state.
"""
neighbors_alive = core.IntegerVariable()
for i in range(len(self.buffers)):
neighbors_alive += self.neighbors[i].buffer.state
is_born = (8 >> neighbors_alive) & 1
is_sustain = (12 >> neighbors_alive) & 1
self.main.state = is_born | is_sustain & self.main.state
@color_effects.MovingAverage
def color(self):
"""
Implement the logic of cell's color calculation.
Must return a tuple of RGB values computed from ``self.main``
properties.
Also, must be decorated by a class from ``color_effects``
module.
Here, we simply define 0 state as pure black, and 1 state as
pure white.
"""
red = self.main.state * 255
green = self.main.state * 255
blue = self.main.state * 255
return (red, green, blue)
class GameOfLifeStatic(GameOfLife):
"""
Game of Life variant with static border made of live cells.
This is an example of how easy you can inherit other models.
"""
class Topology(GameOfLife.Topology):
"""
You can inherit parent class ``Topology``.
Then, override only necessary variables.
"""
border = core.StaticBorder(1)
class GameOfLifeColor(GameOfLife):
"""
Game Of Life variant with RGB color.
This is an example of how to use multiple properties per cell.
"""
state = core.IntegerProperty(max_val=1)
red = core.IntegerProperty(max_val=255)
green = core.IntegerProperty(max_val=255)
blue = core.IntegerProperty(max_val=255)
def emit(self):
"""Copy all properties to surrounding buffers."""
for buf in self.buffers:
buf.state = self.main.state
buf.red = self.main.red
buf.green = self.main.green
buf.blue = self.main.blue
def absorb(self):
"""
Calculate RGB as neighbors sum for living cell only.
Note, parent ``absorb`` method should be called using direct
class access, not via ``super``.
"""
GameOfLife.absorb(self)
red_sum = core.IntegerVariable()
green_sum = core.IntegerVariable()
blue_sum = core.IntegerVariable()
for i in range(len(self.buffers)):
red_sum += self.neighbors[i].buffer.red + 1
green_sum += self.neighbors[i].buffer.green + 1
blue_sum += self.neighbors[i].buffer.blue + 1
self.main.red = red_sum * self.main.state
self.main.green = green_sum * self.main.state
self.main.blue = blue_sum * self.main.state
@color_effects.MovingAverage
def color(self):
"""Calculate color as usual."""
red = self.main.state * self.main.red
green = self.main.state * self.main.green
blue = self.main.state * self.main.blue
return (red, green, blue)
class GameOfLife6D(GameOfLife):
"""
Game of Life variant in 6D.
Nothing interesting, just to prove you can do it with ease.
"""
class Topology(GameOfLife.Topology):
"""
Hyper-spacewalk, is as easy as increase ``dimensions`` value.
However, we are also changing neighborhood to Von Neumann
here, to prevent neighbors number exponential grow.
"""
dimensions = 6
neighborhood = core.VonNeumannNeighborhood()
class LifelikeCA(GameOfLife):
"""Lifelike CA with a flexible rule that could be changed at runtime."""
rule = core.Parameter(
default=LifeLike.golly2int("B3/S23"),
interactive=True,
)
def absorb(self):
"""Implement parent's clone with a rule as a parameter."""
neighbors_alive = core.IntegerVariable()
for i in range(len(self.buffers)):
neighbors_alive += self.neighbors[i].buffer.state
is_born = (self.rule >> neighbors_alive) & 1
is_sustain = (self.rule >> 9 >> neighbors_alive) & 1
self.main.state = is_born | is_sustain & self.main.state
def step(self):
"""Change the rule interactively after some time passed."""
if self.timestep == 23:
self.rule = LifeLike.golly2int("B3/S23")
super().step()
class GOLExperiment(core.Experiment):
"""
Particular experiment for the vanilla Game of Life.
Here, we define constants and initial conditions from which the
world's seed will be generated.
The ``word`` is an RNG seed string. The ``size``, ``zoom`` and
``pos`` are board contstants. The ``seed`` is a pattern used in
the initial board state generation.
``BigBang`` is a pattern when small area initialized with a
high-density random values.
"""
word = "OBEY XENTICA"
size = (640, 360, )
zoom = 3
pos = [0, 0]
seed = seeds.patterns.BigBang(
pos=(320, 180),
size=(100, 100),
vals={
"state": seeds.random.RandInt(0, 1),
}
)
class GOLExperiment2(GOLExperiment):
"""
Another experiment for the vanilla GoL.
Since it is inherited from ``GOLExperiment``, we can redefine only
values we need.
``PrimordialSoup`` is a pattern when the whole board is
initialized with low-density random values.
"""
word = "XENTICA IS YOUR GODDESS"
seed = seeds.patterns.PrimordialSoup(
vals={
"state": seeds.random.RandInt(0, 1),
}
)
class GOLExperimentColor(GOLExperiment):
"""
The experiment for ``GameOfLifeColor``.
Here, we introduce ``fade_out`` constant, which is used in
rendering and slowly fading out the color of cells.
Note, it is only an aestetic effect, and does not affect the real
cell state.
"""
fade_in = 255
fade_out = 10
smooth_factor = 1
seed = seeds.patterns.PrimordialSoup(
vals={
"state": seeds.random.RandInt(0, 1),
"red": seeds.random.RandInt(0, 255),
"green": seeds.random.RandInt(0, 255),
"blue": seeds.random.RandInt(0, 255),
}
)
class GOLExperiment6D(GOLExperiment2):
"""
Special experiment for 6D Life.
Here, we define the world with 2 spatial and 4 looped
micro-dimensions, 3 cells per micro-dimension.
As a result, we get large quasi-stable oscillators, looping over
micro-dimensions. Strangely formed, but nothing interesting,
really.
"""
size = (640, 360, 3, 3, 3, 3)
class DiamoebaExperiment(GOLExperiment):
"""Experiment with the interactive rule."""
rule = LifeLike.golly2int("B35678/S5678")
def main():
"""Run model/experiment interactively."""
moire = importlib.import_module("moire")
model = GameOfLifeColor(GOLExperimentColor)
gui = moire.GUI(runnable=model)
gui.run()
if __name__ == "__main__":
main()