Search code examples
pygamespritepygame-surface

Pygame surfaces being unintentionally shared between objects


Working with this code in two separate files :

import pygame

# These are used to figure out where this top-level module is being run 
#   so that the path can be passed to submodules
from inspect import getsourcefile
from os.path import abspath

import RT_Core.rt_multimedia as mm
import Core.class_hexagon as hx
import RT_Core.rt_map as mp

# Import pygame locals into local namespace.  Do them all so we're not
#   constantly updating this list every time we find out we need a new one
from pygame.locals import *

print("Starting Pygame...")
pygame.init()

"""
    Globals/Constants
""" ###########################################################################
SCR_WIDTH = 800
SCR_HEIGHT = 600

WORLD_X = 3
WORLD_Y = 5

"""
    Classes
""" ###########################################################################

class Tile(pygame.sprite.Sprite) :
    def __init__(self, gfx) :
        super().__init__()
        self.image = gfx.copy()
        self.rect = self.image.get_rect()

# end class Tile

# Window needs to be made first or images won't load...
screen = pygame.display.set_mode((SCR_WIDTH, SCR_HEIGHT), RESIZABLE)
pygame.display.set_caption("Railroad Tycoon")

local_path = abspath(getsourcefile(lambda:0))
local_path = local_path.replace("railroad_tycoon.py" , "")
mm.multimedia_init(local_path)
tile_invalid = mm.load_tile_invalid()
tiles_sea = mm.load_tile_sea()
tiles_clear = mm.load_tile_clear()
tiles_mtn = mm.load_tile_mountain()

hex_manager = mp.Hexagon_Map(   tile_invalid.get_width(), \
                                tile_invalid.get_height())
empty_world_row = list()
world = list()
for i in range(WORLD_X) :
    empty_world_row.append(Tile(tile_invalid))
for i in range(WORLD_Y) :
    world.append(empty_world_row)

world[0][ 0] = Tile(tiles_sea[0x00])
"""
world[0][ 1] = Tile(tiles_sea[0x01])
world[0][ 2] = Tile(tiles_sea[0x02])
world[1][ 0] = Tile(tiles_clear[0x00])
world[1][ 1] = Tile(tiles_clear[0x01])
world[1][ 2] = Tile(tiles_clear[0x02])
world[2][ 0] = Tile(tiles_mtn[0x00])
world[2][ 1] = Tile(tiles_mtn[0x01])
world[2][ 2] = Tile(tiles_mtn[0x02])     
"""
    
bg_image = mm.load_background_image()
bg_cur = pygame.transform.smoothscale(bg_image, (SCR_WIDTH, SCR_HEIGHT))
screen.blit(bg_cur, (0, 0))
screen.blit(tile_invalid, (SCR_WIDTH / 2, SCR_HEIGHT / 2))
#screen.blit(world._get(0, 0).image, (0, 0))
for i in range(WORLD_X) :
    for j in range(WORLD_Y) :
        screen.blit(world[j][ i].image, hex_manager.get_xy(i, j))

running = True
clock = pygame.time.Clock()
while running :
    for event in pygame.event.get() :
        if event.type == QUIT :
            running = False
        if event.type == WINDOWRESIZED :
            SCR_WIDTH = screen.get_width()
            SCR_HEIGHT = screen.get_height()
            bg_cur = pygame.transform.smoothscale(  bg_image, \
                                                    (SCR_WIDTH, SCR_HEIGHT) )
            screen.blit(bg_cur, (0, 0))
    
    pygame.display.update()
            
# Close properly            
pygame.quit()

# end railroad_tycoon.py

and

class Hexagon_Map :
    def __init__(self, tile_width : int, tile_height : int) :
        self.w = tile_width
        self.h = tile_height
            
    def convert_from_axial_q(self, col : int, row : int) :
        return col + ((row - (row & 0x01)) >> 0x01)
                     
    def get_xy(self, col : int, row : int) :
        # All odd rows are x-offset by just a half tile
        x = (col * self.w) + ((self.w * (row & 0x01)) >> 0x01)
        # Each row is 75% of a height down from the previous row
        y = ((self.h * 0x03) >> 0x02) * row
        return (x, y)
         
# end class Hexagon_Map

I'm getting the following output :

Pygame screen with Sprite-based objects

What I expected is that only the top left hexagon appears blue while all others are black. So it looks like an underlying surface object is being shared somehow but I'm not seeing where that is coming from since the Tile class constructor uses the copy() operation. The other option would be that I'm actually just drawing the same row over and over but I'm not sure why that would be the case. What am I missing here?

Note that load_tile_invalid() returns a single surface object while each of the other load_tile functions return a list of three surfaces.


Solution

  • You append the same empty_world_row list multiple times in your world list, any change to the list will be reflected everywhere it's referenced.

    You need to create a new list every time

    
    world = list()
    for i in range(WORLD_Y):
        empty_world_row = list()
        for j in range(WORLD_X):
            empty_world_row.append(Tile(tile_invalid))
        world.append(empty_world_row)