Enforcing rules for pieces' movement

Before we get these chess pieces to move on click of mouse, we need to know how many squares a given piece can move. We need to enforce rules for each of the chess pieces.

Prepare for Lift Off

Before we start coding the rules, let's quickly recap the rules of chess:

  • King can move only one square in any direction: up, down, to the sides, and diagonally.
  • Queen can move in any one straight direction: forward, backward, sideways, or diagonally; as far as possible as long as she does not move through any of her own pieces.
  • Rook can move as far as it wants, but only forward, backward, and to the sides
  • Bishop can move as far as it wants, but only diagonally.
  • Knights are different from others. They must move two squares in one direction, and then one more move at a 90 degree angle, following the shape of L. Knights are also the only pieces that can jump over other pieces.
  • Pawns move forward, but capture diagonally. Pawns can only move forward one square at a time, except for their very first move where they can move forward two squares. Pawns can only capture one square diagonally in front of them.

The bottom line here is that we need to track three common things for each of the piece:

  • Its current position
  • Allowed directions for movement
  • Distance that a piece can move

Engage Thrusters

Step 1 – tracking moves available for all pieces from Pieces superclass

Because the preceding things can be tracked at a central place, let's define a method named moves_available in our superclass, Pieces (see code 4.04: pieces.py), for tracking moves available for all pieces as follows:

    def moves_available(self, pos, diagonal, orthogonal, distance):
        board = self.board
        allowed_moves = []
        orth  = ((-1,0),(0,-1),(0,1),(1,0))
        diag  = ((-1,-1),(-1,1),(1,-1),(1,1))
        piece = self
        beginningpos = board.num_notation(pos.upper())
        if orthogonal and diagonal:
            directions = diag+orth
        elif diagonal:
            directions = diag
        elif orthogonal:
            directions = orth

        for x,y in directions:
            collision = False
            for step in range(1, distance+1):
                if collision: break
                dest = beginningpos[0]+step*x, beginningpos[1]+step*y
                if self.board.alpha_notation(dest) not in board.occupied('white') + board.occupied('black'):
                    allowed_moves.append(dest)
                elif self.board.alpha_notation(dest) in board.occupied(piece.color):
                    collision = True
                else:
                    allowed_moves.append(dest)
                    collision = True
        allowed_moves = filter(board.is_on_board, allowed_moves)
        return map(board.alpha_notation, allowed_moves)

The description of the code is listed as follows:

  • The method accepts four arguments: the current position of a piece, two Boolean values representing whether or not diagonal and orthogonal movements are allowed for a piece, and the number of squares a piece can move at one time.
  • Depending upon these arguments, the method collects all allowed moves for a given piece in a list, allowed_moves.
  • Having collected all directions of movements, the code iterates through all locations to detect any possible collision. If collision is detected, it breaks out of the loop, else it appends the coordinate to allowed_moveslist.
  • collision = True is our way to break out of the loop. We need to break out of the loop in two cases: when the destination is occupied, and when it is not occupied, and we have already appended that position into our list of possible moves.
  • The second last line filters out those moves that fall out of the board, and the last line returns the equivalent board notations for all allowed moves.

Note

Having defined our moves_available method, we now simply need to call it from different pieces class.(see code 4.04: pieces.py).

Step 2 – rules for the king, queen, rook and bishop class

King, queen, rook, and bishop pieces on the chessboard have relatively simple rules governing them. These pieces can capture only in the direction in which they move.

Moreover, they move in either orthogonal, diagonal, or a combination of these two directions. We have already coded moves_available in our superclass to handle these directions.

Accordingly, deciding their available moves is just a matter of passing the right arguments to our moves_available method.

class King(Piece):
   shortname = 'k'
   def moves_available(self,pos):
     return super(King, self).moves_available(pos.upper(), True, True, 1)
class Queen(Piece):
   shortname = 'q'
   def moves_available(self,pos):
     return super(Queen,self).moves_available(pos.upper(), True, True, 8)
class Rook(Piece):
   shortname = 'r'
   def moves_available(self,pos):
     return super(Rook, self).moves_available(pos.upper(), False, True, 8)
