refactoring and comments
This commit is contained in:
parent
530072b1d5
commit
0960991e47
@ -31,8 +31,8 @@ class WorldGeneratorWindow:
|
|||||||
cell_size = [x / y for x, y in zip(self.grid_size, grid_dimension)]
|
cell_size = [x / y for x, y in zip(self.grid_size, grid_dimension)]
|
||||||
|
|
||||||
surfaces_to_update = []
|
surfaces_to_update = []
|
||||||
for cell in self._cellular_automaton.grid.get_active_cells().values():
|
for cell in self._cellular_automaton.grid.get_cells().values():
|
||||||
if not cell.is_dirty():
|
if not cell.is_set_for_redrawing():
|
||||||
continue
|
continue
|
||||||
cell_coordinate = cell.coordinate
|
cell_coordinate = cell.coordinate
|
||||||
status = cell.get_status_for_iteration(self._cellular_automaton.get_iteration_index())
|
status = cell.get_status_for_iteration(self._cellular_automaton.get_iteration_index())
|
||||||
@ -45,7 +45,7 @@ class WorldGeneratorWindow:
|
|||||||
surface_pos = [x * y for x, y in zip(cell_size, cell_coordinate)]
|
surface_pos = [x * y for x, y in zip(cell_size, cell_coordinate)]
|
||||||
surface_pos[1] += 20
|
surface_pos[1] += 20
|
||||||
surfaces_to_update.append(self.screen.fill(cell_color, (surface_pos, cell_size)))
|
surfaces_to_update.append(self.screen.fill(cell_color, (surface_pos, cell_size)))
|
||||||
cell.release_dirty()
|
cell.release_from_redraw()
|
||||||
pygame.display.update(surfaces_to_update)
|
pygame.display.update(surfaces_to_update)
|
||||||
|
|
||||||
def main_loop(self):
|
def main_loop(self):
|
||||||
@ -53,7 +53,7 @@ class WorldGeneratorWindow:
|
|||||||
|
|
||||||
while running:
|
while running:
|
||||||
time_ca_start = time.time()
|
time_ca_start = time.time()
|
||||||
self._cellular_automaton.evolve()
|
self._cellular_automaton.evolve_x_times(10)
|
||||||
time_ca_end = time.time()
|
time_ca_end = time.time()
|
||||||
self._display_cellular_automaton()
|
self._display_cellular_automaton()
|
||||||
time_ds_end = time.time()
|
time_ds_end = time.time()
|
||||||
@ -86,17 +86,17 @@ class TestRule(Rule):
|
|||||||
cell.set_status_for_iteration([rand], iteration_index + 1)
|
cell.set_status_for_iteration([rand], iteration_index + 1)
|
||||||
if rand != 0:
|
if rand != 0:
|
||||||
active = True
|
active = True
|
||||||
cell.set_dirty()
|
cell.set_for_redraw()
|
||||||
elif len(neighbors) == 8:
|
elif len(neighbors) == 8:
|
||||||
left_neighbour_status = neighbors[3].get_status_for_iteration(last_iteration)
|
left_neighbour_status = neighbors[3].get_status_for_iteration(last_iteration)
|
||||||
active = cell.set_status_for_iteration(left_neighbour_status, iteration_index)
|
active = cell.set_status_for_iteration(left_neighbour_status, iteration_index)
|
||||||
if active:
|
if active:
|
||||||
cell.set_dirty()
|
cell.set_for_redraw()
|
||||||
return active
|
return active
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
rule = TestRule()
|
rule = TestRule()
|
||||||
ca = CellularAutomaton([10, 10], MooreNeighborhood(EdgeRule.IGNORE_EDGE_CELLS), rule, thread_count=2)
|
ca = CellularAutomaton([400, 400], MooreNeighborhood(EdgeRule.IGNORE_EDGE_CELLS), rule)
|
||||||
ca_window = WorldGeneratorWindow([1000, 730], ca)
|
ca_window = WorldGeneratorWindow([1000, 730], ca)
|
||||||
ca_window.main_loop()
|
ca_window.main_loop()
|
||||||
|
@ -7,19 +7,22 @@ class Cell:
|
|||||||
self._dirty = False
|
self._dirty = False
|
||||||
|
|
||||||
def set_neighbours(self, neighbours: list):
|
def set_neighbours(self, neighbours: list):
|
||||||
|
""" Set new cells as neighbour of this cell.
|
||||||
|
:param neighbours: A List of Cell names.
|
||||||
|
"""
|
||||||
self.neighbours = neighbours
|
self.neighbours = neighbours
|
||||||
|
|
||||||
def is_dirty(self):
|
def is_set_for_redrawing(self):
|
||||||
return self._dirty
|
return self._dirty
|
||||||
|
|
||||||
def set_dirty(self):
|
def set_for_redraw(self):
|
||||||
self._dirty = True
|
self._dirty = True
|
||||||
|
|
||||||
def release_dirty(self):
|
def release_from_redraw(self):
|
||||||
self._dirty = False
|
self._dirty = False
|
||||||
|
|
||||||
def set_status_for_iteration(self, new_status, iteration):
|
def set_status_of_iteration(self, new_status, iteration):
|
||||||
""" Will set the new status for Iteration.
|
""" Will set the new status for the iteration modulo two.
|
||||||
:param new_status: The new status to set.
|
:param new_status: The new status to set.
|
||||||
:param iteration: Uses the iteration index, to differ between current and next state.
|
:param iteration: Uses the iteration index, to differ between current and next state.
|
||||||
:return True if status has changed.
|
:return True if status has changed.
|
||||||
@ -28,8 +31,8 @@ class Cell:
|
|||||||
|
|
||||||
return self._status[0] != self._status[1]
|
return self._status[0] != self._status[1]
|
||||||
|
|
||||||
def get_status_for_iteration(self, iteration):
|
def get_status_of_iteration(self, iteration):
|
||||||
""" Will return the status for the iteration.
|
""" Will return the status for the iteration modulo two.
|
||||||
:param iteration: Uses the iteration index, to differ between current and next state.
|
:param iteration: Uses the iteration index, to differ between current and next state.
|
||||||
:return The status for this iteration.
|
:return The status for this iteration.
|
||||||
"""
|
"""
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
from cellular_automaton.ca_cell import Cell
|
from cellular_automaton.ca_cell import Cell
|
||||||
from cellular_automaton.ca_neighborhood import Neighborhood
|
from cellular_automaton.ca_neighborhood import Neighborhood
|
||||||
|
|
||||||
@ -16,19 +14,24 @@ class Grid:
|
|||||||
self._active_cells = self._cells.copy()
|
self._active_cells = self._cells.copy()
|
||||||
self._set_all_cells_active()
|
self._set_all_cells_active()
|
||||||
|
|
||||||
def get_names_of_active_cells(self):
|
def get_active_cell_names(self):
|
||||||
return list(self._active_cells.keys())
|
return list(self._active_cells.keys())
|
||||||
|
|
||||||
def get_active_cells(self):
|
def get_active_cells(self):
|
||||||
return self._active_cells
|
return self._active_cells
|
||||||
|
|
||||||
def clear_active_cells(self):
|
def get_cells(self):
|
||||||
self._active_cells.clear()
|
return self._cells
|
||||||
|
|
||||||
def set_cell_and_neighbours_active(self, cell_info: list):
|
def clear_active_cells(self):
|
||||||
self._active_cells[cell_info[0].name] = cell_info[0]
|
self._active_cells = {}
|
||||||
for neighbour in cell_info[1]:
|
|
||||||
self._active_cells[neighbour.name] = neighbour
|
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] = cells
|
||||||
|
|
||||||
def get_cell_and_neighbors(self, cell_name):
|
def get_cell_and_neighbors(self, cell_name):
|
||||||
cell = self._cells[cell_name]
|
cell = self._cells[cell_name]
|
||||||
@ -84,18 +87,10 @@ class Grid:
|
|||||||
try:
|
try:
|
||||||
self._recursive_step_down_dimensions(coordinate.copy(), dimension_index, self._set_cell_neighbours)
|
self._recursive_step_down_dimensions(coordinate.copy(), dimension_index, self._set_cell_neighbours)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
neighbours_coordinates = self._neighborhood.get_neighbor_coordinates(coordinate, self._dimension)
|
neighbours_coordinates = self._neighborhood.calculate_cell_neighbor_coordinates(coordinate, self._dimension)
|
||||||
neighbour_names = [self._cells[_join_coordinate(nc)].name for nc in neighbours_coordinates]
|
neighbour_names = [self._cells[_join_coordinate(nc)].name for nc in neighbours_coordinates]
|
||||||
self._cells[_join_coordinate(coordinate)].set_neighbours(neighbour_names)
|
self._cells[_join_coordinate(coordinate)].set_neighbours(neighbour_names)
|
||||||
|
|
||||||
# def __sizeof__(self):
|
|
||||||
# size = 0
|
|
||||||
# for cell in self._cells.values():
|
|
||||||
# size += sys.getsizeof(cell)
|
|
||||||
# size += sys.getsizeof(self._dimension) + sys.getsizeof(self._cells) + sys.getsizeof(self._active_cells)
|
|
||||||
|
|
||||||
# return size
|
|
||||||
|
|
||||||
|
|
||||||
def _instantiate_coordinate_if_necessary(coordinate):
|
def _instantiate_coordinate_if_necessary(coordinate):
|
||||||
if coordinate is None:
|
if coordinate is None:
|
||||||
|
@ -9,19 +9,28 @@ class EdgeRule(Enum):
|
|||||||
|
|
||||||
class Neighborhood:
|
class Neighborhood:
|
||||||
def __init__(self, neighbors: list, edge_rule: EdgeRule):
|
def __init__(self, neighbors: list, edge_rule: EdgeRule):
|
||||||
|
""" Defines a neighborhood for cells.
|
||||||
|
:param neighbors: List of relative coordinates for the neighbors.
|
||||||
|
:param edge_rule: A EdgeRule to define, how Cells on the edge of the grid will be handled.
|
||||||
|
"""
|
||||||
self._neighbors = neighbors
|
self._neighbors = neighbors
|
||||||
self.edge_rule = edge_rule
|
self.edge_rule = edge_rule
|
||||||
self.dimensions = []
|
self.grid_dimensions = []
|
||||||
|
|
||||||
def get_relative_neighbor_coordinates(self):
|
def get_relative_neighbor_coordinates(self):
|
||||||
return self._neighbors
|
return self._neighbors
|
||||||
|
|
||||||
def get_neighbor_coordinates(self, cell_coordinate, dimensions):
|
def calculate_cell_neighbor_coordinates(self, cell_coordinate, grid_dimensions):
|
||||||
self.dimensions = dimensions
|
""" Get a list of coordinates for the cell neighbors. The EdgeRule can reduce the returned neighbor count.
|
||||||
|
:param cell_coordinate: The coordinate of the cell to get the neighbors
|
||||||
|
:param grid_dimensions: The dimensions of the grid, to apply edge the rule.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.grid_dimensions = grid_dimensions
|
||||||
if self._does_ignore_edge_cell_rule_apply(cell_coordinate):
|
if self._does_ignore_edge_cell_rule_apply(cell_coordinate):
|
||||||
return []
|
return []
|
||||||
else:
|
else:
|
||||||
return self._apply_edge_rule_to_neighbours_of(cell_coordinate)
|
return self._apply_edge_rule_to_neighbours(cell_coordinate)
|
||||||
|
|
||||||
def _does_ignore_edge_cell_rule_apply(self, coordinate):
|
def _does_ignore_edge_cell_rule_apply(self, coordinate):
|
||||||
if self.edge_rule == EdgeRule.IGNORE_EDGE_CELLS and self._is_coordinate_on_an_edge(coordinate):
|
if self.edge_rule == EdgeRule.IGNORE_EDGE_CELLS and self._is_coordinate_on_an_edge(coordinate):
|
||||||
@ -29,37 +38,42 @@ class Neighborhood:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def _is_coordinate_on_an_edge(self, coordinate):
|
def _is_coordinate_on_an_edge(self, coordinate):
|
||||||
for nd, d in zip(coordinate, self.dimensions):
|
for neighbor_dimension, dimension in zip(coordinate, self.grid_dimensions):
|
||||||
if nd == 0 or nd == d - 1:
|
if neighbor_dimension == 0 or neighbor_dimension == dimension - 1:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _apply_edge_rule_to_neighbours_of(self, cell_coordinate):
|
def _apply_edge_rule_to_neighbours(self, coordinate):
|
||||||
remaining_neighbours = []
|
remaining_neighbours = []
|
||||||
for neighbour in self._neighbors:
|
for neighbour in self._neighbors:
|
||||||
if not self._does_ignore_edge_cell_neighbours_rule_apply(neighbour, cell_coordinate):
|
if not self._does_ignore_edge_cell_neighbours_rule_apply(neighbour, coordinate):
|
||||||
remaining_neighbours.append(self._calculate_neighbour_coordinate(neighbour, cell_coordinate))
|
remaining_neighbours.append(self._calculate_neighbour_coordinate(neighbour, coordinate))
|
||||||
return remaining_neighbours
|
return remaining_neighbours
|
||||||
|
|
||||||
def _does_ignore_edge_cell_neighbours_rule_apply(self, neighbour, cell_coordinate):
|
def _does_ignore_edge_cell_neighbours_rule_apply(self, neighbour, coordinate):
|
||||||
if self.edge_rule == EdgeRule.IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS:
|
if self.edge_rule == EdgeRule.IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS:
|
||||||
for rel_nd, cd, d in zip(neighbour, cell_coordinate, self.dimensions):
|
for rel_neighbour_dim, cell_dim, dim in zip(neighbour, coordinate, self.grid_dimensions):
|
||||||
nd = cd + rel_nd
|
neighbor_dimension = cell_dim + rel_neighbour_dim
|
||||||
if nd < 0 or nd >= d:
|
if neighbor_dimension < 0 or neighbor_dimension >= dim:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _calculate_neighbour_coordinate(self, neighbour, cell_coordinate):
|
def _calculate_neighbour_coordinate(self, neighbour, cell_coordinate):
|
||||||
new_coordinate = []
|
new_coordinate = []
|
||||||
for rel_nd, cd, d in zip(neighbour, cell_coordinate, self.dimensions):
|
for rel_neighbour_dim, cell_dim, dim in zip(neighbour, cell_coordinate, self.grid_dimensions):
|
||||||
nd = cd + rel_nd
|
neighbor_dim = cell_dim + rel_neighbour_dim
|
||||||
if nd < 0:
|
neighbor_dim = self._calculate_neighbour_dimension_of_edge_cells(dim, neighbor_dim)
|
||||||
nd = d - 1
|
new_coordinate.append(neighbor_dim)
|
||||||
elif nd >= d:
|
|
||||||
nd = 0
|
|
||||||
new_coordinate.append(nd)
|
|
||||||
return new_coordinate
|
return new_coordinate
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _calculate_neighbour_dimension_of_edge_cells(dim, neighbor_dim):
|
||||||
|
if neighbor_dim < 0:
|
||||||
|
neighbor_dim = dim - 1
|
||||||
|
elif neighbor_dim >= dim:
|
||||||
|
neighbor_dim = 0
|
||||||
|
return neighbor_dim
|
||||||
|
|
||||||
|
|
||||||
class MooreNeighborhood(Neighborhood):
|
class MooreNeighborhood(Neighborhood):
|
||||||
def __init__(self, edge_rule: EdgeRule):
|
def __init__(self, edge_rule: EdgeRule):
|
||||||
|
@ -1,81 +1,62 @@
|
|||||||
import multiprocessing
|
|
||||||
import time
|
|
||||||
|
|
||||||
from cellular_automaton.ca_grid import Grid
|
from cellular_automaton.ca_grid import Grid
|
||||||
from cellular_automaton.ca_rule import Rule
|
from cellular_automaton.ca_rule import Rule
|
||||||
from cellular_automaton.ca_neighborhood import Neighborhood
|
from cellular_automaton.ca_neighborhood import Neighborhood
|
||||||
|
|
||||||
|
|
||||||
class CellularAutomaton:
|
class CellularAutomaton:
|
||||||
def __init__(self, dimension: list, neighborhood: Neighborhood, rule_: Rule=None, thread_count: int=4):
|
def __init__(self, dimension: list, neighborhood: Neighborhood, evolution_rule: Rule=None):
|
||||||
self.grid = Grid(dimension, neighborhood)
|
self.grid = Grid(dimension, neighborhood)
|
||||||
self.rule = rule_
|
self.evolution_rule = evolution_rule
|
||||||
self._thread_count = thread_count
|
|
||||||
self.iteration = 0
|
self.iteration = 0
|
||||||
self.test_number = 0
|
self.test_number = 0
|
||||||
|
|
||||||
def set_rule(self, rule: Rule):
|
def set_rule(self, rule: Rule):
|
||||||
self.rule = rule
|
""" Set new evolution rule.
|
||||||
|
:param rule:
|
||||||
def set_thread_count(self, thread_count: int):
|
:return:
|
||||||
self._thread_count = thread_count
|
"""
|
||||||
|
self.evolution_rule = rule
|
||||||
|
|
||||||
def get_iteration_index(self):
|
def get_iteration_index(self):
|
||||||
|
""" Get the count of evolution cycles done.
|
||||||
|
:return: Evolution steps done.
|
||||||
|
"""
|
||||||
return self.iteration
|
return self.iteration
|
||||||
|
|
||||||
|
def evolve_x_times(self, evolutions: int):
|
||||||
|
""" Evolve all cells for x time steps.
|
||||||
|
:param evolutions: the count of evolutions done.
|
||||||
|
:return: True if all cells are inactive
|
||||||
|
"""
|
||||||
|
for evo in range(evolutions):
|
||||||
|
finished = self.evolve()
|
||||||
|
if finished:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def evolve(self):
|
def evolve(self):
|
||||||
""" Evolves all active cells for one time step.
|
""" Evolves all active cells for one time step.
|
||||||
:return: True if all cells are inactive.
|
:return: True if all cells are inactive.
|
||||||
"""
|
"""
|
||||||
if self._all_cells_are_inactive():
|
if self._is_evolution_finished():
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.iteration += 1
|
self.iteration += 1
|
||||||
self._start_multi_process_cell_evolution()
|
self._evolve_all_active_cells()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _start_multi_process_cell_evolution(self):
|
def _evolve_all_active_cells(self):
|
||||||
""" Evolves the cells in separate threads """
|
active_cells = self.grid.get_active_cell_names()
|
||||||
lists_of_cell_names = self._create_list_of_cell_names_lists_for_all_threads()
|
|
||||||
self.grid.clear_active_cells()
|
self.grid.clear_active_cells()
|
||||||
jobs = self._start_treads_to_evolve_grid(lists_of_cell_names)
|
self._evolve_cells(active_cells)
|
||||||
self._wait_for_all_threads_to_finish(jobs)
|
|
||||||
print(self.test_number)
|
|
||||||
|
|
||||||
def _all_cells_are_inactive(self):
|
def _is_evolution_finished(self):
|
||||||
return len(self.grid.get_names_of_active_cells()) == 0
|
return len(self.grid.get_active_cell_names()) == 0
|
||||||
|
|
||||||
def _create_list_of_cell_names_lists_for_all_threads(self):
|
def _evolve_cells(self, cell_names: list):
|
||||||
active_cells = self.grid.get_names_of_active_cells()
|
for cell_name in cell_names:
|
||||||
cell_count_per_thread = int(len(active_cells) / self._thread_count)
|
cell_info = self.grid.get_cell_and_neighbors(cell_name)
|
||||||
return self._divide_list_in_parts_of_size(active_cells, cell_count_per_thread)
|
active = self.evolution_rule.evolve_cell(cell_info[0], cell_info[1], self.iteration)
|
||||||
|
|
||||||
@staticmethod
|
if active:
|
||||||
def _divide_list_in_parts_of_size(active_cells: list, cell_count_per_thread: int):
|
self.grid.set_cells_active(cell_info[0] + cell_info[1])
|
||||||
return [active_cells[i:i + cell_count_per_thread]
|
|
||||||
for i in range(0, len(active_cells), cell_count_per_thread)]
|
|
||||||
|
|
||||||
def _start_treads_to_evolve_grid(self, lists_of_cell_names):
|
|
||||||
jobs = []
|
|
||||||
for t in range(self._thread_count):
|
|
||||||
process = multiprocessing.Process(target=_evolve_cells, args=(self, lists_of_cell_names[t]))
|
|
||||||
jobs.append(process)
|
|
||||||
process.start()
|
|
||||||
return jobs
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _wait_for_all_threads_to_finish(jobs):
|
|
||||||
for process in jobs:
|
|
||||||
process.join()
|
|
||||||
|
|
||||||
|
|
||||||
def _evolve_cells(cellular_automaton, cell_names: list):
|
|
||||||
time.sleep(2)
|
|
||||||
cellular_automaton.test_number += 1
|
|
||||||
print(cellular_automaton.grid)
|
|
||||||
for cell_name in cell_names:
|
|
||||||
cell_info = cellular_automaton.grid.get_cell_and_neighbors(cell_name)
|
|
||||||
active = cellular_automaton.rule.evolve_cell(cell_info[0], cell_info[1], cellular_automaton.iteration)
|
|
||||||
|
|
||||||
if active:
|
|
||||||
cellular_automaton.grid.set_cell_and_neighbours_active(cell_info)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user