diff --git a/scripts/main_ui.py b/scripts/main_ui.py index a32e59a..6bc58e1 100644 --- a/scripts/main_ui.py +++ b/scripts/main_ui.py @@ -10,6 +10,7 @@ class TestRule(Rule): try: return last_neighbour_states[0] except IndexError: + print("damn neighbours") pass return False @@ -21,7 +22,7 @@ class MyState(CellState): if rand > 99: init = 1 - super().__init__(init, draw_first_state=False) + super().__init__((float(init),), draw_first_state=False) def get_state_draw_color(self, iteration): red = 0 @@ -41,7 +42,7 @@ if __name__ == "__main__": freeze_support() random.seed(1000) rule = TestRule() - grid = Grid(dimension=[400, 400], + grid = Grid(dimension=[200, 200], # best is 400/400 with 0,2 ca speed and 0,09 redraw neighborhood=MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS), state_class=MyState) ca = CellularAutomaton(grid, rule) diff --git a/src/cellular_automaton/ca_cell.py b/src/cellular_automaton/ca_cell.py index b96dff9..67a8192 100644 --- a/src/cellular_automaton/ca_cell.py +++ b/src/cellular_automaton/ca_cell.py @@ -1,61 +1,44 @@ -import multiprocessing - from cellular_automaton.ca_cell_state import CellState class Cell: - def __init__(self, name, state_class: CellState.__class__, coordinate: list): - self._name = name + def __init__(self, state_class: CellState.__class__, coordinate: list): self._coordinate = coordinate self._state = state_class() self._neighbours = [] - self._active = multiprocessing.Value('i', 1) - self._age = multiprocessing.Value('i', 0) def set_neighbours(self, neighbours): self._neighbours = neighbours + def get_state(self): + return self._state + def get_coordinate(self): return self._coordinate def evolve_if_ready(self, rule): if self._neighbours_are_younger(): - if self._is_active(): - new_state = rule(self.get_current_state(), self.get_neighbour_states()) + if self._state.is_active(): + new_state = rule(self._state.get_current_state(), self.get_neighbour_states()) self.set_new_state_and_activate(new_state) - self.increase_age() + self._state.increase_age() def _neighbours_are_younger(self): for n in self._neighbours: - if n.get_age() < self.get_age(): + if n.get_age() < self._state.get_age(): return False - - def get_age(self): - return self._age.value - - def _is_active(self): - return self._active.value > self._age.value - - def get_current_state(self): - return self._state.get_state_of_iteration(self._age.value) + return True def get_neighbour_states(self): - return [n.get_state_from_iteration(self._age.value) for n in self._neighbours] + return [n.get_state_of_iteration(self._state.get_age()) for n in self._neighbours] def set_new_state_and_activate(self, new_state: CellState): - changed = self._state.set_current_state(new_state, self._age.value + 1) + changed = self._state.set_current_state(new_state) if changed: self._set_active() def _set_active(self): - self.set_active_for_next_iteration(self._age.value) + self._state.set_active_for_next_iteration(self._state.get_age() + 1) for n in self._neighbours: - n.set_active_for_next_iteration(self._age.value) - - def set_active_for_next_iteration(self, iteration): - self._active.value = max(self._active.value, iteration + 1) - - def increase_age(self): - with self._age.get_lock(): - self._age += 1 + n.set_active_for_next_iteration(self._state.get_age() + 1) diff --git a/src/cellular_automaton/ca_cell_state.py b/src/cellular_automaton/ca_cell_state.py index 42e6e3e..d8c0ca3 100644 --- a/src/cellular_automaton/ca_cell_state.py +++ b/src/cellular_automaton/ca_cell_state.py @@ -2,32 +2,51 @@ from multiprocessing import Array, Value class CellState: + _state_save_slot_count = 2 """ This is the base class for all cell states. When using the cellular automaton display, inherit this class and implement get_state_draw_color. """ - def __init__(self, initial_state=(0., ), state_save_slot_count=2, draw_first_state=True): - self._state_save_slot_count = state_save_slot_count - self._state_slots = [Array('d', initial_state)] * state_save_slot_count + def __init__(self, initial_state=(0., ), draw_first_state=True): + self._state_slots = [Array('d', initial_state) for i in range(self.__class__._state_save_slot_count)] + self._active = Value('i', 1) + self._age = Value('i', 0) if draw_first_state: self._dirty = Value('i', 1) else: self._dirty = Value('i', 0) + def get_age(self): + return self._age.value + + def get_current_state(self): + return self.get_state_of_iteration(self.get_age()) + + def get_state_of_iteration(self, iteration): + """ Will return the state for the iteration modulo number of saved states. + :param iteration: Uses the iteration index, to differ between concurrent states. + :return The state for this iteration. + """ + return self._state_slots[iteration % self.__class__._state_save_slot_count] + + def is_active(self): + return self._active.value > self._age.value + + def set_active_for_next_iteration(self, iteration): + self._active.value = max(self._active.value, iteration + 1) + + def increase_age(self): + with self._age.get_lock(): + self._age.value += 1 + def is_set_for_redraw(self): return self._dirty != 1 - def get_state_changes(self): - return self._dirty - - def set_for_redraw(self): - self._dirty = 1 - def was_redrawn(self): self._dirty = 0 - def set_current_state(self, new_state, current_iteration_index): - return self.set_state_of_iteration(new_state, current_iteration_index) + def set_current_state(self, new_state): + return self.set_state_of_iteration(new_state, self.get_age() + 1) def get_state_of_last_iteration(self, current_iteration_index): return self.get_state_of_iteration(current_iteration_index - 1) @@ -38,10 +57,8 @@ class CellState: :param iteration: Uses the iteration index, to differ between concurrent states. :return True if state has changed. """ - slot_count = self._state_save_slot_count - states = self._state_slots - current_state = states[iteration % slot_count] + current_state = self.get_state_of_iteration(iteration) changed = False for i in range(len(current_state)): @@ -53,16 +70,9 @@ class CellState: except IndexError: raise IndexError("New State length or type is invalid!") - self._dirty |= changed + self._dirty.value |= changed return changed - def get_state_of_iteration(self, iteration): - """ Will return the state for the iteration modulo number of saved states. - :param iteration: Uses the iteration index, to differ between concurrent states. - :return The state for this iteration. - """ - return self._state_slots[iteration % self._state_save_slot_count] - def get_state_draw_color(self, iteration): raise NotImplementedError diff --git a/src/cellular_automaton/ca_display.py b/src/cellular_automaton/ca_display.py index 5c7a3bd..a2c7d0e 100644 --- a/src/cellular_automaton/ca_display.py +++ b/src/cellular_automaton/ca_display.py @@ -74,13 +74,13 @@ class PyGameFor2D: def _cell_redraw_rectangles(cells, evolution_index, display_info): for cell in cells: - if cell.state.is_set_for_redraw(): - cell_color = cell.state.get_state_draw_color(evolution_index) + if cell.get_state().is_set_for_redraw(): + cell_color = cell.get_state().get_state_draw_color(evolution_index) cell_pos = _calculate_cell_position(display_info.cell_size, cell) surface_pos = list(map(operator.add, cell_pos, display_info.grid_pos)) yield display_info.screen.fill(cell_color, (surface_pos, display_info.cell_size)) - cell.state.was_redrawn() + cell.get_state().was_redrawn() def _calculate_cell_position(cell_size, cell): - return list(map(operator.mul, cell_size, cell.coordinate)) + return list(map(operator.mul, cell_size, cell.get_coordinate())) diff --git a/src/cellular_automaton/ca_grid.py b/src/cellular_automaton/ca_grid.py index 4c73aba..c9781f2 100644 --- a/src/cellular_automaton/ca_grid.py +++ b/src/cellular_automaton/ca_grid.py @@ -17,36 +17,6 @@ class Grid: self._active_cells = self._cells.copy() self._set_all_cells_active() - def get_active_cell_names(self): - return list(self._active_cells.keys()) - - def get_active_cells(self): - return self._active_cells - - def get_cells(self): - return self._cells - - def clear_active_cells(self): - self._active_cells = {} - - def set_cells_active(self, cells: list): - """ Consider the cells in the next evolution cycle. - :param cells: A list of Cell objects, that shall be considered in the next evolution cycle. - """ - for cell in cells: - self._active_cells[cell.name] = cell - - def get_cell_and_neighbors(self, cell_name): - cell = self._cells[cell_name] - return [cell, cell.neighbours] - - def get_dimension(self): - return self._dimension - - def _set_all_cells_active(self): - for cell_key, cell in self._cells.items(): - self._active_cells[cell_key] = cell - def _create_cells(self, dimension_index=0, coordinate=None): """ Recursively steps down the dimensions to create cells in n dimensions and adds them to a dict. :param dimension_index: The index indicating which dimension is currently traversed. @@ -59,7 +29,7 @@ class Grid: self._recursive_step_down_dimensions(coordinate, dimension_index, self._create_cells) except IndexError: coordinate_string = _join_coordinate(coordinate) - self._cells[coordinate_string] = Cell(coordinate_string, self._state_class, coordinate) + self._cells[coordinate_string] = Cell(self._state_class, coordinate) def _recursive_step_down_dimensions(self, coordinate, dimension_index, recursion_method): """ For the range of the current dimension, recalls the recursion method. @@ -75,10 +45,26 @@ class Grid: for cell in self._cells.values(): neighbours_coordinates = neighborhood.calculate_cell_neighbor_coordinates(cell.get_coordinate(), self._dimension) - cell.set_neighbours(list(map(self._get_cell_by_coordinate, neighbours_coordinates))) + cell.set_neighbours(list(map(self._get_cell_state_by_coordinate, neighbours_coordinates))) - def _get_cell_by_coordinate(self, coordinate): - return self._cells[_join_coordinate(coordinate)] + def _get_cell_state_by_coordinate(self, coordinate): + return self._cells[_join_coordinate(coordinate)].get_state() + + def _set_all_cells_active(self): + for cell_key, cell in self._cells.items(): + self._active_cells[cell_key] = cell + + def get_active_cell_names(self): + return list(self._active_cells.keys()) + + def get_active_cells(self): + return self._active_cells + + def get_cells(self): + return self._cells + + def get_dimension(self): + return self._dimension def _instantiate_coordinate_if_necessary(coordinate): diff --git a/src/cellular_automaton/cellular_automaton.py b/src/cellular_automaton/cellular_automaton.py index a02eb21..7b9f3c7 100644 --- a/src/cellular_automaton/cellular_automaton.py +++ b/src/cellular_automaton/cellular_automaton.py @@ -35,5 +35,5 @@ class CellularAutomatonProcessor: def _process_routine(cells, rule, active): while active.value == 1: for cell in cells: - cell.evolve_if_ready(rule) + cell.evolve_if_ready(rule.evolve_cell) diff --git a/test/test_cell.py b/test/test_cell.py new file mode 100644 index 0000000..30e2f67 --- /dev/null +++ b/test/test_cell.py @@ -0,0 +1,13 @@ +import sys +sys.path.append('../src') + +import cellular_automaton.ca_cell as cac +import unittest + + +class TestCellState(unittest.TestCase): + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_cell_state.py b/test/test_cell_state.py index ea1d3ab..97984ba 100644 --- a/test/test_cell_state.py +++ b/test/test_cell_state.py @@ -7,20 +7,20 @@ import unittest class TestCellState(unittest.TestCase): - def test_get_state_of_iteration(self): - cell_state = cs.CellState(0, state_save_slot_count=3) - cell_state.set_status_of_iteration(1, 0) - self.assertEqual(cell_state.get_status_of_iteration(3), 1) + def test_get_state_with_overflow(self): + cell_state = cs.CellState(initial_state=(0,)) + cell_state.set_state_of_iteration(new_state=(1,), iteration=0) + self.assertEqual(tuple(cell_state.get_state_of_iteration(2)), (1,)) - def test_set_state_applies_overflow(self): - cell_state = cs.CellState(0, state_save_slot_count=4) - cell_state.set_status_of_iteration(1, 4) - self.assertEqual(cell_state.get_status_of_iteration(0), 1) + def test_set_state_with_overflow(self): + cell_state = cs.CellState(initial_state=(0,)) + cell_state.set_state_of_iteration(new_state=(1,), iteration=2) + self.assertEqual(tuple(cell_state.get_state_of_iteration(0)), (1,)) - def test_set_state_only_applies_to_iteration_slot(self): - cell_state = cs.CellState(0, state_save_slot_count=2) - cell_state.set_status_of_iteration(1, 0) - self.assertEqual(cell_state.get_status_of_iteration(1), 0) + def test_set_state_does_not_effect_all_slots(self): + cell_state = cs.CellState(initial_state=(0,)) + cell_state.set_state_of_iteration(new_state=(1,), iteration=0) + self.assertEqual(tuple(cell_state.get_state_of_iteration(1)), (0,)) if __name__ == '__main__':