Fix/display stop when done

This commit is contained in:
Richard Feistenauer 2021-01-09 12:51:20 +00:00
parent 9e211aa581
commit 6d1666bb8b
8 changed files with 54 additions and 12 deletions

View File

@ -98,8 +98,12 @@ There ist still quite some work to do.
And for all others, don't hesitate to open issues when you have problems! And for all others, don't hesitate to open issues when you have problems!
## Changelog ## Changelog
#### 1.0.8
- Fixes automaton using edge cells with radius > 1 not working
- Fixes automaton is not stopping after evolution ended
#### 1.0.7 #### 1.0.7
- Fixes automaton ont active on reactivation - Fixes automaton not active on reactivation
#### 1.0.6 #### 1.0.6
- Fixes reactivation not redrawing all cells - Fixes reactivation not redrawing all cells

View File

@ -75,15 +75,19 @@ class CellularAutomatonCreator(abc.ABC):
class CellularAutomaton(CellularAutomatonCreator, abc.ABC): class CellularAutomaton(CellularAutomatonCreator, abc.ABC):
""" """ This class represents a cellular automaton.
This class represents a cellular automaton.
It can be created with n dimensions and can handle different neighborhood definitions. It can be created with n dimensions and can handle different neighborhood definitions.
:param dimension: Iterable of len = dimensions It is intended to be uses as base class.
(e.g. [4, 3, 3, 3] = 4 x 3 x 3 x 3 cells in a four dimensional cube). Override `init_cell_state()` to define the state the cell(s) are initiated with.
:param neighborhood: Defines which cells are considered neighbors. Override `evolve()` to define the rule that is aplied on every evolution step of this automaton.
""" """
def __init__(self, neighborhood: Neighborhood, *args, **kwargs): def __init__(self, neighborhood: Neighborhood, *args, **kwargs):
""" Initiates a cellular automaton by the use of the `init_cell_state` method.
:param neighborhood: Defines which cells are considered neighbors.
:param dimension: Iterable of len = dimensions
(e.g. [4, 3, 3, 3] = 4 x 3 x 3 x 3 cells in a four dimensional cube).
"""
super().__init__(neighborhood=neighborhood, *args, **kwargs) super().__init__(neighborhood=neighborhood, *args, **kwargs)
self._evolution_step = 0 self._evolution_step = 0
self._active = True self._active = True
@ -132,7 +136,7 @@ class CellularAutomaton(CellularAutomatonCreator, abc.ABC):
evolution_rule = self.evolve_rule evolution_rule = self.evolve_rule
for old, new in zip(this_state.values(), next_state.values()): for old, new in zip(this_state.values(), next_state.values()):
if old.is_active: if old.is_active:
new_state = evolution_rule(old.state, [n.state for n in old.neighbors]) new_state = evolution_rule(old.state.copy(), [n.state for n in old.neighbors])
old.is_active = False old.is_active = False
evolve_cell(old, new, new_state) evolve_cell(old, new, new_state)

View File

@ -117,7 +117,8 @@ class CAWindow:
time.sleep(rest_time) time.sleep(rest_time)
def _not_at_the_end(self, last_evolution_step): def _not_at_the_end(self, last_evolution_step):
return self._cellular_automaton.evolution_step < last_evolution_step or last_evolution_step <= 0 return (self._cellular_automaton.evolution_step < last_evolution_step or last_evolution_step <= 0) \
and self._cellular_automaton.active
def __calculate_cell_display_size(self, stretch_cells): # pragma: no cover def __calculate_cell_display_size(self, stretch_cells): # pragma: no cover
grid_dimension = self._cellular_automaton.dimension grid_dimension = self._cellular_automaton.dimension

View File

@ -83,7 +83,7 @@ class Neighborhood:
yield tuple(map(operator.add, rel_n, cell_coordinate)) yield tuple(map(operator.add, rel_n, cell_coordinate))
def __is_coordinate_on_an_edge(self, coordinate): def __is_coordinate_on_an_edge(self, coordinate):
return any(ci in [0, di-1] for ci, di in zip(coordinate, self._grid_dimensions)) return any(not(self._radius-1 < ci < di-self._radius) for ci, di in zip(coordinate, self._grid_dimensions))
class MooreNeighborhood(Neighborhood): class MooreNeighborhood(Neighborhood):
@ -154,7 +154,6 @@ class RadialNeighborhood(Neighborhood):
cross_sum = 0 cross_sum = 0
for coordinate_i in rel_neighbor: for coordinate_i in rel_neighbor:
cross_sum += pow(coordinate_i, 2) cross_sum += pow(coordinate_i, 2)
return math.sqrt(cross_sum) <= self._radius + self.delta return math.sqrt(cross_sum) <= self._radius + self.delta

