Search code examples
pythonooppygameconways-game-of-life

Conways Game of Life in PyGame


So I read about Conways Game of Life and tried to implement it with PyGame.

I tried to make it object-oriented. The way it works is that I have a list of cell-instances, which then check how many neighbours they have and then either stay alive or die, based on their neighbours. Then the process repeats itself.

The problem is that when I test it with some known starting patterns (e.g. in the code below (CELL_MAP)) it does not work the way it should.

I read the code over and over and I dont really get what I'm missing here. I posted the whole code below as I dont know where my mistake is, but I'd highly appreciate if someone would point me in the right direction.

Thanks in advance!

import pygame

class Cell:
    def __init__(self, live, xcor, ycor):
        self.alive = live
        self.x = xcor
        self.y = ycor
        self.neighbours = 0

def checkNeighbours(self, cellList):
    for cell in cellList:
        #left
        if cell.x == self.x-1 and cell.y == self.y and cell.alive == True:
            self.neighbours += 1        
        #right
        elif cell.x == self.x+1 and cell.y == self.y and cell.alive == True:
            self.neighbours += 1 
        #upleft
        elif cell.x == self.x-1 and cell.y == self.y-1 and cell.alive == True:
            self.neighbours += 1     
        #up
        elif cell.x == self.x and cell.y == self.y-1 and cell.alive == True:
            self.neighbours += 1 
        #upright
        elif cell.x == self.x+1 and cell.y == self.y-1 and cell.alive == True:
            self.neighbours += 1     
        #downleft
        elif cell.x == self.x-1 and cell.y == self.y+1 and cell.alive == True:
            self.neighbours += 1     
        #down
        elif cell.x == self.x and cell.y == self.y+1 and cell.alive == True:
            self.neighbours += 1 
        #downright
        elif cell.x == self.x+1 and cell.y == self.y+1 and cell.alive == True:
            self.neighbours += 1 

def breed(self):
    if self.alive == False and self.neighbours == 3:
        #dead cell ressurects if neighbours equals 3
        self.alive = True
    elif self.alive and self.neighbours < 2:
        #die from loneliness
        self.alive = False
    elif self.alive and self.neighbours == 2:
        #stay alive
        pass
    elif self.alive and self.neighbours == 3:
        #stay alive
        pass
    elif self.alive and self.neighbours > 3:
        #die from overpopulation
        self.alive = False

def render(self, display):
    if self.alive:
        pygame.draw.rect(display, (0,0,0), [self.x*10, self.y*10, 10, 10])
    elif self.alive == False:
        pygame.draw.rect(display, (0,0,255), [self.x*10, self.y*10, 10, 10])




WID = 33
HEI = 20            
CELL_MAP = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]

CELL_LIST = []

xc = -1
yc = -1
for yList in CELL_MAP:
    yc += 1
    for x in yList:
        xc += 1
        if x == 0:
            #create dead cell
            newCell = Cell(False, xc, yc)
            CELL_LIST.append(newCell)
        elif x == 1:
            #create alive cell
            newCell = Cell(True, xc, yc)
            CELL_LIST.append(newCell)
    xc = -1



#pygame init

pygame.init()
(width, height) = (WID*10, HEI*10)

pygame.display.set_caption('Game of Life')

screen = pygame.display.set_mode((width, height))

#game loop

def gameLoop():
    gameLoop = True 

    while gameLoop:
        #check for exit
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                gameLoop = False
                pygame.quit()               

        #render cells
        for cell in CELL_LIST:
            cell.render(screen)    

        #check neighbours
        for cell in CELL_LIST:
            cell.checkNeighbours(CELL_LIST)

        pygame.display.flip()

        #breed
        for cell in CELL_LIST:
            cell.breed()

        pygame.time.wait(5)

    quit()

if __name__ == "__main__":
    gameLoop()

Solution

  • I don't have pygame installed, so I can't run your code. However, the bug that's causing your error is that you don't reset a cell's neighbour count to zero after you've determined whether it'll be alive or dead in the next generation. So in each generation each cell's new neighbour count gets added to the previous accumulated neighbour count. You should probably do that resetting in the .breed method.

    Here's a more compact version of that method:

    def breed(self):
        self.alive = self.neighbours == 3 or self.alive and self.neighbours == 2
        self.neighbours = 0
    

    I have a few more comments about your code.

    Your checkNeighbours method is extremely inefficient: for each cell, it scans the entire grid looking for a cell's neighbours! A simple alternative is to store your cells in a 2D list so you can quickly locate a cell's neighbours.


    Here's a more compact way to build your CELL_LIST than what your code currently does:

    CELL_LIST = []
    for y, row in enumerate(CELL_MAP):
        for x, v in enumerate(row):
            CELL_LIST.append(Cell(v == 1, x, y))
    

    Here's the same thing as a list comprehension:

    CELL_LIST = [Cell(bool(v), x, y)
        for y, row in enumerate(CELL_MAP)
            for x, v in enumerate(row)
    ]
    

    But as I said earlier, it's probably a good idea to make CELL_LIST a 2D list:

    cell_list = [[Cell(bool(v), x, y) for x, v in enumerate(row)]
        for y, row in enumerate(CELL_MAP)]
    

    Your CELL_MAP isn't a convenient way to put Life patterns into your program, but I guess it's ok for testing purposes. Take a look at this answer I wrote earlier this month for an alternative.

    Eventually, you should give your program the ability to read the common RLE format used by many Life programs.

    You may also like to check out this moderately efficient version I wrote that uses Numpy: numpy_life.py. Like the other version I linked it displays the output in the Linux terminal, but both versions should be easy to adapt to pygame or another GUI framework.