I am trying to implement Conway's Game of Life:
The universe of the Game of Life is a [...] two-dimensional orthogonal grid of square cells, each of which is in one of two possible states, live or dead (or populated and unpopulated, respectively). Every cell interacts with its eight neighbours, which are the cells that are horizontally, vertically, or diagonally adjacent. At each step in time, the following transitions occur:
- Any live cell with fewer than two live neighbours dies, as if by underpopulation.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overpopulation.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
Given is a grid of 5 rows and 7 columns ("X" is alive, "-" is dead):
-------
-------
------X
---XXXX
------X
I need to calculate the next generation of this grid, which should be:
-------
-------
----X-X
----X-X
----X-X
But my code produces this output:
-------
-------
----X--
----X--
-------
I don't understand why this code is not giving the correct answer. What is wrong with the logic here?
Here is my code:
# #mark all places where new life will be born;
# mark all organisms which will die;
# remove all marked organisms;
# fill all marked empty cells with new organisms.
# Input data will contain 5 lines of 7 characters each. They represent a 5 by 7 fragment of the game field.
def convert(initial_state= ('''-------
-------
------X
---XXXX
------X''')): #convert input into a list, so it is easier to operate on
game_list = []
row_start = 0
row_end = 7
for _ in range(5):
row_cells = ''
for i in range(row_start,row_end):
row_cells += initial_state[i]
game_list.append(row_cells)
row_start += 8
row_end += 8
return(game_list)
def mark_cell():
#if cell has 0-1 or 3+ alive neighbours(X) - dies
#if 2 neighbours - survives
#if 3 neighbours - it becomes alive
result = '' #life after day 1 (after one neighbour check for all cells)
game_list = convert()
for r in range(len(game_list)):
result += '''
'''
for c in range(len(game_list[r])):
choosen_cell = game_list[r][c]
try:
born_count = 0
neighbours = [game_list[r-1][c-1],game_list[r-1][c],game_list[r-1][c+1],game_list[r][c-1],game_list[r][c+1],game_list[r+1][c-1],game_list[r+1][c],game_list[r+1][c+1]]
if r == 0 and c != 0 and c!= 6:
neighbours = neighbours[3:]
elif r == 4 and c != 0 and c!= 6:
neighbours = neighbours[:5]
elif c == 0 and r != 0 and r != 4:
del neighbours[5]
del neighbours[3]
del neighbours[0]
elif c == 6 and r != 0 and r != 4:
del neighbours[7]
del neighbours[4]
del neighbours[2]
elif r == 0 and c == 0:
neighbours = neighbours[3:]
del neighbours[2]
del neighbours[0]
elif r == 0 and c == 6:
neighbours = neighbours[3:]
del neighbours[4]
del neighbours[1]
elif r == 4 and c == 0:
neighbours = neighbours[:5]
del neighbours[3]
del neighbours[0]
elif r == 4 and c == 6:
neighbours = neighbours[:5]
del neighbours[4]
del neighbours[2]
for num in neighbours:
if num == "X":
born_count +=1
except IndexError:
pass
finally:
pass
if born_count < 2 or born_count > 3:
result += "-"
elif born_count == 3:
result += "X"
else:
result += choosen_cell
print (result)
return result
mark_cell()
The first function changes the multiline string input into a list with rows as its elements.
The second function checks the neighbour of each cell, counts alive neighbours and creates new multiline string, which represents the "board" state after one dead-alive check (or rather it was supposed to do so).
What is wrong with the logic here?
There are a few issues here:
When r
is 0, the access to [r-1]
will be to the last row. A cell in the first row does not have neighbors in the 5th row, so this is wrong. A similar thing happens with [c-1]
When r
is 4 (the last row), then the access to [r+1]
will trigger an exception, and the way this exception is handled means that born_count
will remain zero, so ignoring the fact there are some other neighbors that should have been counted.
You can fix these issues by filtering first the valid row coordinates, and for each of those, the valid column coordinates. Only when that is true, process that neighbor. You can use min
and max
functions to do such filtering on the coordinate.
Here is a correction of your mark_cell
function. A few other things were improved but are not essential to the correction:
def mark_cell():
result = ''
game_list = convert()
for r in range(len(game_list)):
result += '\n'
for c in range(len(game_list[r])):
choosen_cell = game_list[r][c]
born_count = 0
for rr in range(max(0, r-1), min(len(game_list), r+2)):
for cc in range(max(0, c-1), min(len(game_list[rr]), c+2)):
if (rr, cc) != (r, c) and game_list[rr][cc] == "X":
born_count += 1
if born_count == 3:
result += "X"
elif born_count == 2:
result += choosen_cell
else:
result += "-"
return result