HowTo Project: LifeGame¶
Objective¶
This howto will help you to discover synergine with a simple implementation. We will only show you the code of main logic. But all not displayed code here is described in main documentation.
The howto project implement the “Conway’s Game of Life”.
From Wikipedia, the free encyclopedia:
The Game of Life, also known simply as Life, is a cellular automaton devised by
the British mathematician John Horton Conway in 1970.
The "game" is a zero-player game, meaning that its evolution is determined by
its initial state, requiring no further input. One interacts with the Game of
Life by creating an initial configuration and observing how it evolves or, for
advanced players, by creating patterns with particular properties.
First steps¶
Note
The LifeGame howto
source code is available at https://github.com/buxx/synergine_lifegame.git.
You will need some dependencies:
>>> pip install synergine
>>> pip install synergine_xyz
Content of simulation¶
Our simulation will implement a basic pattern of “Conway’s Game of Life” and visualisation tools.
Simulation¶
The objective is to implement the rules of “Conway’s Game of Life”. From Wikipedia, the free encyclopedia:
The universe of the Game of Life is an infinite two-dimensional orthogonal
grid of square cells, each of which is in one of two possible states, alive
or dead. Every cell interacts with its eight neighbours, which are the
cells that are horizontally, vertically, or diagonally adjacent. At each
step in time, the following transitions occur:
1. Any live cell with fewer than two live neighbours dies, as if caused by
under-population.
2. Any live cell with two or three live neighbours lives on to the next generation.
3. Any live cell with more than three live neighbours dies, as if by overcrowding.
4. Any dead cell with exactly three live neighbours becomes a live cell, as if by
reproduction.
The initial pattern constitutes the seed of the system. The first generation
is created by applying the above rules simultaneously to every cell in the
seed—births and deaths occur simultaneously, and the discrete moment at which
this happens is sometimes called a tick (in other words, each generation is a
pure function of the preceding one). The rules continue to be applied repeatedly
to create further generations.
Additional features¶
In additional of “Conway’s Game of Life” rules, we will represent differently in our 2d graphic output:
- A just born cell
- A born cell since 2 cycles
- A born cell since more 2 cycles
Prerequisite¶
To correctly follow this tutorial, remember to work with Python 3.4 interpreter and create in your project directory a
synergine_lifegame
(sources files will be placed in) directory. Different command are executed from your
project directory.
Constants¶
First, we have to prepare some constants to feed our metas data. There it is:
synergine_lifegame/cst.py:
from synergine.lib.eint import IncrementedNamedInt
ALIVE = IncrementedNamedInt.get('synergine_lifegame.alive')
DIED = IncrementedNamedInt.get('synergine_lifegame.died')
COL_DIED = IncrementedNamedInt.get('synergine_lifegame.col.died')
COL_ALIVE = IncrementedNamedInt.get('synergine_lifegame.col.alive')
SynergyObjects¶
We need: A Cell.
synergine_lifegame/synergy/object/Cell.py:
from synergine.cst import COL_ALL
from synergine_lifegame.cst import DIED, ALIVE, COL_DIED, COL_ALIVE
from synergine_xyz.SynergyObject import SynergyObject as XyzSynergyObject
class Cell(XyzSynergyObject):
"""
Representation of cell.
"""
def __init__(self, collection, context):
"""
:param collection: Collection who contain this Cell
:param context: The context object
"""
super().__init__(collection, context)
self._alive = False
self._alive_since = -1
# By default, a cell is dead
self._add_col(COL_DIED)
self._add_state(DIED)
def set_alive(self, alive):
"""
Change the state of Cell.
:param alive: Alive boolean
:return:
"""
# When alive state of Cell change, self._alive is reinitialized
self._alive_since = -1
self._alive = alive
# We update states to.
if alive:
# State of cell is now ALIVE
self._add_state(ALIVE)
self._remove_state(DIED)
# Cell is now in COL_ALIVE collection
self._add_col(COL_ALIVE)
self._remove_col(COL_DIED)
else:
# State of cell is now DIED
self._add_state(DIED)
self._remove_state(ALIVE)
# Cell is now in COL_DIED collection
self._add_col(COL_DIED)
self._remove_col(COL_ALIVE)
def is_alive(self):
return self._alive
def get_is_alive_since(self):
"""
:return: Count of alive cycles.
:rtype: int
"""
return self._alive_since
def end_cycle(self):
"""
At each cycle, cell is more older if it's alive.
:return:
"""
if self.is_alive():
self._alive_since += 1
Note
Cell class is a child class of synergine_xyz.SynergyObject.SynergyObject.
And no more for our SynergyObjects.
Events and Actions¶
AliveAroundEvent¶
Events/Actions will be “born” and “die”. These actions will need to know how many alive cells are around the concerned cell. So we write AliveAroundEvent event:
synergine_lifegame/synergy/event/AliveAroundEvent.py:
from synergine.core.exceptions import NotConcernedEvent
from synergine_xyz.mechanism.AroundMechanism import AroundMechanism
from synergine.synergy.event.ContactEvent import ContactEvent
from synergine_lifegame.cst import ALIVE, COL_DIED
class AliveAroundEvent(ContactEvent):
"""
This class is a refactored class for die and born events.
"""
def _get_alive_cell_around_count(self, context, parameters):
cell_near_count = 0
# parameters dict is prepared by mechanism
for object_id_near in parameters['objects_ids_near']:
if context.metas.states.have(object_id_near, ALIVE):
cell_near_count += 1
return cell_near_count
Born and die event will be child of this event.
Born¶
The born event:
synergine_lifegame/synergy/event/GoodConditionToBornEvent.py:
from synergine_lifegame.synergy.event.AliveAroundEvent import AliveAroundEvent
from synergine.core.exceptions import NotConcernedEvent
from synergine_xyz.mechanism.AroundMechanism import AroundMechanism
from synergine_lifegame.cst import ALIVE, COL_DIED
class GoodConditionToBornEvent(AliveAroundEvent):
"""
This event is applied when born condition are here. So when exactly 3 alive cell are around of observed cell.
"""
_mechanism = AroundMechanism
"""This event need to know what is around concerned cell. So we use AroundMechanism who give us list of around
objects ids."""
_concern = COL_DIED
"""This event only concern died cells."""
def _prepare(self, object_id, context, parameters={}):
"""
According to “Conway’s Game of Life”, event match if exactly 3 around cell are alive.
"""
cell_near_count = self._get_alive_cell_around_count(context, parameters)
if cell_near_count is 3:
return parameters
# If event not match, we must raise an NotConcernedEvent.
raise NotConcernedEvent()
And his action:
synergine_lifegame/synergy/event/BornAction.py:
from synergine.synergy.event.Action import Action
from synergine_lifegame.synergy.event.GoodConditionToBornEvent import GoodConditionToBornEvent
class BornAction(Action):
"""
This action change state of Cell into alive.
"""
_listen = GoodConditionToBornEvent
"""This action listen the GoodConditionToBornEvent"""
def run(self, obj, context, synergy_manager):
obj.set_alive(True)
Die¶
The die event:
synergine_lifegame/synergy/event/NotGoodConditionToPersistEvent.py:
from synergine_lifegame.synergy.event.AliveAroundEvent import AliveAroundEvent
from synergine.core.exceptions import NotConcernedEvent
from synergine_xyz.mechanism.AroundMechanism import AroundMechanism
from synergine_lifegame.cst import ALIVE, COL_ALIVE
class NotGoodConditionToPersistEvent(AliveAroundEvent):
"""
This event is applied when die condition are here. So when less of 2 or more of 3 alive cell are around
of observed cell.
"""
_mechanism = AroundMechanism
"""This event need to know what is around concerned cell. So we use AroundMechanism who give us list of around
objects ids."""
_concern = COL_ALIVE
"""This event only concern alive cells."""
def _prepare(self, object_id, context, parameters={}):
"""
According to “Conway’s Game of Life”, event match if less of 2 or more of 3 alive cell are around.
"""
cell_near_count = self._get_alive_cell_around_count(context, parameters)
if cell_near_count < 2 or cell_near_count > 3:
return parameters
# If event not match, we must raise an NotConcernedEvent.
raise NotConcernedEvent()
And his action:
synergine_lifegame/synergy/event/DieAction.py:
from synergine.synergy.event.Action import Action
from synergine_lifegame.synergy.event.NotGoodConditionToPersistEvent import NotGoodConditionToPersistEvent
class DieAction(Action):
"""
This action change state of Cell into died.
"""
_listen = NotGoodConditionToPersistEvent
"""This action listen the NotGoodConditionToPersistEvent"""
def run(self, obj, context, synergy_manager):
obj.set_alive(False)
AroundMechanism¶
As you can see, these events uses the AroundMechanism. This mechanism prepare a list of object ids who are around the observed object.
Collection¶
Our Cells must be contained by a Collection.
synergine_lifegame/synergy/collection/LifeGameCollection.py:
from synergine.synergy.collection.SynergyCollection import SynergyCollection
from synergine_lifegame.synergy.event.DieAction import DieAction
from synergine_lifegame.synergy.event.BornAction import BornAction
from synergine_lifegame.synergy.event.TimePassAction import TimePassAction
class LifeGameCollection(SynergyCollection):
"""
This collection own cells of simulation.
"""
def __init__(self, configuration):
super().__init__(configuration)
# We list here actions who concern our simulation.
self._actions = [DieAction, BornAction, TimePassAction]
The collection must have a configuration (to populate his synergies objects).
synergine_lifegame/synergy/collection/LifeGameCollectionConfiguration.py:
from synergine.synergy.collection.Configuration import Configuration
from synergine_lifegame.synergy.object.Cell import Cell
class LifeGameCollectionConfiguration(Configuration):
def get_start_objects(self, collection, context):
cells = []
# We build a grid of 40x50 cells
for x in range(40):
for y in range(50):
cell = Cell(collection, context)
cell.set_position((0, x, y))
cells.append(cell)
# We define some position for alive cells
alive_cell_positions = (
(0, 20, 20),
(0, 21, 20),
(0, 22, 20),
(0, 22, 21),
(0, 22, 22),
(0, 21, 22),
(0, 20, 22)
)
# And we born these alive cell
for dead_cell in cells:
if dead_cell.get_position() in alive_cell_positions:
dead_cell.set_alive(True)
# Synergy objects can be return to the collection
return cells
And a Simulation container.
synergine_lifegame/synergy/LifeGameSimulation.py:
from synergine.synergy.Simulation import Simulation
class LifeGameSimulation(Simulation):
"""
Life game simulation container.
"""
pass # Nothing to do here
Additional features¶
According to our additional features:
In additional of "Conway's Game of Life" rules, we will represent differently in our 2d graphic output:
* A just born cell
* A born cell since 2 cycles
* A born cell since more 2 cycles
We must add an Action who have to increment age of alive cells:
synergine_lifegame/synergy/event/TimePassAction.py:
from synergine.synergy.event.Action import Action
from synergine_lifegame.synergy.event.TimePassEvent import TimePassEvent
from synergine_lifegame.synergy.event.DieAction import DieAction
from synergine_lifegame.synergy.event.BornAction import BornAction
class TimePassAction(Action):
_listen = TimePassEvent
"""This action listen the TimePassEvent"""
_depend = [BornAction, DieAction]
"""This action need to be executed after Born and Die action"""
def run(self, obj, context, synergy_manager):
obj.end_cycle()
Who listen a simple event:
synergine_lifegame/synergy/event/TimePassEvent.py:
from synergine.synergy.event.Event import Event
class TimePassEvent(Event):
def _prepare(self, obj, context, parameters={}):
# All cells are concerned
return parameters
Simple terminal¶
All needed algorithms are here. To be able to see something, we write a very simple output like this:
synergine_lifegame/PrintTerminal.py:
from synergine.core.connection.Terminal import Terminal
class PrintTerminal(Terminal):
"""
A very simple Terminal to see how many cells are alive and dead.
"""
def receive(self, actions_done):
objects = self._synergy_manager.get_objects()
alive_objects = [obj for obj in objects if obj.is_alive()]
died_objects = [obj for obj in objects if not obj.is_alive()]
cycle = self._context.get_cycle()
print("Cycle %d: %d cells alive, %d cells died." % (cycle, len(alive_objects), len(died_objects)))
Let’s go¶
We now need to prepare configuration and run script for our simulation (run.py)
from synergine.core.Core import Core
from synergine_lifegame.PrintTerminal import PrintTerminal
from synergine_lifegame.synergy.collection.LifeGameCollection import LifeGameCollection
from synergine_lifegame.synergy.LifeGameSimulation import LifeGameSimulation
from synergine_lifegame.synergy.collection.LifeGameCollectionConfiguration import LifeGameCollectionConfiguration
from synergine_xyz.Context import Context as XyzContext
config = {
'app': {
'name': 'LifeGame simulation',
'classes': {
'Context': XyzContext
}
},
'engine': {
'fpsmax': 5,
},
'simulations': [LifeGameSimulation([LifeGameCollection(LifeGameCollectionConfiguration())])],
'connections': [PrintTerminal]
}
if __name__ == '__main__':
# Run simulation
Core.start_core(config)
We execute script and, tadaaa:
python3.4 run.py
Cycle 0: 7 cells alive, 1993 dead cells.
Cycle 1: 7 cells alive, 1993 dead cells.
Cycle 2: 9 cells alive, 1991 dead cells.
Cycle 3: 9 cells alive, 1991 dead cells.
Cycle 4: 10 cells alive, 1990 dead cells.
Cycle 5: 12 cells alive, 1988 dead cells.
Cycle 6: 11 cells alive, 1989 dead cells.
Cycle 7: 16 cells alive, 1984 dead cells.
Cycle 8: 15 cells alive, 1985 dead cells.
Cycle 9: 23 cells alive, 1977 dead cells.
Cycle 10: 20 cells alive, 1980 dead cells.
Outputs¶
You can create all terminals you want. Some example:
2D pygame¶
Note
To install PyGame for Python 3.x you can follow these steps:
Xyzworld module delivery a ready to use PygameDisplay. We just need to prepare a display configuration for it:
synergine_lifegame/display/pygame_visualisation.py:
from synergine_xyz.display.object.pygame.PygameImage import PygameImage
from synergine_lifegame.synergy.object.Cell import Cell
from os import getcwd
image_cell_full = PygameImage.from_filepath(getcwd()+'/synergine_lifegame/display/pygame/cha3.png')
image_cell_medium = PygameImage.from_filepath(getcwd()+'/synergine_lifegame/display/pygame/cha2.png')
image_cell_small = PygameImage.from_filepath(getcwd()+'/synergine_lifegame/display/pygame/cha.png')
image_cell_dead = PygameImage.from_filepath(getcwd()+'/synergine_lifegame/display/pygame/cha0.png')
def alive_render(cell, context):
"""
:param cell: Cell
:param context: Context object
:return: The image render
:rtype: PygameImage
"""
if cell.is_alive():
if cell.get_is_alive_since() < 1:
return image_cell_small
elif cell.get_is_alive_since() == 1:
return image_cell_medium
else:
return image_cell_full
else:
return image_cell_dead
visualisation = {
'objects': {
Cell: {
'default': image_cell_small,
'callbacks': [alive_render]
}
}
}
And add it to run configuration (run.py)
from lifegame.synergy.LifeGameSimulation import LifeGameSimulation
from lifegame.synergy.collection.LifeGameCollectionConfiguration import LifeGameCollectionConfiguration
from synergine_xyz.Context import Context as XyzContext
+from synergine_xyz.display.PygameDisplay import PygameDisplay
+from synergine_lifegame.display.pygame_visualisation import visualisation as pygame_visualisation
# ...
'simulations': [LifeGameSimulation([LifeGameCollection(LifeGameCollectionConfiguration())])],
- 'connections': [PrintTerminal]
+ 'connections': [PrintTerminal, PygameDisplay],
+ 'terminal': {
+ 'pygame': {
+ 'visualisation': pygame_visualisation,
+ 'window_size': (1024, 768),
+ 'display': {
+ 'grid': {
+ 'size': 20
+ }
+ }
+ },
+ }
}
if __name__ == '__main__'
Then unzip these images
into synergine_lifegame/display/pygame/ folder.
When we run our simulation we can now see a beautiful 2D render :
Plot¶
Note
To get matplotlib and scipy dependencies on debian-like system you can execute:
>>> sudo apt-get install python3-scipy python3-matplotlib
Then, install matplotlib with pip with:
>>> pip install matplotlib
For example, we want to display a plot with history of alive cells count. We create a new Terminal:
synergine_lifegame/PlotTerminal.py:
from synergine.core.connection.Terminal import Terminal
import matplotlib.pyplot as plt
class PlotTerminal(Terminal):
"""
A very simple Terminal to see how many cells are alive in plot.
"""
def __init__(self, config, context, synergy_manager):
super().__init__(config, context, synergy_manager)
plt.axis([0, 1, 0, 1])
plt.ion()
plt.show()
self._max_alive = 0
def receive(self, actions_done):
objects = self._synergy_manager.get_objects()
alive_objects = [obj for obj in objects if obj.is_alive()]
cycle = self._context.get_cycle()
y = len(alive_objects)
if y > self._max_alive:
self._max_alive = y
plt.axis([0, cycle, 0, self._max_alive])
plt.scatter(cycle, y)
plt.draw()
And add it to run configuration (run.py)
from lifegame.synergy.collection.LifeGameCollectionConfiguration import LifeGameCollectionConfiguration
from synergine_xyz.Context import Context as XyzContext
from synergine_xyz.display.PygameDisplay import PygameDisplay
+from synergine_lifegame.PlotTerminal import PlotTerminal
# ...
- 'connections': [PrintTerminal, PygameDisplay]
+ 'connections': [PrintTerminal, PygameDisplay, PlotTerminal],
And let see result:
Note
If matplotlib don’t display, it’s possibly pip bugged package. Try with OS package python-matplotlib.
You are now ready to use synergine. You can visit intelligine project to see advanced usage of synergine.