File size: 3,037 Bytes
ea2f505
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import numpy as np
from scipy import signal
from PIL import Image

from utils import generate_image_from_grid


class GameOfLife:

    def __init__(self):
        """Initialize the game.
        dim: dimensions of the board
        p: probability of a cell being dead at init
        seed: (optional) for reproducibility, set to None for a new random state
        init_state: (optional) a np.array grid to start the game with
        """
        self.kernel = [
            [1, 1, 1],
            [1, 0, 1],
            [1, 1, 1],
        ]

        self.kernel = np.ones((3, 3))
        self.kernel[1, 1] = 0
        self.game_history = []
        self.state = None
        self.step_counter = 0

    def set_random_state(self, dim=(100, 100), p=0.5, seed=None):
        if seed:
            np.random.seed(seed)
        self.state = (np.random.random(dim) < p).astype("int")
        self.game_history.append(self.state.copy())

    def set_empty_state(self, dim=(100, 100)):
        self.state = np.zeros(dim)
        self.game_history.append(self.state.copy())

    def set_state_from_array(self, array):
        self.state = array.copy()
        self.game_history.append(self.state.copy())

    def count_neighbors(self):
        """
        Count the number of live neighbors each cell in self.state has with convolutions.
        """
        self.neighbors = signal.convolve2d(
            self.state, self.kernel, boundary="fill", fillvalue=0, mode="same"
        ).astype("int")

    def place_blob(self, blob, i, j):
        """Place a blob at coordinates i,j
        blob: ndarray of zeros and ones
        i: int
        j: int
        """
        try:
            self.state[i : i + blob.shape[0], j : j + blob.shape[1]] = blob
        except:
            print("Check bounds of box vs size of game!")

    def step(self):
        """Update the game based on conway game of life rules"""
        # Count the number of neighbors via convolution
        self.count_neighbors()

        # Copy of initial state
        self.new_state = self.state

        # Rebirth if cell is dead and has three live neighbors
        self.new_state += np.logical_and(self.neighbors == 3, self.state == 0)

        # Death if cell has less than 2 neighbors
        self.new_state -= np.logical_and(self.neighbors < 2, self.state == 1)

        # Death if cell has more than 3 neighbors
        self.new_state -= np.logical_and(self.neighbors > 3, self.state == 1)

        # Update game state
        self.state = self.new_state

        # Save state to history
        self.game_history.append(self.state.copy())

        # Update step counter
        self.step_counter += 1

    def generate_n_steps(self, n):
        for _ in range(n):
            self.step()

            if np.array_equal(self.game_history[-1], self.game_history[-2]):
                # If the game is stable, break
                break

    def generate_image(self, grid: np.array, img_size: int = 512) -> Image:
        return generate_image_from_grid(grid, img_size)