Reputation: 13
I have been trying to write my own version of Conway's Game of Life as practice for Python using Pygame. I first wrote the functions for initializing the game field, and then calculating the next generation. I verified it's functionality using the console to print the results and verify that they returned the expected results (just 2 generations deep by hand on a 5x5 grid).
An important note of how I am calculating the neighbors... Instead of doing a for loop through the entire array and doing for loops to count for each cell, I have implemented an array that holds the neighbor counts. Only making changes when a cells status is changed. This means I don't waste time calculating neighbors for cells that have not changed.
When it came time to use Pygame to display the array with rectangles, I wrote the following program. At first I was drawing the screen by filling in the entire screen white, and then drawing the live cells as black (this can be done by commenting out the else statement in update()). I expected this to work as normal, but when I ran the program all I ended up with was the screen filling in black.
I was perplexed by the result so i drew white rectangles for the unpopulated cells (using the else statement. And got a better looking result, but instead of the cells eventually all dying, they eventually multiplied across the whole screen. This is opposite of what I expected, as I was expecting it to eventually stabilize.
Anyone know what I am doing wrong? I know that this is not the best way of writing this program, I welcome comments of how I can make it better.
- RETURN = run simulation
- 'R' = randomize
- 'T' = tick one generation
- 'C' = clear game field
- 'N' = display neighbor map
import pygame
from pygame.locals import *
import numpy as np
from random import *
import copy
fieldSize = [100,50]
cellSize = 10 # size of >10 is recommended to see neighbor count
windowSize = [fieldSize[0]*cellSize, fieldSize[1]*cellSize]
# calculate the last cell in each axis so it is not done repeatedly
lastCell = [(fieldSize[0]-1), (fieldSize[1]-1)]
dX = float(windowSize[0])/float(fieldSize[0])
dY = float(windowSize[1])/float(fieldSize[1])
colorAlive = [0,125,0]
colorDead = [0, 0, 0]
# todo list
# 1. make cLife take in the field size
# 2. convert random functions to numpy.random.randint
class cLife():
def randomize(self):
self.neighbors = np.zeros(fieldSize)
# fill in the game field with random numbers
for x in range(fieldSize[0]):
for y in range(fieldSize[1]):
if(randint(0,99)<20):
self.gameField[x][y] = 1
self.updateNeighbors([x,y], True)
else:
self.gameField[x][y] = 0
def displayNeighbors(self, surface):
self.drawField(surface)
for x in range(fieldSize[0]):
for y in range(fieldSize[1]):
neighborCount=font.render(str(int(self.neighbors[x][y])), 1,(200,200,200))
surface.blit(neighborCount, (x*dX+dX/3, y*dY+dY/3.5))
pygame.display.flip()
# This is the function to update the neighbor map, the game field is torroidal so the complexity is greatly
# increased. I have handcoded each instruction to avoid countless if statements and for loops.
# Hopefully, this has drastically improved the performance. Using this method also allows me to avoid calculating
# the neighbor map for every single cell because the neighbor map is updated only for the cells affected by a change.
def updateNeighbors(self, pos, status):
if(status == True):
change = 1
else:
change = -1
# testing for the cells in the center of the field (most cells are in the center so this is first)
# cells are filled in starting on the top-left corner going clockwise
if((pos[0]>0 and pos[0]<lastCell[0])and(pos[1]>0 and pos[1]<lastCell[1])):
self.neighbors[pos[0]-1][pos[1]-1] += change
self.neighbors[pos[0]][pos[1]-1] += change
self.neighbors[pos[0]+1][pos[1]-1] += change
self.neighbors[pos[0]+1][pos[1]] += change
self.neighbors[pos[0]+1][pos[1]+1] += change
self.neighbors[pos[0]][pos[1]+1] += change
self.neighbors[pos[0]-1][pos[1]+1] += change
self.neighbors[pos[0]-1][pos[1]] += change
elif(pos[0] == 0): # left edge
if(pos[1] == 0): # top left corner
self.neighbors[lastCell[0]][lastCell[1]] += change
self.neighbors[0][lastCell[1]] += change
self.neighbors[1][lastCell[1]] += change
self.neighbors[1][0] += change
self.neighbors[1][1] += change
self.neighbors[0][1] += change
self.neighbors[lastCell[0]][1] += change
self.neighbors[lastCell[0]][0] += change
elif(pos[1] == lastCell[1]): # bottom left corner
self.neighbors[lastCell[0]][pos[1]-1] += change
self.neighbors[0][pos[1]-1] += change
self.neighbors[1][pos[1]-1] += change
self.neighbors[1][pos[1]] += change
self.neighbors[1][0] += change
self.neighbors[0][0] += change
self.neighbors[lastCell[0]][0] += change
self.neighbors[lastCell[0]][pos[1]] += change
else: # everything else
self.neighbors[lastCell[0]][pos[1]-1] += change
self.neighbors[0][pos[1]-1] += change
self.neighbors[1][pos[1]-1] += change
self.neighbors[1][pos[1]] += change
self.neighbors[1][pos[1]+1] += change
self.neighbors[0][pos[1]+1] += change
self.neighbors[lastCell[0]][pos[1]+1] += change
self.neighbors[lastCell[0]][pos[1]] += change
elif(pos[0] == lastCell[0]): # right edge
if(pos[1] == 0): # top right corner
self.neighbors[pos[0]-1][lastCell[1]] += change
self.neighbors[pos[0]][lastCell[1]] += change
self.neighbors[0][lastCell[1]] += change
self.neighbors[0][0] += change
self.neighbors[0][1] += change
self.neighbors[pos[0]][1] += change
self.neighbors[pos[0]-1][1] += change
self.neighbors[pos[0]-1][0] += change
elif(pos[1] == lastCell[1]): # bottom right corner
self.neighbors[pos[0]-1][pos[1]-1] += change
self.neighbors[pos[0]][pos[1]-1] += change
self.neighbors[0][pos[1]-1] += change
self.neighbors[0][pos[1]] += change
self.neighbors[0][0] += change
self.neighbors[pos[0]][0] += change
self.neighbors[pos[0]-1][0] += change
self.neighbors[pos[0]-1][pos[1]] += change
else: # everything else
self.neighbors[pos[0]-1][pos[1]-1] += change
self.neighbors[pos[0]][pos[1]-1] += change
self.neighbors[0][pos[1]-1] += change
self.neighbors[0][pos[1]] += change
self.neighbors[0][pos[1]+1] += change
self.neighbors[pos[0]][pos[1]+1] += change
self.neighbors[pos[0]-1][pos[1]+1] += change
self.neighbors[pos[0]-1][pos[1]] += change
elif(pos[1] == 0): # top edge, corners already taken care of
self.neighbors[pos[0]-1][lastCell[1]] += change
self.neighbors[pos[0]][lastCell[1]] += change
self.neighbors[pos[0]+1][lastCell[1]] += change
self.neighbors[pos[0]+1][0] += change
self.neighbors[pos[0]+1][1] += change
self.neighbors[pos[0]][1] += change
self.neighbors[pos[0]-1][1] += change
self.neighbors[pos[0]-1][0] += change
elif(pos[1] == lastCell[1]): # bottom edge, corners already taken care of
self.neighbors[pos[0]-1][pos[1]-1] += change
self.neighbors[pos[0]][pos[1]-1] += change
self.neighbors[pos[0]+1][pos[1]-1] += change
self.neighbors[pos[0]+1][pos[1]] += change
self.neighbors[pos[0]+1][0] += change
self.neighbors[pos[0]][0] += change
self.neighbors[pos[0]-1][0] += change
self.neighbors[pos[0]-1][pos[1]] += change
def nextGeneration(self):
# copy the neighbor map, because changes will be made during the update
self.neighborsOld = copy.deepcopy(self.neighbors)
for x in range(fieldSize[0]):
for y in range(fieldSize[1]):
# Any live cell with fewer than two live neighbours dies, as if caused by under-population.
if(self.gameField[x][y] == 1 and self.neighborsOld[x][y] < 2):
self.gameField[x][y] = 0
self.updateNeighbors([x,y], False)
# Any live cell with more than three live neighbours dies, as if by overcrowding.
elif(self.gameField[x][y] == 1 and self.neighborsOld[x][y] >3):
self.gameField[x][y] = 0
self.updateNeighbors([x,y], False)
# Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
elif(self.gameField[x][y] == 0 and self.neighborsOld[x][y] == 3):
self.gameField[x][y] = 1
self.updateNeighbors([x,y], True)
def drawField(self, surface):
surface.fill(colorDead)
# loop through and draw each live cell
for x in range(fieldSize[0]):
for y in range(fieldSize[1]):
if(self.gameField[x][y] == 1):
pygame.draw.rect(surface, colorAlive, [dX*x, dY*y, dX, dY])
pygame.display.flip()
def __init__(self):
# initialize the game field and neighbor map with zeros
self.gameField = np.zeros(fieldSize)
self.neighbors = np.zeros(fieldSize)
# begining of the program
game = cLife()
pygame.init()
surface = pygame.display.set_mode(windowSize)
pygame.display.set_caption("Conway\'s Game of Life")
clock = pygame.time.Clock()
pygame.font.init()
font=pygame.font.Font(None,10)
surface.fill(colorDead)
game.randomize()
game.drawField(surface)
pygame.display.flip()
running = False
while True:
#clock.tick(60)
# handling events
for event in pygame.event.get():
if(event.type == pygame.MOUSEBUTTONDOWN):
mousePos = pygame.mouse.get_pos()
x = int(mousePos[0]/dX)
y = int(mousePos[1]/dY)
if(game.gameField[x][y] == 0):
game.gameField[x][y] = 1
game.updateNeighbors([x, y], True)
game.drawField(surface)
else:
game.gameField[x][y] = 0
game.updateNeighbors([x, y], False)
game.drawField(surface)
elif(event.type == pygame.QUIT):
pygame.quit()
elif(event.type == pygame.KEYDOWN):
# return key starts and stops the simulation
if(event.key == pygame.K_RETURN):
if(running == False):
running = True
else:
running = False
# 't' key ticks the simulation forward one generation
elif(event.key == pygame.K_t and running == False):
game.nextGeneration()
game.drawField(surface)
# 'r' randomizes the playfield
elif(event.key == pygame.K_r):
game.randomize()
game.drawField(surface)
# 'c' clears the game field
elif(event.key == pygame.K_c):
running = False
game.gameField = np.zeros(fieldSize)
game.neighbors = np.zeros(fieldSize)
game.drawField(surface)
# 'n' displays the neighbor map
elif(event.key == pygame.K_n):
game.displayNeighbors(surface)
if(running == True):
game.nextGeneration()
game.drawField(surface)
Upvotes: 0
Views: 897
Reputation: 39406
self.neighborsOld = self.neighbors
does not copy the map, it only points to it.
See :
a = [[1,2],[3,4]]
b = a
b[0][0] = 9
>>> a
[[9, 2], [3, 4]]
You need to either make a copy (a[:]
) for every row in a
, or use the copy
module and use deepcopy
:
b = [x[:] for x in a]
or
import copy
b = copy.deepcopy(a)
Either way, it results in
b[0][0] = 9
>>> a
[[1, 2], [3, 4]]
Upvotes: 1