Jump to content

Python code erasing variables for no reason

Go to solution Solved by geomac,

Python has a "fun" way of assigning references instead of actual values to variables at most times. Based on the code you presented, if all the cells in the <self.__snakeCells> list are references to the same object, it may being set to a `None` value at some point in your code -- If I'm reading it correctly probably in this line:

self.__snakeCells[-1].becomeEmpty()

The copy library is your friend, I've used <copy.copy(obj)> and <copy.deepcopy(obj)> to get around these kind of reference issues.

Hello all! thanks for coming to see if you can help. I have an issue where a line of my code which is just supposed to iterate over a list (which is also a class attribute) is actually setting any index it touches to None.

The background is that I'm trying to code a game of snake using starter code provided by my professor. The second iteration of the snake movement algorithm errors out becuase the for loop used to move the snake forward in the first iteration is actually just setting the snake cells to None, and None obviously doesn't have any of the attributes my code uses to navigate. The for loop only sets each cell to be the same as the cell ahead of it (first I reverse the list, then I go through in order overwriting each cell to be the next cell stopping before the last cell, set the last cell to be the new head location, then reverse the list again). The code I believe is relevant is below. I'll try to get a picture of the variable names/values in debug mode before/after overwrite to help.

class GameData:
    def __init__(self):
        # Dimensions of the board (in cells)
        self.__height = Preferences.NUM_CELLS_TALL
        self.__width = Preferences.NUM_CELLS_WIDE

        # Keep track of how many cells are empty and in the board
        self.__freeCells = self.__height * self.__width
        self.__totalCells = self.__height * self.__width

        # The current movement mode of the snake (i.e., the current
        # direction or in AI mode
        self.__currentMode = self.SnakeMode.GOING_EAST

        #A 2D array of cells in the board
        self.__board = self.createBoard()

        # A list of cells that currently contain food (from oldest to newest)
        self.__foodCells = [] 
        # A list of cells that contain the snake (from head to tail)
        self.__snakeCells = []

        # Whether or not the game is over
        self.__gameOver = False
        .
        .
        .
            #function to move snake forward. this is my code
    def slither(self, nextCell):
        #the next cell will become the new head, tail cell will become empty
        self.__snakeCells[-1].becomeEmpty()
        #reverse the list to work on it more intuitively
        self.__snakeCells.reverse()
        #the old head is now a body part
        self.__snakeCells[-1] = self.__snakeCells[-1].becomeBody()
        #move each body part up to the one in front of it, except for the last one
        for i in range(len(self.__snakeCells)-1): #this for loop is what is erasing data! ever cell it touches becomes None.
        	#none of the members of this list are None before the loop runs. They are all class:BoardCell
            self.__snakeCells[i] = self.__snakeCells[i+1]
        #set the last cell to the new head position
        self.__snakeCells[-1] = nextCell.becomeHead()
        #reverse the list again so its back to normal
        self.__snakeCells.reverse()

 

Ryzen 7 3700X

Aorus GTX 1080ti

G.Skill TridentZ 3200MHz 2x8GB

Corsair SFX 750W

Phanteks Evolve Shift Air (glass front)

2x Corsair Force GS 120GB SSD (RAID 0)

Link to comment
https://linustechtips.com/topic/1589183-python-code-erasing-variables-for-no-reason/
Share on other sites

Link to post
Share on other sites

Python has a "fun" way of assigning references instead of actual values to variables at most times. Based on the code you presented, if all the cells in the <self.__snakeCells> list are references to the same object, it may being set to a `None` value at some point in your code -- If I'm reading it correctly probably in this line:

self.__snakeCells[-1].becomeEmpty()

The copy library is your friend, I've used <copy.copy(obj)> and <copy.deepcopy(obj)> to get around these kind of reference issues.

Link to post
Share on other sites

It sounds and looks so complicated, but I guess your teacher wants to teach you all that object stuff,  with using BecomeBody, BecomeHead and so on ...

 

I'd just have an array that holds the coordinates of every cell of the snake, but use the first entry of the array to hold the number of cells. The coordinates can be stored as an unsigned integer, for 256 x 256 game areas, you could store coordinates as  location = (y*256) + x   , or you could go even up to location = (65536 * y ) + x

 

