Question

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)
Was it helpful?

Solution

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]]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top