Search code examples
pythonpython-3.xconways-game-of-life

Game of life neighbours issue


I'm trying to create my Game of life code in python, but when it checks for the living neighbours, it seems to have a problem. I've been tried to solve it for 2 days but I still don't know what I'm missing. Here is the code broken up into pieces.

from random import randrange
from tkinter import *
from time import sleep
root = Tk()
canv = Canvas(width = 800, height = 800)
canv.pack()

def value():
    if randrange(16) == 0:
        return 1
    else:
        return 0 

I set up the canvas in tkinter and create a function which determines the state of a cell, so every cell has 1/x chance at being alive first.

def pprint(lst):
    for row in lst:
        print(row)

I created a pretty-print function when I tried to debug.

def nb(life,row,col):
    count = 0
    #print(x,y)
    #print(life[x][y])
    for r in [row-1, row, row+1]:
        for c in [col-1,col,col+1]:
            if life[r][c] == 1:
                count += 1
    if life[row][col] == 1:
        count -= 1
    #print(count,end=" ")
    return count

This function it supposed to return the number of living neighbours a cell has. (notes remained from debugging all over the code)

def ud(life,life2,size,box):        
    #print(life)        
    while True:
        try:
            for row in range(1,size+1):
                for col in range(1,size+1):
                    #print(row,col)
                    a = nb(life,row,col)
                    print("[{0},{1}]".format(row,col), end =" ")
                    print(a, end=" ")
                    
                    if ((1<a) and (a<4)):
                        life2[row][col] = 1
                        canv.itemconfig(box[row-1][col-1], fill ="black")
                                                    
                    else:
                        life2[row][col] = 0
                        canv.itemconfig(box[row-1][col-1], fill ="white")
                print("")                         
            print("ok")
            #print(life2[19][19])
            root.update()
            pprint(life)
            #sleep(5)

            life = life2
            sleep(800/size**2)
            print("")
        except:
            break

This is the main updating function. It's supposed to run the nb function for every cell in the life 2d array and the determine it's value in life2. "box" is a 2d array, which contains the rectangle tkinter id for every cell on the visual grid. (Yes, print functions are also for debugging.)

#######
size = 10
w = 10
box = [[canv.create_rectangle(y*w,x*w,y*w+10,x*w+10) for y in range(size)] for x in range(size)]
life = [[value() for y in range(size)] for x in range(size)]
life2 = [[0 for y in range(size+2)] for x in range(size+2)]
for i in range(1,size+1):
    life2[i] = [0] + life[i-1] + [0]
#life = life2

pprint(life)
root.update()
ud(life2,life2,size,box)

del box
del life
del life2

I create the box variable (w = width of a cell) and generate the random living cells. Then I create a "0" frame for "life" (so the program doesn't break on the nb checking when it's at the corners. And I know I could've use try/except). Then I launch the ud function with the parameters (size = number of cells in a row; both life-matrix arguments are life2, because the 2nd will be completely rewritten in theory)

So what is the problem with the program?


Solution

  • This line life = life2 inside def ud(life,life2,size,box): makes it so that life and live2 both name/point to the same data. You need to deep copy life2 and let life point to that absolutely seperate data.

    Deep copy, because your life2 contains other lists and if you only shallow copy by doing live = life2[:] you would still have the same problem - live would then have unique refs to list inside of itself, but the data the refs of the inner lists point to would still be shared. You use import copy & copy.deepcopy(...) for deep copying.


    Renaming:

    import copy
    print()
    a = [[1,3,5],[2,4,6]]
    b = a  # second name, same data
    a[0] = [0,8,15]    # replaces the ref on a[0] with a new one, b == a 
    a[1][2] = 99       # modifies a value inside the 2nd list ref, b == a
    print (a,"=>",b)   # b is just another name for a
    

    Output:

    # nothing got preserved, both identical as b ist just another name for a
    ([[0, 8, 15], [2, 4, 99]], '=>', [[0, 8, 15], [2, 4, 99]])
    

    Shallow Copy:

    a = [[1,3,5],[2,4,6]]
    b = a[:]  # shallow copy
    a[0] = [0,8,15]       # replaces one ref with a new one (only in a)
    a[1][2] = 99          # modifies the value inside 2nd list, same ref in a&b
    print (a,"=>",b)
    

    Output:

    # b got "unique" refs to the inner lists, if we replace one in a 
    # this does not reflect in b as its ref is unique. chaning values 
    # inside a inner list, will be reflected, as they are not uniqe
    ([[0, 8, 15], [2, 4, 99]], '=>', [[1, 3, 5], [2, 4, 99]])
    

    Deep Copy:

    a = [[1,3,5],[2,4,6]]
    b = copy.deepcopy(a)  # deep copy
    a[0] = [0,8,15]       # only a modified
    a[1][2] = 99          # only in a modified
    print (a,"=>",b)
    

    Output:

    # completely independent - b shares no refs with former a, all got new'ed up
    ([[0, 8, 15], [2, 4, 99]], '=>', [[1, 3, 5], [2, 4, 6]])
    

    If you replace the print's above with

    print(id(a), id(a[0]), id(a[1]), id(b), id(b[0]), id(b[1]))
    

    you get the output of the unique IDs of a and it's contend as well as of b and its content:

    rename:

    `a`: 139873593089632, 139873593146976, 139873593072384 
    `b`: 139873593089632, 139873593146976, 139873593072384
    

    shallow:

    `a`: 139873593229040, 139873593089632, 139873593146040
    `b`: 139873593227168, 139873593088552, 139873593146040
    

    deep:

    `a`: 139873593253968, 139873593227168, 139873593072384
    `b`: 139873593229040, 139873593089632, 139873593254112