Search code examples
pythonpygamesimulationcellular-automata

Error with a cellular-automata simulation in Python


I'm trying to build the famous "Game of Life" of Conway using Python and its library "pygame".

This is the Wikipedia page of the simulation: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life

Code: https://pastebin.com/kKGvshVK

(I post it also at the end of the question because the site told me i must put some code)

I created a long code that should do the following things in order:

  • Initialize pygame library and window;
  • The game is based on a 2D grid, composed by cell with the same size each;
  • Create a class "Cell" that includes the attributes and features of every single cell; Fundamental is the "alive" attribute that determine whether the cell is alive (so in the screen is black) or dead (white);
  • Create a function checkAlive that check wheter a cell from a list of cells is alive;
  • Create a second function neighbour that gives in output the number of neighbours (using the Moore neighbourhood theory), using checkAlive inside of it;
  • Cell initialization in a while loop;
  • Main loop with event handler and the algorithm that should update the screen following the rules of the game of life.

Now, some technical things. I created the functions that determined the number of neighbours because I didn't understand (for sure) a simple algorithm (The moore algorithm) that should help me made this game. So the things is, i made this function from scratch and it is very long, and at the beginning of it there are multiple exception i made in order to consider when the cell which will be analyzed to determine the number of neighbours it's placed on the border of the screen or on a corner, etc. So please excuse me if this code it's a little messy.

Now, the problem is, I think I've succeded at initialize the cells, but, the cell s doesn't update, also there is no error. I think that inside the main loop it's happening something that blocks the flow, because the print function that should print "Algorithm succesful" doesn't show up, so there is a bug that I didn't understand. That's all, thank you for any answer!

#Conway Game of Life

import pygame
import random


#PYGAME INITIALIZATION
success, failure = pygame.init()

screen_width = 800
screen_height = 600

screen = pygame.display.set_mode((screen_width, screen_height)) #Init the screen
time = pygame.time.Clock() #Time from startup
FPS = 5

#Screen Area = 480000 px (width * height)
#Area of a cell = 100px --> 4800 Cell

BLACK = (0, 0, 0)#Live cell
WHITE = (255, 255, 255)#dead cell


class Cell:
    """ x: x coordinate
        y: y coordinate
        size: width and height (same)
        alive: int (boolean, 0 o 1), to track the status of a cell (live or dead), at the startup is random
    """
    def __init__(self, x, y, alive):
        self.x = x
        self.y = y
        self.size = 10 #it's a square
        self.alive = alive
        if self.alive == 1:
            self.color = BLACK
        elif self.alive == 0:
            self.color = WHITE

#Function needed in the next function ------------------------------------------------
def checkAlive(cell, cellArray, curr_x, curr_y, counter):
    """ Check wheter the current cell near the original cell is alive. If it is alive it adds 1 to the counter
        cell: instance of the original cell
        cellArray: cell list with all the initialized cells
        curr_x: x coordinate of the cell which will be examined
        curr_y: y coordinate of the cell which will be examined
        counter: variable that is updated whenever a cell near to original has the "alive" attribute == 1
    """

    for current_cell in cellArray:
        if (current_cell.x == curr_x and current_cell.y == curr_y):
            if (current_cell.alive == 1):
                counter += 1


#Function to find the neighbours of a cell ---------------------------------------------------

