I am trying to implement a chess AI using a monte carlo tree search. This requires playing through 800 random games from each position to evaluate the value of each move. However, I am using a pygame sprite group to hold instances of the pieces, which I then loop through to find the possible moves for each piece in the position. When I take a piece I use the .kill() function as otherwise I will end up with my legal_moves() function returning moves from pieces that have been taken. However, when this is used in the search, it also kills the piece in the game, which I don't want it to do.
I have tried using the inbuilt copy function, but this is just a shallow copy. I tried copy.deepcopy(piece_list), but this gives an error message:
File "C:\Users\Sean\PycharmProjects\Chess\Monte_Carlo_Tree_Search.py", line 339, in run_mcts
exploration_game = copy.deepcopy(game)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 180, in deepcopy
y = _reconstruct(x, memo, *rv)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 280, in _reconstruct
state = deepcopy(state, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 150, in deepcopy
y = copier(x, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 240, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 180, in deepcopy
y = _reconstruct(x, memo, *rv)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 280, in _reconstruct
state = deepcopy(state, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 150, in deepcopy
y = copier(x, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 240, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 150, in deepcopy
y = copier(x, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 240, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 180, in deepcopy
y = _reconstruct(x, memo, *rv)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 280, in _reconstruct
state = deepcopy(state, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 150, in deepcopy
y = copier(x, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 240, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "C:\Users\Sean\AppData\Local\Programs\Python\Python37-32\lib\copy.py", line 169, in deepcopy
rv = reductor(4)
TypeError: can't pickle pygame.Surface objects
I've had a look around online, but none of the responses seem to give a way to do it, rather an alternate method.
import pygame
knight = pygame.sprite.Sprite()
piece_list = pygame.sprite.Group()
piece_list.add(knight)
piece_list_copy = piece_list.copy()
for piece in piece_list_copy:
piece.kill()
print(piece_list)
I would want some method of changing this code such that it returns "Group(1 sprites)", as opposed to "Group(0 sprites)"
Answer
The error you're encountering, TypeError: can't pickle pygame.Surface objects
, is caused because pygame.Surface
objects (which are used to represent the visual part of your sprites) cannot be deep-copied using the standard Python copy
module. pygame.Surface
objects contain low-level resources like graphical data that aren't designed to be copied in this way.
To resolve your issue, you'll need to approach the problem of duplicating your sprite group in a way that doesn't rely on deep copying the entire pygame.sprite.Group
object.
Explanation:
-
The root problem:
You cannot deepcopy apygame.sprite.Sprite
object or itsSurface
attribute becausepygame.Surface
is not designed to be copied like that (and contains non-copyable resources like the graphical data). -
What you need:
You want to create a copy of the sprite group for use in your Monte Carlo Tree Search (MCTS), but without modifying the original group or killing any pieces in the original group.
Solution:
Instead of deep-copying the entire Group
(which includes the sprites), you can:
- Create a shallow copy of the sprite group (
pygame.sprite.Group()
), which will copy the references to the sprites but not the actual sprite objects. - Create new sprite instances for each sprite in the group if you need to simulate moves and changes. These new sprites can be configured as copies of the originals but won't affect the original sprite group when you call
.kill()
or make other changes.
Step-by-Step Solution:
Here’s how you can modify your code:
-
Create a fresh group:
Instead of usingcopy.copy()
orcopy.deepcopy()
, we can manually copy the pieces into a new group. This ensures that the original group isn't affected when you modify the new group. -
Clone the sprites:
When creating a copy of each sprite, you can either create a new sprite and copy its attributes or simply reference the original sprite if you're only concerned with its position and state.
Here’s an example solution where we manually "clone" the sprite objects without copying the Surface
:
import pygame
class Knight(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((50, 50)) # Placeholder surface
self.image.fill((255, 0, 0)) # Red color for the piece
self.rect = self.image.get_rect()
# Initialize pygame
pygame.init()
# Create an instance of the Knight sprite and add it to a group
knight = Knight()
piece_list = pygame.sprite.Group()
piece_list.add(knight)
# Create a new group to simulate the "copy"
piece_list_copy = pygame.sprite.Group()
# Add the same pieces to the copy group but create new sprite instances
for piece in piece_list:
# You can create a new instance or duplicate attributes
piece_copy = Knight() # Create a fresh instance
piece_copy.rect.topleft = piece.rect.topleft # Copy position
piece_list_copy.add(piece_copy)
# Now you can "kill" pieces in the copy without affecting the original group
for piece in piece_list_copy:
piece.kill()
# Check if the original group is intact (should still contain one piece)
print(piece_list) # Group(1 sprites)
Key Changes:
-
Cloning the sprite:
We create newKnight
instances (piece_copy = Knight()
), which represent cloned versions of the original pieces. We also copy relevant attributes (e.g., position) to ensure the clone behaves like the original. -
Killing the clones only:
Thepiece_list_copy
group is separate from the originalpiece_list
, so killing pieces inpiece_list_copy
does not affect the original group. This way, you can simulate moves, including removing pieces, without modifying the state of the original game. -
Avoiding deep copy of
pygame.Surface
:
Since we are creating new instances of the sprites and not copying theSurface
object directly, we avoid the issue of trying to deepcopypygame.Surface
objects.
Conclusion:
By manually cloning each sprite and copying its relevant attributes (like position and state), you avoid the pitfalls of trying to deep copy a pygame.sprite.Group
, and you can safely modify the clones without affecting the original sprite group. This is a more efficient and flexible approach for your MCTS-based chess AI.