Structuring chessboard-and-pieces-related data

In our drum program, we had decided on a notation to describe a set of beat patterns. We could then store (pickle) that beat pattern notation and reproduce (unpickle) it later. The chess program is no different. It too needs a suitable notation for describing chess pieces and for locating their positions on the board.

Prepare for Lift Off

We can define our own notation for representing chess piece and their positions, but it turns out that there already exists a globally accepted, simple, compact, and standard notation for representing a chessboard. The notation is called Forsyth-Edwards notation (FEN) available at http://en.wikipedia.org/wiki/Forsyth-Edwards_Notation.

We might have decided to define our notation, but we preferred not to reinvent the wheel here.

The FEN record for starting position of a chess game is written as:

rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1

The key things to note about the notation are as follows:

  • The notation displays six records for a chess game. Each record is separated by a blank space.
  • The first record shows the positions of pieces on a chessboard. Each row of the chessboard (rank) is represented in a section demarcated by the / symbol.
  • Within the first record, each piece is identified by a single letter (pawn = p, knight = n, bishop = b, rook = r, queen = q and king = k).
  • White pieces are represented using uppercase letters (PNBRQK), but black pieces are represented by lowercase letters (pnbrqk).
  • Squares with no pieces on it are represented using digits 1 through 8 (the number of blank squares).
  • The second record denotes the turn of a player. Letter w denotes white turn, and letter b denotes black turn.
  • The third record KQkq indicates whether or not castling feature is available. If neither castle, this is -. Otherwise, this has one or more letters: K (white can castle kingside), Q (white can castle queenside), k (black can castle kingside), and/or q (black can castle queenside).
  • The fourth record_captures En passant details for the game. We will not be implementing castling and En passant features in our game, so we can safely disregard these two records for now.
  • The fifth record keeps track of half-move clock for the game. The half-move clock keeps track of number of turns played since the last pawn advance or last capture. This is used to determine if a draw can be claimed under the fifty-move rule.
  • The sixth record tracks the full-move number, which is incremented by 1 after each move of black. This is used to track the overall length for which a game was played.

The notation as previously stated can be represented pictorially along x and y axis as follows:

Prepare for Lift Off

Using this notation, we can accurately represent any particular square on the chessboard.

The color of piece depends on whether the alphabet is in small letters (black) or capital letters (white).

Thus A1 denotes the bottom and left-most square on the chessboard. Currently, it is occupied by a white rook. The C3 position is currently empty, and E8 has black king and A8 has a black rook.