def neighbour(cells, cell):
    """Give as output the number of neighbours of a cell (only neighbours with the alive attribute = 1)
        cells: List containing all the instances of the initialized cells
        cell: The single instance of cell which will be examined to determine the number of live neighbours
        return: number of live neighbours
    """
    num_neighbours = 0 #Number of near live cells(Moore neighbourhood)
    x = cell.x
    y = cell.y

    #List of exceptions before the main algorithm
    #Upper-left corner (x = 0, y = 0) !*!*!*!*!*!*!*!*!**!*!*!*!*!*!*!*
    if (x == 0 and y == 0):
        #Cell on the right -----------
        current_x = 1
        current_y = 0

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell below current ----------------------------------------
        current_x = 1
        current_y = 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell below original cell
        current_x = 0
        current_y = 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Return the number of neighbours
        return num_neighbours

    #Upper-right corner (x = window, y = 0)!*!*!*!*!**!*!*!*!*!*!*!*!*!*!*!*!*!*!**!*!*!*!*!*!*!*!
    elif (x == screen_width - cell.size and y == 0):
        #Cell below -------------------------------------
        current_x = screen_width - cell.size
        current_y = 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the left of current -----------------------------------
        current_x -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the left of original
        current_y = 0

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Return the number of neighbours
        return num_neighbours

    #Lower-left corner (x = 0, y = window) !*!*!*!**!*!!*!**!*!!**!*!*!*!*!*
    elif(x == 0 and y == (screen_height - cell.size)):

        #Cell over original ----------------------
        current_x = 0
        current_y = (screen_height - cell.size) - 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the right of current ------------------------------------------
        current_x += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell below current ---------------------------------------------
        current_y += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Return the number of neighbours
        return num_neighbours



    #Lower right corner !*!*!*!*!*!!*!*!*!*!*!*!**!!*!*!*
    elif (x == (screen_width - cell.size) and y == (screen_height - cell.size)):

        #Cell to the left of original ------------------------------------------------
        current_x = (screen_width - cell.size) - 1
        current_y = screen_height - cell.size

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell on top of current -------------------------------------------------------
        current_y -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the right of current
        current_x += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Return the number of neighbours
        return num_neighbours


    #If the cell is in the first row (y = 0) (2 corners excluded) !*!*!*!*!*!!*!!*!*!*!*!
    elif (y == 0 and (x != 0 and x != (screen_width - cell.size))):
        #Cell to the right of original
        current_x = x + 1
        current_y = 0

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell below current
        current_y += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell below original
        current_x = x

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the left of current
        current_x -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the left of original
        current_y -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Return the number of neighbours
        return num_neighbours


    #If the cell is in the last row (y = screen_height) 2 corners excluded !*!*!*!*!*!*!*!!*!*
    elif (y == (screen_height - cell.size) and (x != 0 and x != (screen_width - cell.size))):
        #Cell to the left of original
        current_x = x - 1
        current_y = y

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell on top of current
        current_y -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the right of current
        current_x += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the right of current
        current_x += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell below current
        current_y += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Return the number of neighbours
        return num_neighbours


    #If the cell is in the first column (2 corners excluded) !*!*!*!*!*!*!*!*!*!*!*!*
    elif (x == 0 and (y != 0 and y != (screen_height - cell.size))):
        #Cell on top of original
        current_x = x
        current_y = y - 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the right of current
        current_x += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell below current
        current_y += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell below current
        current_y += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the left of current
        current_x -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)


        return num_neighbours


    #If the cell is in the last column (x = screen width) !*!*!*!*!*!*!*!!**!!*
    elif (x == (screen_width - cell.size) and (y != 0 and y != (screen_height - cell.size))):
        #Cell below original
        current_x = x
        current_y = y + 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the left of current
        current_x -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell on top of current
        current_y -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell on top of current
        current_y -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the right of current
        current_x += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        return num_neighbours


    #GENERAL RULE
    else:
        #8 Neighbours
        #Cell on top of original
        current_x = x
        current_y = y - 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the right of original
        current_x += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell below current
        current_y += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell below current
        current_y += 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the left of current
        current_x -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell to the left of current
        current_x -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell on top of current
        current_y -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        #Cell on top of current
        current_y -= 1

        checkAlive(cell, cells, current_x, current_y, num_neighbours)

        return num_neighbours




#CELL INITIALIZATION
cell_array = []
#Useful variable in the for loop
x = 0
y = 0
init = False #Become true when Initialization is completed


#Initialization
while not init:

    is_alive = random.choices([0,1], weights = (95, 5), k=1)[0]#Randomly spawn cells with probability (Dead 95%, Alive 5 %)
    cell = Cell(x, y, is_alive)#Single object
    x += cell.size
    cell_array.append(cell)
    if x == screen_width: #End of a row
        x = 0
        y += cell.size
    if y == screen_height:#Last row
        init = True


#DRAWING CELLS
for cl in cell_array:
    pygame.draw.rect(screen, cl.color, pygame.Rect(cl.x, cl.y, cl.size, cl.size))#Draw any single cell

pygame.display.flip() #To update the screen

#Debug
print("Initialization Completed.")


done = False #Check whether the program should run

#Main loop
while not done:
    #FPS
    time.tick(FPS)

    #EVENT HANDLER
    for event in pygame.event.get():
        if event.type == pygame.QUIT: #Exit button
            print("Quitting.")
            done = True



    #SIMULATION --------------------------------------------------------------------

    #Run the algorithm of the game and update the screen (Moore algorithm)
    for cell in cell_array:
        if neighbour(cell_array, cell) in (2, 3): #2 or 3 live neighbours (survive)
            cell.alive = 1
        elif neighbour(cell_array, cell) < 2: #Few than 2 live neighbours (dies)
            cell.alive = 0
        elif neighbour(cell_array, cell) > 3: #More than 3 live neighbours (dies)
            cell.alive = 0
        elif ((cell.alive == 0) and (neighbour(cell_array, cell) == 3)): #Dead cell with 3 live neigh (live)
            cell.alive == 1

    #Debug
    print("Algorithm succesful.")

    #DRAWING CELLS
    for cl in cell_array:
        pygame.draw.rect(screen, cl.color, pygame.Rect(cl.x, cl.y, cl.size, cl.size))
    #Debug
    print("Cell loaded to the screen")

    pygame.display.flip() #To update the screen

Solution

  • There are a lot of bugs here. You should check step by step whether those functions are working. Few examples:

    1. checkAlive method should probably check something and return whether the current cell near the original cell is alive as you wrote. It doesn't return anything. Instead it changes the value of the counter which is not returned
    2. Probably you hope that counter variable is the same in the checkAlive function as in the neighbour function the variable num_neighbours - nope, they are different primitives.
    3. When you change the alive property - you don't set the color to the proper one, so the alive status changes, but the color doesn't change. You should create a setter for alive where you set the color.

    When you finish changing those issues, you might pass the code and we'll think what else is wrong.