View File

@ -59,5 +59,7 @@ def profile(code):
if __name__ == "__main__": if __name__ == "__main__":
with contextlib.suppress(KeyboardInterrupt): with contextlib.suppress(KeyboardInterrupt):
print("=== CREATION ===")
profile('ca = StarFallAutomaton()') profile('ca = StarFallAutomaton()')
print("=== COMPUTATION ===")
profile('ca.evolve(times=10)') profile('ca.evolve(times=10)')

View File

@ -7,7 +7,7 @@ with open('README.md') as f:
setup( setup(
name="cellular_automaton", name="cellular_automaton",
version="1.0.7", version="1.0.8",
author="Richard Feistenauer", author="Richard Feistenauer",
author_email="r.feistenauer@web.de", author_email="r.feistenauer@web.de",
packages=find_packages(exclude=('tests', 'docs', 'examples')), packages=find_packages(exclude=('tests', 'docs', 'examples')),

View File

@ -52,7 +52,10 @@ class TAutomaton(ca.CellularAutomaton):
return [1] if cell_coordinate == (1, 1) else [0] return [1] if cell_coordinate == (1, 1) else [0]
def evolve_rule(self, last_cell_state, neighbors_last_states): def evolve_rule(self, last_cell_state, neighbors_last_states):
return [last_cell_state[0] + 1] if neighbors_last_states else last_cell_state ns = last_cell_state[:]
if 0 < last_cell_state[0] < 40:
ns[0] += 1
return ns
@pytest.fixture @pytest.fixture
@ -70,3 +73,12 @@ def test_evolution_steps_per_draw(automaton, pygame_mock):
def test_updated_rectangle_calls(automaton, pygame_mock): def test_updated_rectangle_calls(automaton, pygame_mock):
ca.CAWindow(cellular_automaton=automaton, window_size=(10, 10)).run(last_evolution_step=4) ca.CAWindow(cellular_automaton=automaton, window_size=(10, 10)).run(last_evolution_step=4)
assert pygame_mock.display.update.call_count == 4 * (3 + 1) # steps * (texts + changed cells) assert pygame_mock.display.update.call_count == 4 * (3 + 1) # steps * (texts + changed cells)
@import_mock(module='pygame')
def test_ends_when_ca_is_done(automaton, pygame_mock):
automaton.evolve(39)
assert automaton.active == True
assert automaton.evolution_step == 39
ca.CAWindow(cellular_automaton=automaton, window_size=(10, 10)).run(last_evolution_step=45)
assert automaton.active == False
assert automaton.evolution_step == 40

View File

@ -93,6 +93,26 @@ def test_radial():
(1, 4), (2, 4), (3, 4)) (1, 4), (2, 4), (3, 4))
def test_radial_neighbor_coords():
neighborhood = ca.RadialNeighborhood(edge_rule=ca.EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS, radius=2)
neighbor_coords = neighborhood.calculate_cell_neighbor_coordinates((0, 0), (10, 10))
assert neighbor_coords == ((9, 8), (0, 8), (1, 8),
(8, 9), (9, 9), (0, 9), (1, 9), (2, 9),
(8, 0), (9, 0), (1, 0), (2, 0),
(8, 1), (9, 1), (0, 1), (1, 1), (2, 1),
(9, 2), (0, 2), (1, 2))
def test_radial_neighbor_coords():
neighborhood = ca.RadialNeighborhood(edge_rule=ca.EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS, radius=2)
neighbor_coords = neighborhood.calculate_cell_neighbor_coordinates((1, 1), (10, 10))
assert neighbor_coords == ((0, 9), (1, 9), (2, 9),
(9, 0), (0, 0), (1, 0), (2, 0), (3, 0),
(9, 1), (0, 1), (2, 1), (3, 1),
(9, 2), (0, 2), (1, 2), (2, 2), (3, 2),
(0, 3), (1, 3), (2, 3))
@pytest.mark.parametrize(('coordinate', 'expected_neighborhood'), @pytest.mark.parametrize(('coordinate', 'expected_neighborhood'),
(((2, 2), ((1, 0), (2, 0), (3, 0), (((2, 2), ((1, 0), (2, 0), (3, 0),
(0, 1), (1, 1), (2, 1), (3, 1), (0, 1), (1, 1), (2, 1), (3, 1),