Following these rules, here is how the FEN notation would change after the following indicative turns played (http://en.wikipedia.org/wiki/Forsyth-Edwards_Notation):

After first move, P to e4:

rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1

After second move, p to c5:

rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2

After third move, N to f3:

rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2

All our chessboard and piece related logic will use the preceding notation. It is, therefore, very important that we fully understand this notation before we proceed to code our game.

Now that we are clear about the preceding notation, let's apply the notation to represent our chessboard. The key idea here is that, given a FEN notation, we should be able to represent it on the board.

Engage Thrusters

Step 1 – creating a Piece superclass

Let's now first code the model code for pieces.py (see code 4.02 pieces.py) by creating a Piece super class as follows:

class Piece():
  def __init__(self, color):
     if color == 'black':
        self.shortname = self.shortname.lower()
     elif color == 'white':
        self.shortname = self.shortname.upper()
     self.color = color

  def ref(self, board):
   ''' Get a reference of chessboard instance'''
     self.board = board

The description of the code is listed as follows:

  • We define a class, Piece (). It's __init__ method, which takes a color as an argument. In accordance with our FEN notation, it changes the shortname to lowercase letter for black and uppercase letter for white. The color handling is done in the superclass, Piece, because it is a common feature for all chess pieces.
  • We also define a method named ref. Its only purpose is to get an instance of the chessboard into the object namespace for the board and pieces to interact. We need this method, because our pieces will ultimately be interacting with the chessboard. Accordingly, we need a reference of the chessboard instance within the Piece class.

Step 2 – creating individual child classes for all pieces

We can create individual child classes for all pieces as follows:

class King(Piece):    shortname = 'k'
class Queen(Piece):    shortname = 'q'
class Rook(Piece):    shortname = 'r'
class Knight(Piece):    shortname = 'n'
class Bishop(Piece):    shortname = 'b'
class Pawn(Piece):   shortname = 'p'

The description of the code is listed as follows:

  • We define classes for each of the pieces found on a chessboard. So, we have classes named King, Queen, Rook, Knight, Bishop, and Pawn. These classes are derived from the Piece super class.
  • For now, these child classes merely define the shortname associated with them. We will later expand these child classes to define and enforce rules for movement of each of these pieces.

Step 3 – defining a method to return the piece instance

We will define a method to return the piece instance as follows:


import sys
SHORT_NAME = {'R':'Rook', 'N':'Knight', 'B':'Bishop', 'Q':'Queen', 'K':'King', 'P':'Pawn'}
def create_piece(piece, color='white'):
     if piece in (None, ''): return
     if piece.isupper(): color = 'white'
     else: color = 'black'
     piece = SHORT_NAME[piece.upper()]
     module = sys.modules[__name__]
     return module.__dict__[piece](color)

The description of the code is listed as follows:

  • The code defines a dictionary with pieces shortname and full name as key-value pair.
  • We then define a method piece which takes a piece shortname and returns the corresponding piece instance.

Step 4 – creating the Board class

Now that we have a basic model ready for pieces, let's code the model to deal with their placement on the chessboard. We code this in chessboard.py.(see code 4.02 chessboard.py) by creating a Board class as follows:

import pieces
import re
START_PATTERN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 1'
class Board(dict):
     y_axis = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H')
     x_axis = (1,2,3,4,5,6,7,8)
     def __init__(self, patt = None):
        self.process_notation(START_PATTERN)

The description of the code is listed as follows:

  • Our code begins with defining the starting pattern as per the FEN notation discussed earlier. We do not include the castle and En passant related notation, because we will not be coding that in our program.
  • We then define our Board class as a subclass of built-in dict type. This is because we will store the pattern as a dictionary.
  • We then define x_axis and y_axis for our chessboard as nonimmutable tuples.
  • The __init__ method of our class simply calls the process_notation method of the class.

Step 5 – displaying pieces on board for a given FEN notation

Pieces on Board for a given FEN notation can be displayed as follows:

def process_notation(self, patt):
   self.clear()
   patt = patt.split('')
      # expand_whitespaces blanks

def expand_whitespaces(match): return '' * int(match.group(0))
   patt[0] = re.compile(r'd').sub(expand_whitespaces, patt[0])
   for x, row in enumerate(patt[0].split('/')):
     for y, alphabet in enumerate(row):
        if alphabet == '': continue
        xycoord = self.alpha_notation((7-x,y))
        self[xycoord] = pieces.piece(alphabet)
        self[xycoord].ref(self)
     if patt[1] == 'w': self.player_turn = 'white'
     else: self.player_turn = 'black'

The description of the code is listed as follows:

  • The job of the process_notation method is to first expand the blank spaces represented by integers into actual spaces. It uses Python built-in regular expression module (re) to expand white spaces in a given FEN notation.
  • The code, expand_whitespaces, does something that might be tricky for Python beginners. It replaces each digit by the corresponding number of whitespaces, so you can later assume that a whitespace is an empty square. It then converts the FEN notation into a string corresponding to x and y alphanumeric coordinate for every piece. For doing this, it calls another method named alpha_notation, which is defined in step 7.
  • The final two lines keep a track of turns taken by the players.

Step 6 – checking if a given coordinate is on the board

Finally, let's end this iteration by defining a method to check if a given coordinate is on the board, as follows (see code 4.02 chessboard.py):

def is_on_board(self, coord):
     ifcoord[1] < 0 or coord[1] > 7 or coord[0] < 0 or coord[0] >7:
        return False
     else: return True

Step 7 – generating alphabetic and numeric notation

We need a way to convert the x and y coordinates for a piece to its alphabetic equivalent notation for example, A1, D5, E3, and so on. We accordingly define the alpha_notation method as follows:

def alpha_notation(self,xycoord):
     if not self.is_on_board(xycoord): return
     return self.y_axis[xycoord[1]] + str(self.x_axis[xycoord[0]])

Similarly, we define a method that takes in an x,y coordinate as input and returns its equivalent numerical notation, as follows:

def num_notation(self, xycoord):
     return int(xycoord[1])-1, self.y_axis.index(xycoord[0])

Step 8 – checking places occupied on the board

Before every move, we will need to check all the places occupied by all the pieces of a given color. This is required not only to calculate valid moves, but to also ensure that move by some other piece does not cause a check on the king.

Accordingly, let's define a method to return a list of coordinates occupied by a given color (see code 4.02 chessboard.py) as follows:

def occupied(self, color):
     result = []
     for coord in self:
        if self[coord].color == color:
           result.append(coord)
           return result

Step 9 – handling errors and exceptions

For handling errors and exceptions, we define a custom exception class named ChessError, and all other exceptions will later be subclassed to it, as follows:

classChessError(Exception): pass

Objective Complete – Mini Debriefing

In this iteration, we created a basic Piece class and dummy child classes for each of the pieces found on the chessboard. The individual piece classes inherit from the parent Piece class. We handle color identification in the parent class because it is something we need to do for all child classes.

We then defined our Board class and added some methods that we will surely need every time we want to move a piece on the board.

We are yet to display those pieces on the board. We do that in the next iteration.

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

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