Modelling Chess Positions


The knight is carrying a raw chicken. The man apprehensively covers his head and the knight slams him in the stomach with the chicken.
Woman I think it’s silly to ask a lizard what it thinks, anyway.
Chairman (off) Why?
Woman I mean they should have asked Margaret Drabble.
Young Man (very reasonably) Well I think, er, customs people are quite necessary, and I think they’re doing quite a good job really. Check.
We now see that he is playing chess with another young man. They are in an ordinary flat. There is a tremendous battering, banging, hammering and clattering at the door.

Practically everything in programming involves making a representation of something from real life or someone’s imagination and visualising that  model to the user of the program.  In this tutorial we are going to put together a very basic model of the pieces on a chess board.  A chess board looks like this:

(source)

Which is to say, the board itself has 8 rows (which run horizontally) and 8 columns (which run vertically).  There are 8 pawns for each of white and black and each of white and black have an additional 8 pieces (capital pieces).  A prime contender for representing the board is a list of lists, with each list corresponding to a row and column respectively.  The usual way to represent the pieces is to use capital letters for white pieces (RNBKQP – Rook, kNight, Bishop, King, Queen, Pawn)  and corresponding lower case letters for the black pieces.  Thus, to represent the white rook in the lower left hand corner one would have the letter R in the first column of the first row of your list of lists.

Aside: Lists of lists

Lists can have any elements in them.  In particular, a list can contain lists as its elements.

>>> a = []
>>> a.append(range(8))  # range returns a list
>>> a
[[0, 1, 2, 3, 4, 5, 6, 7]]

Here, the double square brackets [[ ]] indicate two levels of list. This is clearer if we add another element:

>>> a.append(range(4))
>>> a
[[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3]]

The two elements in the list are separated by a comma:

>>> a[0]  # remember the first element of a list is at index 0 not 1.
[0, 1, 2, 3, 4, 5, 6, 7]
>>> a[1]
[0, 1, 2, 3]

There is a shortcut notation for accessing an element of an element of a list of lists [sic]:

>>> a[0][7]
7
>>> a[1][3]
3

So, a[0] refers to the first element of the list a, which is, itself, a list. But a[0][7] refers to the eighth element of that list.

Making our Model
With this knowledge we can make a model of the initial set up of a chess board using a 8×8 list of lists, with each element representing a square on the chess board.* The first row is the black pieces: [‘r’, ‘n’, ‘b’, ‘q’, ‘k’, ‘b’, ‘n’, ‘r’], the second row is the black pawns: [‘p’, ‘p’, ‘p’, ‘p’, ‘p’, ‘p’, ‘p’, ‘p’], then there are four rows of empty squares [‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘] followed by white pawns [‘P’, ‘P’, ‘P’, ‘P’, ‘P’, ‘P’, ‘P’, ‘P’], then white pieces [‘R’, ‘N’, ‘B’, ‘Q’, ‘K’, ‘B’, ‘N’, ‘R’].

Exercise: check there are 8 rows, each with 8 elements.

So, let’s make a class to hold this representation:

EMPTY_SQUARE = " "

class Model(object):
    def __init__(self):
        '''create a chess board with pieces positioned for a new game
        row ordering is reversed from normal chess representations
        but corresponds to a top left screen coordinate 
        '''
        
        self.board = []
        pawn_base = "P "*8
        white_pieces =  "R N B Q K B N R"
        white_pawns = pawn_base.strip() 
        black_pieces = white_pieces.lower()
        black_pawns = white_pawns.lower()
        self.board.append(black_pieces.split(" "))
        self.board.append(black_pawns.split(" "))
        for i in range(4):
            self.board.append([EMPTY_SQUARE]*8)
        self.board.append(white_pawns.split(" "))
        self.board.append(white_pieces.split(" "))

Each time the Model class is instantiated (that is whenever you see something like a = Model()) the instance will be created with an attribute called self.board which has an initial chess position represented in it.

Exercise: make an instance of Model() and print its attribute board.

Viewing the Model
At the moment it is hard to know whether our Model is properly representing a chess board. What we need is a way to view a given board. This could just be a view function, but, since there aren’t enough classes in the world already, I am going to make it a class:

column_reference = "a b c d e f g h".split(" ")
class View(object):
    def __init__(self):
        pass
    def display(self,  board):
        print("%s: %s"%(" ", column_reference))
        print("-"*50)
        for i, row in enumerate(board):
            row_marker = 8-i
            print("%s: %s"%(row_marker,  row))

So, let’s create a model and a view, then pass data from the model to the display method of the view:

>>> m = Model() #instantiate a model
>>> v = View()  #instantiate a view
>>> v.display(m.board)  # pass the model's data to the view's display method
 : ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
