Search code examples
pythonpygamespritedeep-copyminimax

Deepcopying a 2d arrary (board) with sprites on it


i'm currently working on my final project in school so i'm making a game with pygame. The game includes a board with bases, colonies and defenses, all are sprite objets. In the project we were requested to add 2 machine learning algorithems so one of them is minimax. as you probably know, to use minimax you need to copy.deepcopy the board a couple of times and calculate each board's huristic value and find the best boards when max and the worst ones when min.

The problem I am facing is the simple fact that copy.deepcopy probably can't operate on an object that has sprite's attributes. This is the error message that shows up:

TypeError: can't pickle pygame.Surface objects

and it adresses me to this row in my code:

b = copy.deepcopy(Board)

I really don't know what to do in this situation, I can't go back and change the board because I have about 1700 rows of code + finished graphics and changing all objects to a non - sprite object is just too much. so I baisiclly need your help, gotta figure this out to continue

here is an example of a single row in the board at the begining of the game; just to helpy you understand what i'm talking about:

[None, "<"Colony sprite(in 2 groups)">", None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, "<"Colony sprite(in 2 groups)">", None]

an example for a colony class, sprite- related code at the last paragraph:

class Colony(pg.sprite.Sprite):
    def __init__(self, x, y, clan, type):
        self.clan = clan
        if self.clan == 1:
            name = "joogadars"
        else:
            name = "klagars"
        self.type = type
        if type == 1:
            type_name = "doorks"
        else:
            type_name = "gorgs"
        self.lvl = 1
        self.attackable = True

        self.rates = array_from_txt_colonies("Colonies Data.txt", self.type, 'Rates')
        self.prices = array_from_txt_colonies("Colonies Data.txt", self.type, 'Prices')
        self.hps = array_from_txt_colonies("Colonies Data.txt", self.type, 'Hps')
        self.hp = self.hps[self.lvl - 1]
        self.rate = self.rates[self.lvl - 1]

        self.position = (x, y)
        full_name = type_name + '/' + name + '/' + name + " " + type_name + " colony lvl " + str(self.lvl) + " use.png"
        pg.sprite.Sprite.__init__(self)
        img = 'C:/Users/ariel/Desktop/Ariel/12th/Python/final proj/graphics/islands/colonies/' + full_name
        self.image = pg.image.load(img)
        self.rect = self.image.get_rect(center=self.position)

Solution

  • If you don't want or can't seperate the needed gamestate from your drawing code (a.k.a. the sprites), a simple solution is to store the image path in your sprites:

    ...
    self.position = (x, y)
    full_name = type_name + '/' + name + '/' + name + " " + type_name + " colony lvl " + str(self.lvl) + " use.png"
    pg.sprite.Sprite.__init__(self)
    self.img = 'C:/Users/ariel/Desktop/Ariel/12th/Python/final proj/graphics/islands/colonies/' + full_name
    self.image = pg.image.load(self.img)
    self.rect = self.image.get_rect(center=self.position)
    ...
    

    then before trying to copy the sprites, remove the image, and reload it again after copying; something like this:

    for sprite in Board.sprites:
        sprite.image = None
    
    b = copy.deepcopy(Board)
    
    for sprite in Board.sprites:
        sprite.image = pygame.load(sprite.img)
    
    for sprite in b.sprites:
        sprite.image = pygame.load(sprite.img)
    

    You'll get the idea. Also, you should probably cache the images too, so you don't have to load them from disk everytime.

    Another way is to implement __deepcopy__ to prevent the deepcopy function from trying to copy the image attribute; you read about it e.g. here and here (but you would have to load the image after copying).

    Use whatever works for you.