25

I have a class that describe chess pieces. I make for all type piece in the Board a class for example Pawn, Queen, keen, etc... I have a trouble in Pawn class I want to convert to Queen or other object that has a class (when pawn goto 8th row then convert to something another) how can I do this ?

class Pawn:
    def __init__(self ,x ,y):
        self.x = x
        self.y = y
    def move(self ,unit=1):
        if self.y ==7 :
            self.y += 1
            what = raw_input("queen/rook/knight/bishop/(Q,R,K,B)?")
            # There is most be changed that may be convert to:
            # Queen ,knight ,bishop ,rook
        if self.y != 2 and unit == 2:
            print ("not accesible!!")
        elif self.y ==2 and unit == 2:
            self.y += 2
        elif unit == 1:
            self.y += 1
        else:
            print("can`t move over there")
Kurt McKee
  • 1,410
  • 13
  • 17
Mojtaba Kamyabi
  • 3,440
  • 3
  • 29
  • 50

5 Answers5

31

It is actually possible to assign to self.__class__ in Python, but you really have to know what you're doing. The two classes have to be compatible in some ways (both are user-defined classes, both are either old-style or new-style, and I'm not sure about the use of __slots__). Also, if you do pawn.__class__ = Queen, the pawn object will not have been constructed by the Queen constructor, so expected instance attributes might not be there etc.

An alternative would be a sort of copy constructor like this:

class ChessPiece(object):
  @classmethod
  def from_other_piece(cls, other_piece):
    return cls(other_piece.x, other_piece.y)

Edit: See also Assigning to an instance's __class__ attribute in Python

Community
  • 1
  • 1
wutz
  • 3,204
  • 17
  • 13
1

It's cleaner to solve this kind of problem with a delegation pattern.

class Piece:
    
    def __init__(self, col, row, piece_class=Pawn):
        self.piece = piece_class(col, row)

    def move(self):
        self.piece.move()
   
    def promote(self):
        if not isinstance(self.piece, Pawn):
            raise ValueError('No promotion except on pawns')
        self.piece = Queen(self.piece.row, self.piece.col)

class Pawn:
    def __init__(self, col, row):
        self.col = col
        self.row = row
    
    def move(self):
        self.row += 1

class Queen:
    def __init__(self, col, row):
        self.col = col
        self.row = row

    def move(self):
        # you get the idea
        pass

Then you can do...

p = Piece('e', 2, Pawn)
p.promote()
p.piece

And p.piece is now a Queen.

manimino
  • 1,265
  • 1
  • 11
  • 11
1

An object in Python will always be an instance of its class. There is no way to change that without resorting to "dirty hacks".

In your case, you should probably consider refactoring the logic. Instead of letting a piece move itself, define some kind of controller that moves the pieces. If a pawn reaches the last row, this controller can easily replace a piece with another.

Ferdinand Beyer
  • 64,979
  • 15
  • 154
  • 145
1

One way to solve this is to have a generic Piece class and another Strategy or Characteristics class. The piece provides generic interface common to all pieces (like move_to, or something) and Strategy decides if it's possible and how to execute the command. When you want to change a pawn to a queen, you change the strategy of the piece.

Edit: In your case it might not even be necessary to make it that complicated. You could have something like this:

class Piece:
    def __init__(self, movefunc):
        self.move = movefunc

def move_pawn(inst, unit=1):
    pass

pawn = Piece(move_pawn)
Paul Manta
  • 30,618
  • 31
  • 128
  • 208
  • 3
    This doesn't answer the question. You are still creating a new instance instead of modifying an existing instance. – shrewmouse May 16 '19 at 12:58
0

One solution could be to let Pawn, via e.g. on Pawn.promote_to('Q'), return a Queen such that piece = piece.update() would be sensible in any case.