--------------------------------------------------
8: ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']
7: ['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p']
6: [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
5: [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
4: [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
3: [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
2: ['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P']
1: ['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']

I have prettied it up a little by adding coordinate headings (a-h and 1-8). This is a recognised chess notation for identifying board positions and making moves. So, to move the white pawn in front of the king two spaces forward, one writes e2-e4 – that is, e2 (the piece’s starting square) – (to) e4 (the destination square). These coordinates have the same concept as the ones we met in our earlier tutorials (eg here) except that they only have 8 positions in each axis (as opposed to hundreds of pixels in a window), and they start from the lower, rather than the upper left.

Controlling the View and Model
All we need now is something which allows you to feed back information to the model, so that it can change, and for those changes to be displayed back to us. The bit which fits between the model and view, controlling the interactions between them is called (not surprisingly) the Controller. First though, I want to add something for the Model to do – a move piece method, along with a position class (which is actually superfluous – I have only introduced because I wanted to refer to coordinates as .i and .j)

#/usr/bin/python2.7
'''
Representing a chess set in Python
Brendan Scott
19 April 2013

Dark square on a1

'''

#column_reference = "1 2 3 4 5 6 7 8".split(" ")
column_reference = "a b c d e f g h".split(" ")
EMPTY_SQUARE = " "

class Model(object):
    def __init__(self):
        '''create a chess board with pieces positioned for a new game
        row ordering is reversed from normal chess representations
        but corresponds to a top left screen coordinate 
        '''
        
        self.board = []
        pawn_base = "P "*8
        white_pieces =  "R N B Q K B N R"
        white_pawns = pawn_base.strip() 
        black_pieces = white_pieces.lower()
        black_pawns = white_pawns.lower()
        self.board.append(black_pieces.split(" "))
        self.board.append(black_pawns.split(" "))
        for i in range(4):
            self.board.append([EMPTY_SQUARE]*8)
        self.board.append(white_pawns.split(" "))
        self.board.append(white_pieces.split(" "))

    def move(self, start,  destination):
        ''' move a piece located at the start location to destination
        (each an instance of BoardLocation)
        Does not check whether the move is valid for the piece
        '''
        # error checking
        for c in [start, destination]:  # check coordinates are valid
            if c.i > 7 or c.j > 7 or c.i <0 or c.j <0:
                return 
        if start.i == destination.i and start.j == destination.j: # don't move to same location
            return

        if self.board[start.i][start.j] == EMPTY_SQUARE:  #nothing to move
            return 
            
        f = self.board[start.i][start.j]
        self.board[destination.i][destination.j] = f
        self.board[start.i][start.j] = EMPTY_SQUARE


class BoardLocation(object):
    def __init__(self, i, j):
        self.i = i
        self.j = j
        

class View(object):
    def __init__(self):
        pass
    def display(self,  board):
        print("%s: %s"%(" ", column_reference))
        print("-"*50)
        for i, row in enumerate(board):
            row_marker = 8-i
            print("%s: %s"%(row_marker,  row))
        

class Controller(object):
    def __init__(self):
        self.model = Model()
        self.view = View()
    
    def run(self):
        ''' main loop'''
        while True:
            self.view.display(self.model.board)
            move = raw_input("move (eg e2-e4) ")
            move = move.lower()
            if move =="q":
                break
            if move =="":
                move = "e2-e4"
            start,  destination = self.parse_move(move)
            self.model.move(start, destination)
            
    def parse_move(self, move):
        ''' Very basic move parsing 
        given a move in the form ab-cd where a and c are in [a,b,c,d,e,f,g,h]
        and b and d are numbers from 1 to 8 convert into BoardLocation instances
        for start (ab) and destination (cd)
        Does not deal with castling (ie 0-0 or 0-0-0) or bare pawn moves (e4)
        or capture d4xe5 etc
        No error checking! very fragile
        '''
        
        s, d = move.split("-")

        i = 8- int(s[-1]) # board is "upside down" with reference to the representation
        j = column_reference.index(s[0])
        start = BoardLocation(i, j)
        
        i =  8- int(d[-1])
        j= column_reference.index(d[0])
        destination = BoardLocation(i, j)

        return start,  destination
        

if __name__=="__main__":
    C = Controller()
    C.run()

Now, if you run this from a command line, it should allow you to move the pieces around on the board using the e2-e4 notation. It doesn’t play chess – or even checks that the moves are valid, but it does record the result of them.

Homework: Why did we go to all this trouble to separate the model from the view?

* Note: another way of doing this is to keep a dictionary of pieces and their locations…

5 Responses to Modelling Chess Positions

  1. aden says:

    Hello, My son and I are new to Python. Can you help point me in the right direction for the beginning of this project. We would like to build the chess board from start to finish. I’m not finding an obvious beginning? It could just be that I’m so new to Python.

    Thank you

    • brendanscott says:

      Hi Aden,
      This is the first part of the “chess board” project – although it’s not actually a separate project, just a short diversion, the ultimate aim of which is to show why you separate data from the user interface. In the next tutorial we graft a different user interface (view) on top of this data and in the following tutorial, we graft yet another one on top of it. However, this tutorial doesn’t explain any of the basics of Python. For that, start on the getting started page:

      Getting Started


      Regards

      Brendan

  2. Pingback: Hooking up the Sunfish Chess Engine (Advanced) | Python Tutorials for Kids 8+

  3. rhdblog101 says:

    Hi Brendan..thanks for this nice post.
    On copy and paste the final code to chessboard.py and executing python cmd in windows terminal, I get a board display in console, but an error message below it:
    Traceback (most recent call last):
    File “chessboard.py”, line 115, in
    C.run()
    File “chessboard.py”, line 81, in run
    move = raw_input(“move (eg e2-e4) “)
    NameError: name ‘raw_input’ is not defined

    • Brendan says:

      Hi rhd
      Sorry, I don’t have normal access to my blog (or even Python!) at the moment. However, my guess is that you are using python 3 rather than 2.7. If you add a line raw_input = input it should solve that particular problem but the code will probably have other issues. Easiest way to follow at the moment is to install python 2.7
      cheers
      Brendan

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.