I tried to make Game of Life by John Conway in pygame. Not sure exactly what went wrong
Question:
The cells don’t behave like they’re supposed to, meaning they don’t follow the rules. I’ve tried everything I could think of, but it doesn’t work.
The variable "state" is used to store the current states of all the cells (1 if active 0 if not) in the format state[x][y], similarly, "cells" also follow the same format but instead of 1s and 0s, it stores all the sprites, aka cells. The "new_state" is exactly like the state but it stores the states for the next generation. (The next generation is calculated from "state" and stored in "new_state".)
Heres an example of what "state" might look like:
state = [[0,0,0,0,0,0,0,0,0],[0,1,1,1,0,0,0], . . . ]
To calculate how many active neighbors there are next to a cell, I just did a nested for loop, both with range(-1, 2). If i, j are the variables of the for loops, then that would look something like this:
for i in range(-1, 2):
for j in range(-1, 2):
sum += state[x + i][y + j]
sum -= state[x][y]
Anyways, heres the code:
import pygame as pg
pg.init()
ticking = False
# colours
white = (255, 255, 255)
grey = (100, 100, 100)
blue = (0, 0, 255)
black = (0, 0, 0)
drk_blue = (0, 0, 100)
red = (255, 0, 0)
# now the screen
width = 750
height = 500
main_screen = pg.display.set_mode([width, height])
main_screen_rect = main_screen.get_rect()
game_width = width - width // 5
main_screen.fill(black)
game_screen = pg.Surface([game_width, height])
game_screen_rect = game_screen.get_rect()
game_screen.fill(black)
WH_cells = [0, 42]
for x in range(0, game_width, 12):
WH_cells[0] += 1
a = False # This is for toggling the eraser for the cells
# The state says which cells are active and inactive and cells is just a list containing the sprites
state = []
cells = []
new_state = []
# New state is for updating, i.e., it contains the states of the next generation
# imp functions
def logic():
sum_calc()
drawer()
global state, new_state
state = new_state
new_state = blank
def sum_calc():
global new_state
state_len = len(state)
state_len_part = len(state[0])
for x_c in range(1, state_len - 1):
for y_c in range(1, state_len_part - 1):
neigh_sum = 0
for i in range(-1, 2):
for j in range(-1, 2):
if x_c + i < state_len and y_c + j < len(state[x_c + i]):
neigh_sum += state[x_c + i][y_c + j]
neigh_sum -= state[x_c][y_c]
if neigh_sum < 2 or neigh_sum > 3:
new_state[x_c][y_c] = 0
elif neigh_sum == 3:
new_state[x_c][y_c] = 1
def drawer():
state_len = len(new_state)
state_len_part = len(new_state[0])
for x in range(state_len):
for y in range(state_len_part):
if new_state[x][y] != 1:
cells[x][y].Activate(False)
else:
cells[x][y].Activate(True)
# sprites
class Cell(pg.sprite.Sprite):
def __init__(self):
super(Cell, self).__init__()
self.surf = pg.Surface((10, 10))
self.surf.fill(grey)
self.rect = self.surf.get_rect()
self.index = None
def update(self, mouse_pos, eraser):
if self.rect.collidepoint(mouse_pos[0], mouse_pos[1]):
cell_x = self.index[0]
cell_y = self.index[1]
global state
if not eraser:
self.surf.fill(white)
state[cell_x][cell_y] = 1
else: # if eraser
self.surf.fill(grey)
state[cell_x][cell_y] = 0
def Activate(self, yesno):
global new_state
cell_x = self.index[0]
cell_y = self.index[1]
if yesno:
self.surf.fill(white)
new_state[cell_x][cell_y] = 1
else:
self.surf.fill(grey)
new_state[cell_x][cell_y] = 0
all_sprites = pg.sprite.Group()
running = True
# generating the cells and lists
for x in range(0, game_width, 12):
state.append([])
cells.append([])
for y in range(0, height, 12):
x_coord = int(x / 12)
state[x_coord].append(0)
new_cell = Cell()
new_cell.rect.x = x
new_cell.rect.y = y
new_cell.index = (x_coord, int(y / 12))
cells[x_coord].append(new_cell)
all_sprites.add(new_cell)
game_screen.blit(new_cell.surf, (x, y))
sprite_list = all_sprites.sprites()
sprite_list_len = len(sprite_list)
new_state = state
blank = state
while running:
if ticking:
logic()
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
if event.type == pg.KEYDOWN:
if event.key == pg.K_a:
a = not a
if event.key == pg.K_SPACE:
if ticking:
ticking = not ticking
else:
ticking = True
print("ticking toggled to: ", ticking)
if event.type == pg.MOUSEMOTION or pg.mouse.get_pressed()[0]:
if pg.mouse.get_pressed()[0]:
for sprites in all_sprites:
sprites.update(pg.mouse.get_pos(), a)
for sprites in sprite_list:
game_screen.blit(sprites.surf, (sprites.rect.x, sprites.rect.y))
main_screen.blit(game_screen, (0, 0))
pg.display.update(main_screen_rect)
fps = pg.time.Clock()
fps.tick(60)
Answers:
An assignment operation like new_state = state
doesn’t generate a new object. After this expression, you have 2 variables that refer to the same object.
Actually you have just 1 grid of states. The variables state
, new_state
and blank
refer to the same object. You must create a new and empty state grid in each frame:
def logic():
global state, new_state
new_state = [[0 for _ in row] for row in state]
sum_calc()
drawer()
state = new_state
Furthermore, your algorithm is not correct. See Conway’s Game of Life. Change the lgoic:
def sum_calc():
global new_state
state_len = len(state)
state_len_part = len(state[0])
for x_c in range(1, state_len - 1):
for y_c in range(1, state_len_part - 1):
neigh_sum = 0
for i in range(-1, 2):
for j in range(-1, 2):
if x_c + i < state_len and y_c + j < len(state[x_c + i]):
neigh_sum += state[x_c + i][y_c + j]
neigh_sum -= state[x_c][y_c]
#if neigh_sum < 2 or neigh_sum > 3:
# new_state[x_c][y_c] = 0
#elif neigh_sum == 3:
# new_state[x_c][y_c] = 1
if state[x_c][y_c] == 1 and (neigh_sum == 2 or neigh_sum == 3):
new_state[x_c][y_c] = 1
elif state[x_c][y_c] == 0 and neigh_sum == 3:
new_state[x_c][y_c] = 1
else:
new_state[x_c][y_c] = 0
The cells don’t behave like they’re supposed to, meaning they don’t follow the rules. I’ve tried everything I could think of, but it doesn’t work.
The variable "state" is used to store the current states of all the cells (1 if active 0 if not) in the format state[x][y], similarly, "cells" also follow the same format but instead of 1s and 0s, it stores all the sprites, aka cells. The "new_state" is exactly like the state but it stores the states for the next generation. (The next generation is calculated from "state" and stored in "new_state".)
Heres an example of what "state" might look like:
state = [[0,0,0,0,0,0,0,0,0],[0,1,1,1,0,0,0], . . . ]
To calculate how many active neighbors there are next to a cell, I just did a nested for loop, both with range(-1, 2). If i, j are the variables of the for loops, then that would look something like this:
for i in range(-1, 2):
for j in range(-1, 2):
sum += state[x + i][y + j]
sum -= state[x][y]
Anyways, heres the code:
import pygame as pg
pg.init()
ticking = False
# colours
white = (255, 255, 255)
grey = (100, 100, 100)
blue = (0, 0, 255)
black = (0, 0, 0)
drk_blue = (0, 0, 100)
red = (255, 0, 0)
# now the screen
width = 750
height = 500
main_screen = pg.display.set_mode([width, height])
main_screen_rect = main_screen.get_rect()
game_width = width - width // 5
main_screen.fill(black)
game_screen = pg.Surface([game_width, height])
game_screen_rect = game_screen.get_rect()
game_screen.fill(black)
WH_cells = [0, 42]
for x in range(0, game_width, 12):
WH_cells[0] += 1
a = False # This is for toggling the eraser for the cells
# The state says which cells are active and inactive and cells is just a list containing the sprites
state = []
cells = []
new_state = []
# New state is for updating, i.e., it contains the states of the next generation
# imp functions
def logic():
sum_calc()
drawer()
global state, new_state
state = new_state
new_state = blank
def sum_calc():
global new_state
state_len = len(state)
state_len_part = len(state[0])
for x_c in range(1, state_len - 1):
for y_c in range(1, state_len_part - 1):
neigh_sum = 0
for i in range(-1, 2):
for j in range(-1, 2):
if x_c + i < state_len and y_c + j < len(state[x_c + i]):
neigh_sum += state[x_c + i][y_c + j]
neigh_sum -= state[x_c][y_c]
if neigh_sum < 2 or neigh_sum > 3:
new_state[x_c][y_c] = 0
elif neigh_sum == 3:
new_state[x_c][y_c] = 1
def drawer():
state_len = len(new_state)
state_len_part = len(new_state[0])
for x in range(state_len):
for y in range(state_len_part):
if new_state[x][y] != 1:
cells[x][y].Activate(False)
else:
cells[x][y].Activate(True)
# sprites
class Cell(pg.sprite.Sprite):
def __init__(self):
super(Cell, self).__init__()
self.surf = pg.Surface((10, 10))
self.surf.fill(grey)
self.rect = self.surf.get_rect()
self.index = None
def update(self, mouse_pos, eraser):
if self.rect.collidepoint(mouse_pos[0], mouse_pos[1]):
cell_x = self.index[0]
cell_y = self.index[1]
global state
if not eraser:
self.surf.fill(white)
state[cell_x][cell_y] = 1
else: # if eraser
self.surf.fill(grey)
state[cell_x][cell_y] = 0
def Activate(self, yesno):
global new_state
cell_x = self.index[0]
cell_y = self.index[1]
if yesno:
self.surf.fill(white)
new_state[cell_x][cell_y] = 1
else:
self.surf.fill(grey)
new_state[cell_x][cell_y] = 0
all_sprites = pg.sprite.Group()
running = True
# generating the cells and lists
for x in range(0, game_width, 12):
state.append([])
cells.append([])
for y in range(0, height, 12):
x_coord = int(x / 12)
state[x_coord].append(0)
new_cell = Cell()
new_cell.rect.x = x
new_cell.rect.y = y
new_cell.index = (x_coord, int(y / 12))
cells[x_coord].append(new_cell)
all_sprites.add(new_cell)
game_screen.blit(new_cell.surf, (x, y))
sprite_list = all_sprites.sprites()
sprite_list_len = len(sprite_list)
new_state = state
blank = state
while running:
if ticking:
logic()
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
if event.type == pg.KEYDOWN:
if event.key == pg.K_a:
a = not a
if event.key == pg.K_SPACE:
if ticking:
ticking = not ticking
else:
ticking = True
print("ticking toggled to: ", ticking)
if event.type == pg.MOUSEMOTION or pg.mouse.get_pressed()[0]:
if pg.mouse.get_pressed()[0]:
for sprites in all_sprites:
sprites.update(pg.mouse.get_pos(), a)
for sprites in sprite_list:
game_screen.blit(sprites.surf, (sprites.rect.x, sprites.rect.y))
main_screen.blit(game_screen, (0, 0))
pg.display.update(main_screen_rect)
fps = pg.time.Clock()
fps.tick(60)
An assignment operation like new_state = state
doesn’t generate a new object. After this expression, you have 2 variables that refer to the same object.
Actually you have just 1 grid of states. The variables state
, new_state
and blank
refer to the same object. You must create a new and empty state grid in each frame:
def logic():
global state, new_state
new_state = [[0 for _ in row] for row in state]
sum_calc()
drawer()
state = new_state
Furthermore, your algorithm is not correct. See Conway’s Game of Life. Change the lgoic:
def sum_calc():
global new_state
state_len = len(state)
state_len_part = len(state[0])
for x_c in range(1, state_len - 1):
for y_c in range(1, state_len_part - 1):
neigh_sum = 0
for i in range(-1, 2):
for j in range(-1, 2):
if x_c + i < state_len and y_c + j < len(state[x_c + i]):
neigh_sum += state[x_c + i][y_c + j]
neigh_sum -= state[x_c][y_c]
#if neigh_sum < 2 or neigh_sum > 3:
# new_state[x_c][y_c] = 0
#elif neigh_sum == 3:
# new_state[x_c][y_c] = 1
if state[x_c][y_c] == 1 and (neigh_sum == 2 or neigh_sum == 3):
new_state[x_c][y_c] = 1
elif state[x_c][y_c] == 0 and neigh_sum == 3:
new_state[x_c][y_c] = 1
else:
new_state[x_c][y_c] = 0