class Bishop(Piece):
   shortname = 'b'
   def moves_available(self,pos):
     return super(Bishop,self).moves_available(pos.upper(), True, False, 8)

Step 3 – rules for knight

Knight is a different beast because it does not move orthogonally or diagonally. It can also jump over pieces.

Let's, therefore override the moves_available method from our Knight class.

The Knight class is defined as follows (see code 4.04: pieces.py):

class Knight(Piece):
   shortname = 'n'
   def moves_available(self,pos):
     board = self.board
     allowed_moves = []
     beginningpos = board.num_notation(pos.upper())
     piece = board.get(pos.upper())
     changes=((-2,-1),(-2,1),(-1,-2),(-1,2),(1,-2),(1,2),(2,-1),(2,1))
     for x,y in changes:
        dest = beginningpos[0]+x, beginningpos[1]+y
        if(board.alpha_notation(dest) not in board.occupied(piece.color)):
           allowed_moves.append(dest)
        allowed_moves = filter(board.is_on_board, allowed_moves)
     return map(board.alpha_notation, allowed_moves)

The description of the code is listed as follows:

  • The method is quite similar to our previous super class method. However, unlike the super class method, the changes are represented to capture moves two squares in one direction, and then one more move at a 90 degree angle.
  • Similarly, unlike the super class, we do not need to track collisions, because knights can jump over other pieces.

Step 4 – rules for pawn

Pawn too has a unique movement, in that it moves forward, but captures diagonally.

Let's similarly override the moves_available class from within the Pawn class as follows (see code 4.04: pieces.py):

class Pawn(Piece):
    shortname = 'p'
    def moves_available(self, pos):
        board = self.board        
        piece = self
        if self.color == 'white':
            startpos, direction, enemy = 1, 1, 'black'
        else:
            startpos, direction, enemy = 6, -1, 'white'
        allowed_moves = []
        prohibited = board.occupied('white') + board.occupied('black')
        beginningpos   = board.num_notation(pos.upper())
        forward = beginningpos[0] + direction, beginningpos[1]
        # Can a piece move forward?
        if board.alpha_notation(forward) not in prohibited:
            allowed_moves.append(forward)
            if beginningpos[0] == startpos:
                # If pawn in starting pos allow a double move
                double_forward = (forward[0] + direction, forward[1])
                if board.alpha_notation(double_forward) not in prohibited:
                    allowed_moves.append(double_forward)
        # Check for Capturing Moves Available
        for a in range(-1, 2, 2):
            attack = beginningpos[0] + direction, beginningpos[1] + a
            if board.letter_notation(attack) in board.occupied(enemy):
                allowed_moves.append(attack)
        allowed_moves = filter(board.is_on_board, allowed_moves)
        return map(board.alpha_notation, allowed_moves)

The description of the code is listed as follows:

  • We first assign variables startpos, direction, and enemy depending on whether the pawn is black or white.
  • Similar to our previous moves_allowed methods, this method also collects all allowed moves in a blank list, allowed_moves.
  • We then collect a list of all prohibited moves by concatenating two lists of squares occupied by all black and white pieces.
  • We define a list, forward, which holds the position of the one square immediately ahead of the current position of pawn.
  • A pawn cannot move forward if there is a piece in front of it. If the forward position is not prohibited, the position is appended to our allowed_moves list.
  • A pawn can move two places forward from its starting position. We check to see if the current position is the starting position, and if true, we append the double move to our allowed_moves list.
  • A pawn can capture only diagonally adjacent pieces in front of it. We, therefore, assign a variable attack to track the diagonally adjacent positions on the board. If the diagonally adjacent square is occupied by an enemy, that position qualifies to be appended to our list, allowed_moves.
  • We then filter our list to remove all positions which may fall off the board.
  • The last line returns all allowed moves as a list of corresponding letter notations, as we had done in all our previous definitions.

Objective Complete – Mini Debriefing

In this iteration, we coded the logic for enforcing rules related to movement of chess pieces on the board.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.118.229