working fast cellular automaton
This commit is contained in:
parent
390c95df30
commit
bad67211ec
@ -2,64 +2,96 @@
|
||||
|
||||
import pygame
|
||||
import random
|
||||
import time
|
||||
|
||||
from cellular_automaton.cellular_automaton import CellularAutomaton
|
||||
from cellular_automaton.ca_cell import CACell
|
||||
from cellular_automaton.ca_grid import CAGrid
|
||||
from cellular_automaton.ca_rule import CARule
|
||||
from cellular_automaton.ca_rule import Rule
|
||||
from cellular_automaton.ca_neighborhood import MooreNeighborhood, EdgeRule
|
||||
|
||||
|
||||
class WorldGeneratorWindow:
|
||||
def __init__(self, windows_size):
|
||||
def __init__(self, windows_size: list, cellular_automaton: CellularAutomaton):
|
||||
self.window_size = windows_size
|
||||
self.grid_size = self.window_size.copy()
|
||||
self.grid_size[1] -= 20
|
||||
|
||||
pygame.init()
|
||||
pygame.display.set_caption("World Generator")
|
||||
pygame.display.set_mode(self.window_size)
|
||||
self.screen = pygame.display.set_mode(self.grid_size)
|
||||
|
||||
def display_cellular_automaton(self, cellular_automaton_instance):
|
||||
pass
|
||||
self._cellular_automaton = cellular_automaton
|
||||
self.font = pygame.font.SysFont("monospace", 15)
|
||||
|
||||
def set_cellular_automaton(self, cellular_automaton):
|
||||
self._cellular_automaton = cellular_automaton
|
||||
|
||||
def main():
|
||||
def _display_cellular_automaton(self):
|
||||
grid_dimension = self._cellular_automaton.grid.get_dimension()
|
||||
|
||||
cell_size = [x / y for x, y in zip(self.grid_size, grid_dimension)]
|
||||
|
||||
surfaces_to_update = []
|
||||
for cell in self._cellular_automaton.grid.get_active_cells().values():
|
||||
if not cell.dirty:
|
||||
continue
|
||||
cell_coordinate = cell.coordinate
|
||||
status = cell.get_status_for_iteration(self._cellular_automaton.get_iteration_index())
|
||||
if status is None:
|
||||
status = [0]
|
||||
red = 0
|
||||
if status[0] >= 10:
|
||||
red = 255
|
||||
cell_color = [red, 0, 0]
|
||||
surface_pos = [x * y for x, y in zip(cell_size, cell_coordinate)]
|
||||
surface_pos[1] += 20
|
||||
surfaces_to_update.append(self.screen.fill(cell_color, (surface_pos, cell_size)))
|
||||
pygame.display.update(surfaces_to_update)
|
||||
|
||||
def main_loop(self):
|
||||
running = True
|
||||
pygame.init()
|
||||
pygame.display.set_caption("minimal program")
|
||||
|
||||
screen = pygame.display.set_mode((1000, 730))
|
||||
image = pygame.image.load("../images/map.png")
|
||||
screen.blit(image, (0, 0))
|
||||
screen.set_at((50, 60), [50, 0, 0])
|
||||
screen.set_at((50, 61), [50, 0, 0])
|
||||
screen.set_at((51, 60), [50, 0, 0])
|
||||
screen.set_at((51, 61), [50, 0, 0])
|
||||
pygame.display.flip()
|
||||
|
||||
while running:
|
||||
for x in range(0, 1000):
|
||||
for y in range(0, 700):
|
||||
screen.set_at((x, y), [random.randrange(0, 255), random.randrange(0, 255), random.randrange(0, 255)])
|
||||
pygame.display.flip()
|
||||
time_ca_start = time.time()
|
||||
self._cellular_automaton.evolve()
|
||||
time_ca_end = time.time()
|
||||
self._display_cellular_automaton()
|
||||
time_ds_end = time.time()
|
||||
self._print_process_duration(time_ca_end, time_ca_start, time_ds_end)
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
|
||||
def _print_process_duration(self, time_ca_end, time_ca_start, time_ds_end):
|
||||
self.screen.fill([0, 0, 0], ((0, 0), (self.window_size[0], 30)))
|
||||
self._write_text((10, 5), "CA: " + "{0:.4f}".format(time_ca_end - time_ca_start) + "s")
|
||||
self._write_text((310, 5), "Display: " + "{0:.4f}".format(time_ds_end - time_ca_end) + "s")
|
||||
|
||||
class TestRule(CARule):
|
||||
def evolve_cell(self, cell, neighbors):
|
||||
if neighbors[1][0] != 0:
|
||||
return [1]
|
||||
else:
|
||||
return [0]
|
||||
def _write_text(self, pos, text, color=(0, 255, 0)):
|
||||
label = self.font.render(text, 1, color)
|
||||
update_rect = self.screen.blit(label, pos)
|
||||
pygame.display.update(update_rect)
|
||||
|
||||
|
||||
class TestRule(Rule):
|
||||
def evolve_cell(self, cell, neighbors, iteration_index):
|
||||
last_iteration = iteration_index - 1
|
||||
if cell.get_status_for_iteration(last_iteration) is None:
|
||||
rand = random.randrange(0, 101, 1)
|
||||
if rand <= 99:
|
||||
rand = 0
|
||||
cell.set_status_for_iteration([rand], iteration_index)
|
||||
cell.set_status_for_iteration([rand], iteration_index + 1)
|
||||
if rand != 0:
|
||||
cell.dirty = True
|
||||
elif len(neighbors) == 8:
|
||||
left_neighbour_status = neighbors[3].get_status_for_iteration(last_iteration)
|
||||
cell.set_status_for_iteration(left_neighbour_status, iteration_index)
|
||||
return cell.dirty
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
dim = [200, 500]
|
||||
ca = CellularAutomaton(2)
|
||||
|
||||
new_grid = CAGrid(dim)
|
||||
new_grid.set_cell_by_coordinate([1, 1], CACell([1]))
|
||||
rule = TestRule()
|
||||
ca = CellularAutomaton([500, 500], MooreNeighborhood(EdgeRule.IGNORE_EDGE_CELLS), rule, thread_count=1)
|
||||
ca_window = WorldGeneratorWindow([1000, 730], ca)
|
||||
ca_window.main_loop()
|
||||
|
@ -1,7 +1,26 @@
|
||||
class Cell:
|
||||
def __init__(self, name: str):
|
||||
def __init__(self, name: str, coordinate: list):
|
||||
self.name = name
|
||||
self.coordinate = coordinate
|
||||
self.neighbours = []
|
||||
self._status = [None, None]
|
||||
self.dirty = False
|
||||
|
||||
def set_neighbours(self, neighbours: list):
|
||||
self.neighbours = neighbours
|
||||
|
||||
def set_status_for_iteration(self, new_status, iteration):
|
||||
""" Will set the new status for Iteration.
|
||||
:param new_status: The new status to set.
|
||||
:param iteration: Uses the iteration index, to differ between current and next state.
|
||||
"""
|
||||
self._status[iteration % 2] = new_status
|
||||
|
||||
self.dirty = self._status[0] != self._status[1]
|
||||
|
||||
def get_status_for_iteration(self, iteration):
|
||||
""" Will return the status for the iteration.
|
||||
:param iteration: Uses the iteration index, to differ between current and next state.
|
||||
:return The status for this iteration.
|
||||
"""
|
||||
return self._status[iteration % 2]
|
||||
|
@ -1,9 +1,11 @@
|
||||
import sys
|
||||
|
||||
from cellular_automaton.ca_cell import Cell
|
||||
from cellular_automaton.ca_neighborhood import CellularAutomatonNeighborhood
|
||||
from cellular_automaton.ca_neighborhood import Neighborhood
|
||||
|
||||
|
||||
class Grid:
|
||||
def __init__(self, dimension: list, neighborhood: CellularAutomatonNeighborhood):
|
||||
def __init__(self, dimension: list, neighborhood: Neighborhood):
|
||||
self._dimension = dimension
|
||||
self._cells = {}
|
||||
self._neighborhood = neighborhood
|
||||
@ -11,15 +13,22 @@ class Grid:
|
||||
self._create_cells()
|
||||
self._set_cell_neighbours()
|
||||
|
||||
self._active_cells = {}
|
||||
self.set_all_cells_active()
|
||||
self._active_cells = self._cells.copy()
|
||||
self._set_all_cells_active()
|
||||
|
||||
def set_all_cells_active(self):
|
||||
for cell_key in self._cells:
|
||||
self._active_cells[cell_key] = 1
|
||||
def get_names_of_active_cells(self):
|
||||
return list(self._active_cells.keys())
|
||||
|
||||
def get_active_cells(self):
|
||||
return self._active_cells.keys()
|
||||
return self._active_cells
|
||||
|
||||
def clear_active_cells(self):
|
||||
self._active_cells.clear()
|
||||
|
||||
def set_cell_and_neighbours_active(self, cell_info: list):
|
||||
self._active_cells[cell_info[0].name] = cell_info[0]
|
||||
for neighbour in cell_info[1]:
|
||||
self._active_cells[neighbour.name] = neighbour
|
||||
|
||||
def get_cell_and_neighbors(self, cell_name):
|
||||
cell = self._cells[cell_name]
|
||||
@ -30,6 +39,15 @@ class Grid:
|
||||
|
||||
return [cell, neighbour_objects]
|
||||
|
||||
def get_cell_by_coordinate(self, coordinate):
|
||||
return self._cells[_join_coordinate(coordinate)]
|
||||
|
||||
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.
|
||||
@ -37,13 +55,13 @@ class Grid:
|
||||
:param coordinate: The coordinate generated so far.
|
||||
(each recursion adds one dimension to the coordinate.
|
||||
"""
|
||||
coordinate = self.instantiate_coordinate_if_necessary(coordinate)
|
||||
coordinate = _instantiate_coordinate_if_necessary(coordinate)
|
||||
|
||||
try:
|
||||
self._recursive_step_down_dimensions(coordinate, dimension_index, self._create_cells)
|
||||
except IndexError:
|
||||
coordinate_string = '-'.join(coordinate)
|
||||
self._cells[coordinate_string] = Cell(coordinate_string)
|
||||
coordinate_string = _join_coordinate(coordinate)
|
||||
self._cells[coordinate_string] = Cell(coordinate_string, coordinate)
|
||||
|
||||
def _recursive_step_down_dimensions(self, coordinate, dimension_index, recursion_method):
|
||||
""" For the range of the current dimension, recalls the recursion method.
|
||||
@ -52,14 +70,8 @@ class Grid:
|
||||
:param recursion_method: The method to call for recursion.
|
||||
"""
|
||||
for cell_index in range(self._dimension[dimension_index]):
|
||||
coordinate.append(cell_index)
|
||||
recursion_method(dimension_index + 1, coordinate.copy())
|
||||
|
||||
@staticmethod
|
||||
def instantiate_coordinate_if_necessary(coordinate):
|
||||
if coordinate is None:
|
||||
coordinate = []
|
||||
return coordinate
|
||||
new_cod = coordinate + [cell_index]
|
||||
recursion_method(dimension_index + 1, new_cod)
|
||||
|
||||
def _set_cell_neighbours(self, dimension_index=0, coordinate=None):
|
||||
""" Recursively steps down the dimensions to get the string instances for each cells neighbours.
|
||||
@ -67,12 +79,30 @@ class Grid:
|
||||
:param coordinate: The coordinate generated so far.
|
||||
(each recursion adds one dimension to the coordinate.
|
||||
"""
|
||||
coordinate = self.instantiate_coordinate_if_necessary(coordinate)
|
||||
coordinate = _instantiate_coordinate_if_necessary(coordinate)
|
||||
|
||||
try:
|
||||
self._recursive_step_down_dimensions(coordinate, dimension_index, self._set_cell_neighbours)
|
||||
self._recursive_step_down_dimensions(coordinate.copy(), dimension_index, self._set_cell_neighbours)
|
||||
except IndexError:
|
||||
neighbours_coordinates = self._neighborhood.get_neighbor_coordinates(coordinate, self._dimension)
|
||||
neighbour_names = [self._cells['-'.join(nc)].name for nc in neighbours_coordinates]
|
||||
self._cells['-'.join(coordinate)].set_neighbours(neighbour_names)
|
||||
neighbour_names = [self._cells[_join_coordinate(nc)].name for nc in neighbours_coordinates]
|
||||
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):
|
||||
if coordinate is None:
|
||||
coordinate = []
|
||||
return coordinate
|
||||
|
||||
|
||||
def _join_coordinate(coordinate):
|
||||
return '-'.join(str(x) for x in coordinate)
|
||||
|
||||
|
@ -7,7 +7,7 @@ class EdgeRule(Enum):
|
||||
FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS = 2
|
||||
|
||||
|
||||
class CellularAutomatonNeighborhood:
|
||||
class Neighborhood:
|
||||
def __init__(self, neighbors: list, edge_rule: EdgeRule):
|
||||
self._neighbors = neighbors
|
||||
self.edge_rule = edge_rule
|
||||
@ -18,7 +18,9 @@ class CellularAutomatonNeighborhood:
|
||||
|
||||
def get_neighbor_coordinates(self, cell_coordinate, dimensions):
|
||||
self.dimensions = dimensions
|
||||
if not self._does_ignore_edge_cell_rule_apply(cell_coordinate):
|
||||
if self._does_ignore_edge_cell_rule_apply(cell_coordinate):
|
||||
return []
|
||||
else:
|
||||
return self._apply_edge_rule_to_neighbours_of(cell_coordinate)
|
||||
|
||||
def _does_ignore_edge_cell_rule_apply(self, coordinate):
|
||||
@ -37,6 +39,7 @@ class CellularAutomatonNeighborhood:
|
||||
for neighbour in self._neighbors:
|
||||
if not self._does_ignore_edge_cell_neighbours_rule_apply(neighbour, cell_coordinate):
|
||||
remaining_neighbours.append(self._calculate_neighbour_coordinate(neighbour, cell_coordinate))
|
||||
return remaining_neighbours
|
||||
|
||||
def _does_ignore_edge_cell_neighbours_rule_apply(self, neighbour, cell_coordinate):
|
||||
if self.edge_rule == EdgeRule.IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS:
|
||||
@ -47,18 +50,20 @@ class CellularAutomatonNeighborhood:
|
||||
return False
|
||||
|
||||
def _calculate_neighbour_coordinate(self, neighbour, cell_coordinate):
|
||||
new_coordinate = []
|
||||
for rel_nd, cd, d in zip(neighbour, cell_coordinate, self.dimensions):
|
||||
nd = cd + rel_nd
|
||||
if nd < 0:
|
||||
nd = d - 1
|
||||
elif nd >= d:
|
||||
nd = 0
|
||||
return nd
|
||||
new_coordinate.append(nd)
|
||||
return new_coordinate
|
||||
|
||||
|
||||
class MooreNeighborhood(CellularAutomatonNeighborhood):
|
||||
class MooreNeighborhood(Neighborhood):
|
||||
def __init__(self, edge_rule: EdgeRule):
|
||||
super().__init__([[-1, -1], [0, -1], [1, -1],
|
||||
[-1, 0], [0, 0], [1, 0],
|
||||
[-1, 0], [1, 0],
|
||||
[-1, 1], [0, 1], [1, 1]],
|
||||
edge_rule)
|
||||
|
@ -1,9 +1,18 @@
|
||||
from cellular_automaton.ca_cell import Cell
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
class Rule:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def evolve_cell(self, cell: Cell, neighbours: list):
|
||||
pass
|
||||
@abstractmethod
|
||||
def evolve_cell(self, cell: Cell, neighbours: list, iteration_index: int):
|
||||
""" Calculates and sets new state of 'cell'.
|
||||
:param cell: The cell to calculate new state for.
|
||||
:param neighbours: The neighbour cells of this cell.
|
||||
:param iteration_index: The current iteration index, to choose the correct state.
|
||||
:return: True if state changed, False if not.
|
||||
A cells evolution will only be called if it or at least one of its neighbours has changed last iteration cycle.
|
||||
"""
|
||||
return False
|
||||
|
@ -3,13 +3,15 @@ import time
|
||||
|
||||
from cellular_automaton.ca_grid import Grid
|
||||
from cellular_automaton.ca_rule import Rule
|
||||
from cellular_automaton.ca_neighborhood import Neighborhood
|
||||
|
||||
|
||||
class CellularAutomaton:
|
||||
def __init__(self, dimension: list, rule_: Rule=None, thread_count: int=4):
|
||||
self.grid = Grid(dimension)
|
||||
def __init__(self, dimension: list, neighborhood: Neighborhood, rule_: Rule=None, thread_count: int=4):
|
||||
self.grid = Grid(dimension, neighborhood)
|
||||
self._rule = rule_
|
||||
self._thread_count = thread_count
|
||||
self._iteration = 0
|
||||
|
||||
def set_rule(self, rule: Rule):
|
||||
self._rule = rule
|
||||
@ -17,13 +19,28 @@ class CellularAutomaton:
|
||||
def set_thread_count(self, thread_count: int):
|
||||
self._thread_count = thread_count
|
||||
|
||||
def get_iteration_index(self):
|
||||
return self._iteration
|
||||
|
||||
def evolve(self):
|
||||
if self._all_cells_are_inactive():
|
||||
return True
|
||||
else:
|
||||
self._iteration += 1
|
||||
self._delegate_evolve_to_threads()
|
||||
return False
|
||||
|
||||
def _delegate_evolve_to_threads(self):
|
||||
cell_lists_for_threats = self.create_cell_lists_for_threads()
|
||||
self.grid.clear_active_cells()
|
||||
threads = self._start_treads_to_evolve_grid(cell_lists_for_threats)
|
||||
self._wait_for_all_threads_to_finish(threads)
|
||||
|
||||
def _all_cells_are_inactive(self):
|
||||
return len(self.grid.get_names_of_active_cells()) == 0
|
||||
|
||||
def create_cell_lists_for_threads(self):
|
||||
active_cells = self.grid.get_active_cells()
|
||||
active_cells = self.grid.get_names_of_active_cells()
|
||||
cell_count_per_thread = int(len(active_cells) / self._thread_count)
|
||||
return self.divide_active_cells(cell_count_per_thread, active_cells)
|
||||
|
||||
@ -35,7 +52,7 @@ class CellularAutomaton:
|
||||
def _start_treads_to_evolve_grid(self, cell_lists_for_threats):
|
||||
threads = []
|
||||
for t in range(self._thread_count):
|
||||
new_thread = _EvolutionThread(self.grid, self._rule, cell_lists_for_threats[t])
|
||||
new_thread = _EvolutionThread(self.grid, self._rule, cell_lists_for_threats[t], self._iteration)
|
||||
threads.append(new_thread)
|
||||
new_thread.start()
|
||||
return threads
|
||||
@ -50,17 +67,22 @@ class CellularAutomaton:
|
||||
|
||||
|
||||
class _EvolutionThread(threading.Thread):
|
||||
def __init__(self, grid: Grid, rule: Rule, cell_list: list):
|
||||
def __init__(self, grid: Grid, rule: Rule, cell_list: list, iteration: int):
|
||||
super(_EvolutionThread, self).__init__()
|
||||
self._grid = grid
|
||||
self._rule = rule
|
||||
self._cell_list = cell_list
|
||||
self._next_state = []
|
||||
self._finished = False
|
||||
self._iteration = iteration
|
||||
|
||||
def run(self):
|
||||
for cell in self._cell_list:
|
||||
self._rule.evolve_cell(*self._grid.get_cell_and_neighbors(cell))
|
||||
cell_info = self._grid.get_cell_and_neighbors(cell)
|
||||
active = self._rule.evolve_cell(cell_info[0], cell_info[1], self._iteration)
|
||||
|
||||
if active:
|
||||
self._grid.set_cell_and_neighbours_active(cell_info)
|
||||
self._finished = True
|
||||
|
||||
def get_new_cell_states(self):
|
||||
|
Loading…
Reference in New Issue
Block a user