import matplotlib
from matplotlib.animation import FuncAnimation as FA
from matplotlib.patches import Rectangle as RE
import matplotlib.pyplot as plt
class Grid:
#Initialize Generation 1.
def __init__(self, grid_dim):
self._grid_dim = grid_dim
self._s = int((self._grid_dim - 1) / 2)
self._squares = [[0 for x in range(self._grid_dim)] for y in range(self._grid_dim)]
self._squares[self._s][self._s] = 1
self._squares[self._s - 1][self._s] = 1
self._squares[self._s][self._s + 1] = 1
self._squares[self._s + 1][self._s] = 1
self._squares[self._s][self._s - 1] = 1
#Height/width of grid.
def get_grid_dim(self):
return self._grid_dim
def get_s(self):
return self._s
#Clear grid.
def clear(self):
self._squares = [[0 for x in range(self._grid_dim)] for y in range(self._grid_dim)]
#Clear a square.
def square_clear(self, row, col):
self._squares[row][col] = 0
#Fill a square.
def square_fill(self, row, col, g):
self._squares[row][col] = g
#Returns square value.
def square_value(self, row, col):
return self._squares[row][col]
#Eight neighbors of a square. Starting at 12 o'clock going clockwise.
def neighbors(self, row, col):
n = []
if row > 0 :
n.append((row - 1, col))
if (row > 0) and (col < self.get_grid_dim() - 1) :
n.append((row - 1, col + 1))
if col < self.get_grid_dim() - 1 :
n.append((row, col + 1))
if (row < self.get_grid_dim() - 1) and (col < self.get_grid_dim() - 1) :
n.append((row + 1, col + 1))
if row < self.get_grid_dim() - 1 :
n.append((row + 1, col))
if (row < self.get_grid_dim() - 1) and (col > 0) :
n.append((row + 1, col - 1))
if col > 0 :
n.append((row, col - 1))
if (row > 0) and (col > 0) :
n.append((row - 1, col - 1))
return n
#Update Generation.
def update_grid(self, g):
updated_grid = [[self.update_square(r, c, g) \
for c in range(self.get_grid_dim())] \
for r in range(self.get_grid_dim())]
for r in range(self.get_grid_dim()):
for c in range(self.get_grid_dim()):
if updated_grid[r][c] == 0:
self.square_clear(r, c)
else:
self.square_fill(r, c, updated_grid[r][c])
#Update each square in generation.
def update_square(self, r, c, g):
#If a square is empty, check neighbors, update.
if self.square_value(r, c) == 0:
neighbors = self.neighbors(r, c)
filled = 0
for neighbor in neighbors:
if self.square_value(neighbor[0], neighbor[1]) > 0 :
filled += 1
#An empty square has been updated in this generation.
if filled >= 3 :
return g
#An empty square remains empty in this generation.
else :
return 0
#A filled square remains filled.
else :
return self.square_value(r, c)
#Number of shaded squares, new square.
def shaded_squares(self, g) :
total = 0
new = 0
for r in range(self.get_grid_dim()):
for c in range(self.get_grid_dim()):
if self._squares[r][c] != 0:
total += 1
if self._squares[r][c] == g:
new += 1
return (g, new, total)
#Converts (row, col) to (x, y).
def rc_to_plot(self, row, col):
s = self.get_s()
return (row - s, col - s)
#Define size and number of frames.
def Simulation(s, f) :
k = s/2
_k = -1*k
#Plot Setup.
matplotlib.rc_file_defaults()
fig = plt.figure(figsize = (5, 5))
ax = fig.add_subplot(111, xlim = (_k, k), ylim = (_k, k))
ax.set_title("Generational Growth if 3 Neighbors Present", fontsize = 14)
#Remove axes and ticks.
for side in ['bottom', 'left', 'top', 'right']:
ax.spines[side].set_visible(False)
ax.set_xticks([])
ax.set_yticks([])
#Grid lines setup.
for t in [i-k for i in range(s + 1)]:
ax.plot([t, t], [_k, k], c = 'grey', ls = '-', lw = 1)
ax.plot([_k, k], [t, t], c = 'grey', ls = '-', lw = 1)
#Footnote font size.
font = {'size' : 10}
#Three artists:
#Generation text, Number text, Total text.
g_text = ax.text(-22, -24.5, '', **font)
n_text = ax.text(-7, -24.5, '', **font, color = 'limegreen')
t_text = ax.text(8, -24.5, '', **font, color = 'cornflowerblue')
def init():
g_text.set_text('')
n_text.set_text('')
t_text.set_text('')
return g_text, n_text, t_text
def animate(i):
#Patch Helper.
def makeRectangle(x, y, fc, a) :
ax.add_patch(RE(xy = (x - 0.5, y - 0.5),
width = 1,
height = 1,
fc = fc,
alpha = a))
#Get data from object.
if i > 0:
#Go to next generation.
run.update_grid(i+1)
#Get text data.
data = run.shaded_squares(i+1)
else :
data = run.shaded_squares(1)
#Shade.
#Interate through all squares.
for r in range(s) :
for c in range(s) :
if run.square_value(r, c) == 0:
continue
else :
x, y = run.rc_to_plot(r, c)
#If the square is in current generation, make it green.
if run.square_value(r, c) == i+1:
makeRectangle(x, y, 'limegreen', 1)
#Reset squares from previous generation. Then make blue.
elif run.square_value(r, c) == i:
makeRectangle(x, y, 'white', 1)
makeRectangle(x, y, 'cornflowerblue', 0.5)
#Update footnote text.
g_text.set_text("Generation: %.0f" % (data[0]))
n_text.set_text("New Squares: %.0f" % (data[1]))
t_text.set_text("Total Squares: %.0f" % (data[2]))
return g_text, n_text, t_text
#Run animation.
run = Grid(s)
anim = FA(fig, animate, init_func = init, frames = f, interval = 700, blit = True)
#Save animation.
anim.save('2022.01.28 Express.mp4');
Simulation(45, 44)