diff --git a/.idea/WorldGeneration.iml b/.idea/WorldGeneration.iml
deleted file mode 100644
index 3ed66be..0000000
--- a/.idea/WorldGeneration.iml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index a2e120d..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 1cba53f..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index 157ab9c..0000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,405 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- get_coordinate_from_index
- print
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1540970314178
-
-
- 1540970314178
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/scripts/main_ui.py b/scripts/main_ui.py
index 193d0f9..68d6332 100644
--- a/scripts/main_ui.py
+++ b/scripts/main_ui.py
@@ -3,7 +3,10 @@
import pygame
import random
-from cellular_automaton import cellular_automaton
+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
class WorldGeneratorWindow:
@@ -44,5 +47,19 @@ def main():
running = False
+class TestRule(CARule):
+ def evolve_cell(self, cell, neighbors):
+ if neighbors[1][0] != 0:
+ return [1]
+ else:
+ return [0]
+
+
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()
diff --git a/src/cellular_automaton/ca_cell.py b/src/cellular_automaton/ca_cell.py
index 5b3a544..854a95d 100644
--- a/src/cellular_automaton/ca_cell.py
+++ b/src/cellular_automaton/ca_cell.py
@@ -1,19 +1,7 @@
-class CACell:
- def __init__(self, initial_state=None):
- if initial_state:
- assert isinstance(initial_state, (tuple, list))
- self._state = initial_state
- else:
- self._state = [0]
+class Cell:
+ def __init__(self, name: str):
+ self.name = name
+ self.neighbours = []
- def __getitem__(self, index):
- return self._state[index]
-
- def __setitem__(self, index, value):
- self._state[index] = value
-
- def __len__(self):
- return len(self._state)
-
- def __str__(self):
- return str(self._state)
+ def set_neighbours(self, neighbours: list):
+ self.neighbours = neighbours
diff --git a/src/cellular_automaton/ca_grid.py b/src/cellular_automaton/ca_grid.py
index 3ffe2dd..cc0bd77 100644
--- a/src/cellular_automaton/ca_grid.py
+++ b/src/cellular_automaton/ca_grid.py
@@ -1,124 +1,78 @@
-from cellular_automaton.ca_cell import CACell
-from functools import reduce
+from cellular_automaton.ca_cell import Cell
+from cellular_automaton.ca_neighborhood import CellularAutomatonNeighborhood
-class CAGrid:
- def __init__(self, dimensions, initial_grid_state=None):
- assert isinstance(dimensions, list)
- assert len(dimensions) > 0
- self._dimensions = dimensions
+class Grid:
+ def __init__(self, dimension: list, neighborhood: CellularAutomatonNeighborhood):
+ self._dimension = dimension
+ self._cells = {}
+ self._neighborhood = neighborhood
- self._cell_count = reduce(lambda x, y: x*y, dimensions)
+ self._create_cells()
+ self._set_cell_neighbours()
- if initial_grid_state:
- assert isinstance(initial_grid_state, list)
- assert len(initial_grid_state) == self._cell_count
- assert isinstance(initial_grid_state[0], CACell)
- self._grid = initial_grid_state
- else:
- self._grid = []
+ self._active_cells = {}
+ self.set_all_cells_active()
- for i in range(self._cell_count):
- self._grid.append(CACell())
+ def set_all_cells_active(self):
+ for cell_key in self._cells:
+ self._active_cells[cell_key] = 1
- def get_index_from_coordinate(self, coordinate):
- """ Convert a coordinate to the index in the grid list.
- :param coordinate: A tuple or list with the position of the cell.
- Has to have the same dimension as the grid.
- :return: The index of the cell at the coordinates
+ def get_active_cells(self):
+ return self._active_cells.keys()
+
+ def get_cell_and_neighbors(self, cell_name):
+ cell = self._cells[cell_name]
+ neighbours = cell.neighbours
+ neighbour_objects = []
+ for ne in neighbours:
+ neighbour_objects.append(self._cells[ne])
+
+ return [cell, neighbour_objects]
+
+
+ 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.
+ :param coordinate: The coordinate generated so far.
+ (each recursion adds one dimension to the coordinate.
"""
- assert len(self._dimensions) == len(coordinate)
- index = 0
- for i, c in enumerate(coordinate[1:]):
- index += c * reduce(lambda x, y: x * y, self._dimensions[:i+1])
- index += coordinate[0]
- return index
+ coordinate = self.instantiate_coordinate_if_necessary(coordinate)
- def get_coordinate_from_index(self, index):
- """ Convert an index to the coordinate in the grid list.
- :param index: The Index of the cell in the grid.
- :return: The coordinate pointing at the indexed cell in the grid.
+ 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)
+
+ def _recursive_step_down_dimensions(self, coordinate, dimension_index, recursion_method):
+ """ For the range of the current dimension, recalls the recursion method.
+ :param coordinate: The coordinate so far.
+ :param dimension_index: The current dimension lvl.
+ :param recursion_method: The method to call for recursion.
"""
- coordinate = len(self._dimensions)*[0]
- for i, d in enumerate(self._dimensions):
- coordinate[-(i + 1)] = index // reduce(lambda x, y: x * y, self._dimensions[-(i + 1):])
- index = index % reduce(lambda x, y: x * y, self._dimensions[-(i + 1):])
+ for cell_index in range(self._dimension[dimension_index]):
+ coordinate.append(cell_index)
+ recursion_method(dimension_index + 1, coordinate.copy())
- coordinate[0] = index
+ @staticmethod
+ def instantiate_coordinate_if_necessary(coordinate):
+ if coordinate is None:
+ coordinate = []
return coordinate
- def get_cell_by_coordinate(self, coordinate):
- """ Read a cell using a list or tuple as reference
- :param coordinate A tuple or list with the position of the cell.
- Has to have the same dimension as the grid.
- :return: The CACell at the coordinate in the grid.
+ def _set_cell_neighbours(self, dimension_index=0, coordinate=None):
+ """ Recursively steps down the dimensions to get the string instances for each cells neighbours.
+ :param dimension_index: The index indicating which dimension is currently traversed.
+ :param coordinate: The coordinate generated so far.
+ (each recursion adds one dimension to the coordinate.
"""
+ coordinate = self.instantiate_coordinate_if_necessary(coordinate)
+
try:
- return self[self.get_index_from_coordinate(coordinate)]
+ self._recursive_step_down_dimensions(coordinate, dimension_index, self._set_cell_neighbours)
except IndexError:
- return None
+ 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)
- def get_all_neighbour_cells(self, position, neighborhood):
- """ Get a list with all cells defined by the neighborhood.
- :param position: The position as index or coordinate.
- :param neighborhood: The neighborhood definition as tuple.
- :return: All Cells defined by the neighborhood in a list.
- """
- if isinstance(position, (tuple, list)):
- coordinate = position[:]
- else:
- coordinate = self.get_coordinate_from_index(position)
-
- neighbors = []
-
- for neighbor in neighborhood:
- neighbor_coordinate = []
- for i, (c, nc) in enumerate(zip(coordinate, neighbor)):
- coord = c + nc
- if coord < 0:
- coord = self._dimensions[i] - 1
- elif coord == self._dimensions[i]:
- coord = 0
-
- neighbor_coordinate.append(coord)
-
- index_ = self.get_cell_by_coordinate(neighbor_coordinate)
- if index_:
- neighbors.append(index_)
-
- return neighbors
-
- def get_dimensions(self):
- return self._dimensions
-
- def set_cell_by_coordinate(self, coordinate, value):
- """ Write to a cell using a list or tuple as reference
- :param coordinate A tuple or list with the position of the cell.
- Has to have the same dimension as the grid.
- """
- try:
- self._grid[self.get_index_from_coordinate(coordinate)] = value
- except IndexError:
- return None
-
- def __eq__(self, other):
- if len(self._grid) != len(other):
- return False
-
- for i in self._cell_count:
- if self._grid[i] != other[i]:
- return False
-
- return True
-
- def __len__(self):
- return len(self._grid)
-
- def __getitem__(self, index):
- return self._grid[int(index)]
-
- def __setitem__(self, index, value):
- self._grid[index] = value
-
- def __str__(self):
- return str(self._grid)
diff --git a/src/cellular_automaton/ca_neighberhood.py b/src/cellular_automaton/ca_neighberhood.py
deleted file mode 100644
index 3b1b0dd..0000000
--- a/src/cellular_automaton/ca_neighberhood.py
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-class Neighborhood:
- MOOR_2_X_2 = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [0, 1], [1, -1], [1, 0], [1, 1]]
-
- def __init__(self):
- pass
diff --git a/src/cellular_automaton/ca_neighborhood.py b/src/cellular_automaton/ca_neighborhood.py
new file mode 100644
index 0000000..c156b2e
--- /dev/null
+++ b/src/cellular_automaton/ca_neighborhood.py
@@ -0,0 +1,64 @@
+from enum import Enum
+
+
+class EdgeRule(Enum):
+ IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS = 0
+ IGNORE_EDGE_CELLS = 1
+ FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS = 2
+
+
+class CellularAutomatonNeighborhood:
+ def __init__(self, neighbors: list, edge_rule: EdgeRule):
+ self._neighbors = neighbors
+ self.edge_rule = edge_rule
+ self.dimensions = []
+
+ def get_relative_neighbor_coordinates(self):
+ return self._neighbors
+
+ def get_neighbor_coordinates(self, cell_coordinate, dimensions):
+ self.dimensions = dimensions
+ if not self._does_ignore_edge_cell_rule_apply(cell_coordinate):
+ return self._apply_edge_rule_to_neighbours_of(cell_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):
+ return True
+ return False
+
+ def _is_coordinate_on_an_edge(self, coordinate):
+ for nd, d in zip(coordinate, self.dimensions):
+ if nd == 0 or nd == d - 1:
+ return True
+ return False
+
+ def _apply_edge_rule_to_neighbours_of(self, cell_coordinate):
+ remaining_neighbours = []
+ 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))
+
+ def _does_ignore_edge_cell_neighbours_rule_apply(self, neighbour, cell_coordinate):
+ if self.edge_rule == EdgeRule.IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS:
+ for rel_nd, cd, d in zip(neighbour, cell_coordinate, self.dimensions):
+ nd = cd + rel_nd
+ if nd < 0 or nd >= d:
+ return True
+ return False
+
+ def _calculate_neighbour_coordinate(self, neighbour, cell_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
+
+
+class MooreNeighborhood(CellularAutomatonNeighborhood):
+ def __init__(self, edge_rule: EdgeRule):
+ super().__init__([[-1, -1], [0, -1], [1, -1],
+ [-1, 0], [0, 0], [1, 0],
+ [-1, 1], [0, 1], [1, 1]],
+ edge_rule)
diff --git a/src/cellular_automaton/ca_rule.py b/src/cellular_automaton/ca_rule.py
index c36856a..6853d76 100644
--- a/src/cellular_automaton/ca_rule.py
+++ b/src/cellular_automaton/ca_rule.py
@@ -1,13 +1,9 @@
-import abc
+from cellular_automaton.ca_cell import Cell
-class CARule(abc.ABC):
- @abc.abstractmethod
- def evolve_cell(self, cell, neighbors):
- """
- Evolves a cell and returns its new state as list of states.
- :param cell: The cell to evolve.
- :param neighbors: A list of its neighbors.
- :return: The new state list.
- """
+class Rule:
+ def __init__(self):
+ pass
+
+ def evolve_cell(self, cell: Cell, neighbours: list):
pass
diff --git a/src/cellular_automaton/cellular_automaton.py b/src/cellular_automaton/cellular_automaton.py
index e69aaf8..5689d8f 100644
--- a/src/cellular_automaton/cellular_automaton.py
+++ b/src/cellular_automaton/cellular_automaton.py
@@ -1,50 +1,66 @@
import threading
import time
-from cellular_automaton.ca_cell import CACell
-from cellular_automaton.ca_grid import CAGrid
+from cellular_automaton.ca_grid import Grid
+from cellular_automaton.ca_rule import Rule
class CellularAutomaton:
- def __init__(self, threads=1):
- assert threads > 0
- self._thread_count = threads
+ def __init__(self, dimension: list, rule_: Rule=None, thread_count: int=4):
+ self.grid = Grid(dimension)
+ self._rule = rule_
+ self._thread_count=thread_count
- def evolve(self, grid, neighborhood, rule):
- range_ = int(len(grid) / self._thread_count)
+ def set_rule(self, rule: Rule):
+ self._rule = rule
+ def set_thread_count(self, thread_count: int):
+ self._thread_count = thread_count
+
+ def evolve(self):
+ cell_lists_for_threats = self.create_cell_lists_for_threads()
+ threads = self._start_treads_to_evolve_grid(cell_lists_for_threats)
+ self._wait_for_all_threads_to_finish(threads)
+
+ def create_cell_lists_for_threads(self):
+ active_cells = self.grid.get_active_cells()
+ cell_count_per_thread = int(len(active_cells) / self._thread_count)
+ return self.divide_active_cells(cell_count_per_thread, active_cells)
+
+ @staticmethod
+ def divide_active_cells(cell_count_per_thread, active_cells):
+ 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, cell_lists_for_threats):
threads = []
for t in range(self._thread_count):
- new_thread = EvolutionThread(grid, neighborhood, rule, [t * range_, t * range_ + range_])
+ new_thread = _EvolutionThread(self.grid, self._rule, cell_lists_for_threats[t])
threads.append(new_thread)
new_thread.start()
+ return threads
- new_grid_state = []
+ @staticmethod
+ def _wait_for_all_threads_to_finish(threads):
for thread in threads:
while not thread.is_finished():
time.sleep(0.01)
- new_grid_state.extend(thread.get_new_cell_states())
thread.join()
- return CAGrid(grid.get_dimensions(), new_grid_state)
-
-class EvolutionThread(threading.Thread):
- def __init__(self, grid, neighborhood, rule, range_):
- super(EvolutionThread, self).__init__()
+class _EvolutionThread(threading.Thread):
+ def __init__(self, grid: Grid, rule: Rule, cell_list: list):
+ super(_EvolutionThread, self).__init__()
self._grid = grid
- self._neighborhood = neighborhood
self._rule = rule
- self._range = range_
+ self._cell_list = cell_list
self._next_state = []
self._finished = False
def run(self):
- for cell_id in range(*self._range):
- neighbors = self._grid.get_all_neighbour_cells(cell_id, self._neighborhood)
- cell = self._grid[cell_id]
- self._next_state.append(CACell(self._rule.evolve_cell(cell, neighbors)))
+ for cell in self._cell_list:
+ self._rule.evolve_cell(*self._grid.get_cell_and_neighbors(cell))
self._finished = True
def get_new_cell_states(self):
diff --git a/src/world_generator.py b/src/world_generator.py
deleted file mode 100644
index 73bfad7..0000000
--- a/src/world_generator.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/usr/bin/env python3
-
-import tkinter
-
-from cellular_automaton.cellular_automaton import CellularAutomaton
-from cellular_automaton.ca_neighberhood import Neighborhood
-from cellular_automaton.ca_rule import CARule
-from cellular_automaton.ca_grid import CAGrid
-from cellular_automaton.ca_cell import CACell
-
-import datetime
-import pygame
-
-UPDATE_RATE = 10
-
-
-class TkGUI:
- def __init__(self):
- self.root = tkinter.Tk()
-
- self.canvas = tkinter.Canvas(self.root)
- self.canvas.pack(padx=5, pady=5)
-
- self.ca = None
- self.current_state = None
- self.dimensions = None
- self.rule_ = None
-
- self.grid_image = None
-
- def run(self):
- self.root.after(10, self.update_state())
- self.root.mainloop()
-
- def update_state(self):
- a = datetime.datetime.now()
- self.current_state = self.ca.evolve(self.current_state, Neighborhood.MOOR_2_X_2, self.rule_)
- b = datetime.datetime.now()
- self.draw_current_state()
- c = datetime.datetime.now()
- print(b-a)
- print(c-b)
- self.root.after(UPDATE_RATE, self.update_state)
-
- def draw_current_state(self):
- for idx in range(self.dimensions[0]):
- for idy in range(self.dimensions[1]):
- cell = self.current_state[idy * self.dimensions[0] + idx]
- if cell[0] == 0:
- cell_color = "#ffffff"
- else:
- cell_color = "#444444"
-
- self.grid_image.put(cell_color, (idx, idy))
-
- def start(self, ca_, grid, dimensions, rule_):
- self.grid_image = tkinter.PhotoImage(width=dimensions[0], height=dimensions[1])
- self.ca = ca_
- self.current_state = grid
- self.dimensions = dimensions
- self.rule_ = rule_
-
- # Clear the canvas (remove all shapes)
- self.canvas.delete(tkinter.ALL)
-
- self.draw_current_state()
- self.canvas.create_image(self.dimensions, image=self.grid_image, state="normal")
-
- # Draw the canvas
- self.draw_current_state()
-
-
-class TestRule(CARule):
- def evolve_cell(self, cell, neighbors):
- if neighbors[1][0] != 0:
- return [1]
- else:
- return [0]
-
-#if __name__ == '__main__':
-# gui = TkGUI()
-# dim = [200, 500]
-# ca = CellularAutomaton(2)
-#
-# new_grid = CAGrid(dim)
-# new_grid.set_cell_by_coordinate([1, 1], CACell([1]))
-# rule = TestRule()
-# gui.start(ca, new_grid, dim, rule)##
-#
-# gui.run()