Is there a way to prevent a program from checking values that are outside of a list's range?

68 Views Asked by At

So I'm working on an implementation of Conway's Game of Life in Python and I have most of my code written. For the most part it works; when I run it, the board appears and the cells are randomized, but there's one problem. I have a loop that checks the status of the 8 cells around each cell in the board, but the program comes to a complete stop at the first check, (the cell in the top-left corner), because 5 of the 8 cells are outside of the grid. I don't know how to limit this, or to make it check ONLY the cells INSIDE the grid. Does anyone have any idea on how I could approach this?

My board is organized in columns, (height) and number of columns (width). All of this is organized in a list of lists called currentCells and nextCells.

This is my code for checking living neighbors of each cell:

LivingNeighbors = 0 #set to zero so it can change based on the conditions
    
    for x in range( WIDTH ):
      for y in range( HEIGHT ):
        # Count number of living neighbors:
        aboveLeft = currentCells[x - 1] [y - 1]
        above = currentCells[x] [y - 1]
        aboveRight = currentCells[x + 1] [y - 1]
        left = currentCells[x - 1] [y]
        right = currentCells[x + 1] [y]
        bottomLeft = currentCells[x - 1] [y + 1]
        bottom = currentCells[x] [y + 1]
        bottomRight = currentCells[x + 1] [y + 1]
        if aboveLeft == '#':
          LivingNeighbors += 1
        if above == '#':
          LivingNeighbors += 1
        if aboveRight == '#':
          LivingNeighbors += 1
        if left == '#':
          LivingNeighbors += 1
        if right == '#':
          LivingNeighbors += 1
        if bottomLeft == '#':
          LivingNeighbors += 1
        if bottom == '#':
          LivingNeighbors += 1
        if bottomRight == '#':
          LivingNeighbors += 1
4

There are 4 best solutions below

0
Barmar On BEST ANSWER

You can use logical operators to check whether you're in range before trying to access the neighbor.

        aboveLeft = x > 0 and y > 0 and currentCells[x - 1] [y - 1]
        above = y > 0 and currentCells[x] [y - 1]
        aboveRight = x < len(currentCells)-1 and y > 0 and currentCells[x + 1] [y - 1]

and similarly for all the rest of the variables.

Even better would be to use a loop rather than 8 different variables.

for dx in (-1, 0, 1):
    for dy in (-1, 0, 1):
        if not (dx == 0 and dy == 0): # don't count the cell itself
            new_x = x + dx
            new_y = y + dy
            if 0 <= new_x < len(currentCells) and 0 <= new_y < len(currentCells[x]): # check if neighbor is in range
                livingNeighbors += currentCells[new_x][new_y] == '#'
0
John Gordon On

Check if a neighbor cell exists before trying to access it.

# check if this cell has an "above left" neighbor
if x-1 >= 0 and y-1 >= 0:
    # yes it does
    if currentCells[x - 1] [y - 1] == '#':
        LivingNeighbors += 1

If you like, you can combine all the checks into one statement:

if x-1 >= 0 and y-1 >= 0 and currentCells[x-1][y-1] == '#':

Or a different approach entirely would be to make an extra layer of cells all around the grid, and mark these somehow as "ghost" or "placeholder" cells.

0
Tim Roberts On

Any time you find yourself with repeated code segments like that, there is a better way:

for x in range( WIDTH ):
    for y in range( HEIGHT ):
        # Discount the center cell.
        neighbors = -1
        for dx in (-1,0,1):
            for dy in (-1,0,1):
                if x+dx in range(WIDTH) and \
                   y+dy in range(HEIGHT) and \
                   currentCells[x+dx][y+dy] == '#':
                    neighbors += 1

which leads to a comprehension:

for x in range( WIDTH ):
    for y in range( HEIGHT ):
        neighbors = sum(currentCells[x+dx][y+dy] == '#'
            for dx in (-1,0,1)
            for dy in (-1,0,1)
            if x+dx in range(WIDTH) and y+dy in range(HEIGHT)) - 1

It may not matter here, but you will usually find it more convenient to have the Y coordinates first in your array. That way currentCells[3] refers to the whole row, which is more intuitive. In fact, that way, you can print the grid by doing:

for row in currentCells:
    print(' '.join(row))
0
Kelly Bundy On

You could just add an empty column and an empty row (but don't increase WIDTH and HEIGHT), so the "out-of-bounds" accesses simply give empty cells.