Fidget Spinner
Fidget Spinner

Reputation: 21

Python/Pygame: List overwrites itself apparently

So I've recently started with python and I'm working on a simple 2D-minecraft-like game right now. I was trying to draw some images on a Surface using Pygame but something weird keeps happening to the (2D) Array that stores all the blocks in the world. I haven't done any random terrain generation stuff yet so I'm using this to make a completely flat world:


class World:
    def __init__(self, width, height, blockSprites):
        self.width = width
        self.height = height
        self.blockSprites = blockSprites

    def generate(self):
        temp = [self.air] * self.width
        self.map = [temp] * self.height
        for y in range(self.height):
            for x in range(self.width):
                if(y < 50):
                    self.map[y][x] = self.dirt

This is the Block class and the Camera class:

class Block:
    def __init__(self, blockid, textureX, textureY):
        self.textureX = textureX
        self.textureY = textureY
        self.solid = True
        self.id = blockid


from math import floor
class Camera:
    def __init__(self, worldWidth, worldHeight):
        self.x = 0
        self.xB = floor(self.x/64)
        self.y = 55*64 #test value for initial camera position
        self.yB = floor(self.y/64)
        self.zoom = 1
        self.baseWidth = 960
        self.baseWidthB = 15 #in blocks (1block = 64px)
        self.baseHeight = 640
        self.baseHeightB = 10

And here is the World.draw function which gets called every frame:

   def draw(self, canvas, camera):
        #canvas is the pygame surface which I draw on
        canvas.fill(pygame.Color(143, 218, 239))
        for y in range(camera.baseHeightB):
            for x in range(camera.baseWidthB):
                #id=0 means"air"
                if(not self.map[camera.yB + y][camera.xB + x].id == 0):
                    canvas.blit(self.blockSprites, (x*64, camera.baseHeight - (y+1)*64), (self.map[camera.yB + y][camera.xB + x].textureX, self.map[camera. yB + y][camera.xB + x].textureY, 64, 64))
        return canvas

The Problem is that world.map isn't half dirt blocks and half air blocks when I call world.draw()... It's all dirt blocks... I've tried adding

else:
    self.map[y][x] = self.air

to the end of world.generate and everything turned into air blocks... Can someone tell me why this is?

Upvotes: 1

Views: 87

Answers (1)

Rabbid76
Rabbid76

Reputation: 210998

Lets assume you've a class Item and 2 instances o of Item:

class Item:
   def  __init__(self, c):
        self.c = c

o = Item('o')
w, h = 3, 3

The output of

for r in map: print([c.c for c in r])

is

['o', 'o', 'o']
['o', 'o', 'o']
['o', 'o', 'o']

The statement temp = [o] * w create a list o f references to the same item o and the statement map = [temp] * h creates a list of references to the same list object temp.

temp = [o] * w
map = [temp] * h

At the end each element of map refers to the one and only list temp and each element of temp refers to the one and only object o. If the content of one inner element of the map is changed then magically all object seems to change. The reason is there is just one object o and all elements from map refer to the same object.

So the output of:

map[1][1].c = '_'
for r in map: print([c.c for c in r])

is

['_', '_', '_']
['_', '_', '_']
['_', '_', '_']

If a new instance of x of Item is created and a single element of map is changed

x = Item('x')
map[1][1] = x 
for r in map: print([c.c for c in r])

Then all the items of on row seems to change. Note, each element refers to the same list temp. There are not multiple rows, there is just one row object temp. If an element of temp is changed, then this appears to be changes in each row:

['_', 'x', '_']
['_', 'x', '_']
['_', 'x', '_']

You've to create a map where each element of the map is a separate instance of Item:

map = [[Item('o') for i in range(w)] for j in range(h)]

Note, Item('o') creates a new instance of Item.
Now the content of an element can be changed or a new object can be assigned to an inner element, because there is one separate object for each object in each row:

map[1][1].c = 'X'
map[1][0] = Item('x')
for r in map: print([c.c for c in r])
['o', 'o', 'o']
['x', 'X', 'o']
['o', 'o', 'o']

Upvotes: 1

Related Questions