0

I'm trying to model a chess game in python 3 with object-oriented programming. One of the class i am using is Board which contain a matrix (8x8 list of list) with the other object. I also created a simple __str__ method to visualize the chess board.

Here is some part of the Board class definition :

class Board:
    def __init__(self, pieces: list):
        self.container = [[pieces[i] for i in range(8)],  # Whites
                          [pieces[i] for i in range(8, 16)],  # White pawns
                          [None] * 8,
                          [None] * 8,
                          [None] * 8,
                          [None] * 8,
                          [pieces[i] for i in range(16, 24)],  # Black pawns
                          [pieces[i] for i in range(24, 32)]]  # Blacks


    def __str__(self):
        str_copy = self.container.copy()

        for i in range(8):
            for j in range(8):
                if isinstance(str_copy[i][j], King):
                    str_copy[i][j] = 'K'
                elif isinstance(str_copy[i][j], Queen):
                    str_copy[i][j] = 'Q'
                elif isinstance(str_copy[i][j], Rook):
                    str_copy[i][j] = 'R'
                elif isinstance(str_copy[i][j], Knight):
                    str_copy[i][j] = 'N'
                elif isinstance(str_copy[i][j], Bishop):
                    str_copy[i][j] = 'B'
                elif isinstance(str_copy[i][j], Pawn):
                    str_copy[i][j] = 'P'
                elif str_copy[i][j] is None:
                    str_copy[i][j] = '_'

        return ''.join([str(line) + '\n' for line in str_copy])

My problem is that at some point in my code Board.container seems to be overwritten by the str_copy. I really can't figure out why. Here is the full code if you want to have a look at it: pastebin

Thanks a lot for your help !

halden
  • 23
  • 4
  • 3
    You're only copying the outer container (list), which still holds references to the same inner ones. Use `str_copy = copy.deepcopy(self.container)`. – CristiFati Apr 13 '20 at 15:16
  • [deepcopy](https://docs.python.org/3/library/copy.html#copy.deepcopy) can copy class instances https://stackoverflow.com/a/2612990/2630643 – AlexMTX Apr 13 '20 at 15:18
  • `__str__` looks overly complicated. `str` values are immutable, so there's no reason to create copies of anything. – chepner Apr 13 '20 at 15:30
  • I rushed a bit in closing the question. Considering the current error it is indeed a dupe of https://stackoverflow.com/questions/2612802/how-to-clone-or-copy-a-list, but it's also an *XY Problem*. I was preparing an answer, but @chepner beat me to it :) . – CristiFati Apr 13 '20 at 15:39

2 Answers2

0

This is overly complicated. First, each piece should have its own __str__ method, for instance,

class Bishop:
    ...

    def __str__(self):
        return 'B'

Then

class Board:
    def __init__(self, pieces: list):
        self.container = [pieces[:8],  # white back row
                          pieces[8:16],  # white pawns
                          [None] * 8,
                          [None] * 8,
                          [None] * 8,
                          [None] * 8,
                          pieces[16:24],  # black pawns
                          pieces[24:32]   # black back row
                         ]

    @staticmethod
    def _square_to_str(x):
        return '_' if x is None else str(x)

    def __str__(self):
        return '\n'.join(''.join(map(Board._square_to_str, row))
                          for row in self.container)
chepner
  • 497,756
  • 71
  • 530
  • 681
0

I rushed a bit in closing the question. Considering the current error, it is indeed a dupe of [SO]: How to clone or copy a list?.
But on a closer look, it's acutally an XY Problem. Simply duplicating that object for display purposes only, is not just ugly, but it's also unnecessary. Here's a more elegant alternative:

Add a new (class) method to Piece (to avoid altering __str__ or __repr__ behavior, I named it letter):

@classmethod
def letter(cls):
    raise NotImplementedError

and override it in all its children (King, Queen, Rook, Knight, Bishop, Pawn). For example, in King's case it should be:

@classmethod
def letter(cls):
    return "K"

I guess it's pretty obvious how it would look like for the others.

Then, Board.__str__ could be something like:

def __str__(self):
    return "\n".join("".join(getattr(piece, "letter", lambda: "_")() for piece in line) for line in self.container)
CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • Thank you CristiFati and @chepner. Its true that `copy.hardcopy() did solve my problem but your solutions is much more elegant. This issue helped me to have a better understanding of the "everything is an object" paradigm in Python. Again, thanks a lot for your help ! – halden Apr 15 '20 at 14:38