direction could be a number between 0 and 4  , left , up, right, down ... no need for constants like GOING_EAST ...  have an array  direction_change = ( -1,0 ) , (0,-1) , (1,0), (0,1)

 

So when you are about to move the snake, you get the x,y of the head which is always stored in the snake[1]

You determine where the snake head would be on next move, by adding the direction_change for your direction to the head coordinates.

You determine if the snake head can move there (ex see if it's a wall or not, or if the new coordinates are part of your snake body by scanning the array)

You determine if it's something that can be consumed or not (to increase length of snake)

If it's something that can be consumed, you just increase the array size by 1, you use a for from the last to the 2nd cell to copy over the the coordinates, put the new head coordinates in the position 1, and then update the snake length in array position 0

 

You can also use the same stuff to determine if the direction change can be done ... for example if user presses on the RIGHT key, but your snake was going left, and you don't want to allow flipping the snake over (which you can easily do by flipping the array in memory), then you can simply check if the new coordinates + direction_change

= (1,0) is  your 2nd snake cell (3rd position in array) and if so, refuse to go right.

 

 

 

Link to post
Share on other sites

2 hours ago, geomac said:

Python has a "fun" way of assigning references instead of actual values to variables at most times. Based on the code you presented, if all the cells in the <self.__snakeCells> list are references to the same object, it may being set to a `None` value at some point in your code -- If I'm reading it correctly probably in this line:

self.__snakeCells[-1].becomeEmpty()

The copy library is your friend, I've used <copy.copy(obj)> and <copy.deepcopy(obj)> to get around these kind of reference issues.

 

59 minutes ago, mariushm said:

It sounds and looks so complicated, but I guess your teacher wants to teach you all that object stuff,  with using BecomeBody, BecomeHead and so on ...

 

I'd just have an array that holds the coordinates of every cell of the snake, but use the first entry of the array to hold the number of cells. The coordinates can be stored as an unsigned integer, for 256 x 256 game areas, you could store coordinates as  location = (y*256) + x   , or you could go even up to location = (65536 * y ) + x

 

direction could be a number between 0 and 4  , left , up, right, down ... no need for constants like GOING_EAST ...  have an array  direction_change = ( -1,0 ) , (0,-1) , (1,0), (0,1)

 

So when you are about to move the snake, you get the x,y of the head which is always stored in the snake[1]

You determine where the snake head would be on next move, by adding the direction_change for your direction to the head coordinates.

You determine if the snake head can move there (ex see if it's a wall or not, or if the new coordinates are part of your snake body by scanning the array)

You determine if it's something that can be consumed or not (to increase length of snake)

If it's something that can be consumed, you just increase the array size by 1, you use a for from the last to the 2nd cell to copy over the the coordinates, put the new head coordinates in the position 1, and then update the snake length in array position 0

 

You can also use the same stuff to determine if the direction change can be done ... for example if user presses on the RIGHT key, but your snake was going left, and you don't want to allow flipping the snake over (which you can easily do by flipping the array in memory), then you can simply check if the new coordinates + direction_change

= (1,0) is  your 2nd snake cell (3rd position in array) and if so, refuse to go right.

 

 

 

 

Thanks for the replies guys! I was pretty tired while doing this, I completely forgot about how Python uses references and when it does in-place changes. I was suppsoed to be changing cell attributes using methods like .becomeHead() and .becomeEmpty() as literal statements. When I was writing self.__snakeCells[n] = nextCell.becomeHead() I was setting the cell to the output of .becomeHead(), which is None. Then that mistake would propogate within the for loop. It's working now, and I changed the graphic used for the snake's head to a picture of Solid Snake for good measure!

object = object.method()
#object gets set to output of its method (in this case None)

object.method()
#the Method performs its operations in place

 

Ryzen 7 3700X

Aorus GTX 1080ti

G.Skill TridentZ 3200MHz 2x8GB

Corsair SFX 750W

Phanteks Evolve Shift Air (glass front)

2x Corsair Force GS 120GB SSD (RAID 0)

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×