|
|
State of play The state of a game of chess can be broken down into the following data structures:
- The Board: where each piece is on the board and what colour it is
- The Current Go: who's turn it is to play
- The Castle State: once a rook or king has moved, it cannot take part in castling, even if it moves back. The avaialble castle moves need to be stored
- The Enpassant Square: When a pawn advances two squares on its first move, it can be taken by an enemy pawn moving to the place it would have occupied had it only moved one square, but only on the next move. This square is stored as the enpassant square
- The 50 move draw: if no pawn has moved, no piece taken, and no check has occurred for 50 complete moves (ie 100 turns), then the game is drawn. We need to store a counter of how many moves like this have occurred so far.
- The History: if the same position is reached three times within the course of a game, a draw occurs. Rather than store each state, it's more compact to store a history of each move.
Implementation of the state of play
The board The board is represented by two arrays of 100 elements. Why? Let me explain my decision: 100 elements - one of the most time consuming processes in a chess program is generating moves. Most of the moves involve 'sliding' pieces around the board. By adding two extra rank/files to the board, we can mark these squares as 'off the board'. This simplifies generating moves for sliding pieces as one doesn't need to keep testing to see if the current square is off the board at the top/bottom/left/right, you just test to see if the current square is marked as 'off the board' Two arrays - one for the colour of each square: black, white or empty; and one for the piece on each square: King, Queen, Rook, Knight, Bishop, Pawn, empty. This simplifies certain parts of the program as you often need to test the colour of a square but not the piece, and vice versa.
The current go Rather easy this one, I just store black or white. I also have an overall state variable, setting it to checkmate black/white, draw, quit, and playing as appropriate. This makes the 'main loop' of the program quite simple:
While state is playing if go is computer make computer move else make player move check we're still playing
The castle state I use a couple of simple bitmask variables for the black and white castling state. Each has three flags, castle left, castle right, and castle done. As moves are applied we update these flags. We also use the castling flags in the rating algorithm to give a point bonus for successful castling, and a penalty if castling is not available.
The en-passant square Because the board is a 10x10 grid, we can use simple decimal notation for squares, e.g. 11 is A8 and 88 is H1. Note the board is numbered from top left to bottom right, which is a bit different to normal chess notation. Oh, we're talking about the en-passant square? Well, I store it if a pawn moves two spaces, and clear it to 0 otherwise.
The 50 move draw Again this is pretty trivial, I store a count of the number of 'bland' moves, and increase it, or reset it to zero when a move is applied. Note that a draw occurs when the counter reaches 100, as in chess a move is two plays, one for each player.
The history This is slightly more complicated. Rather than just storing each move, we need to also keep track of the castling flags, the enpassant square and the draw50 counter, so that we can use the history to undo moves from the board. There's a neat little trick for quickly working out the three repetition draw in the code, based on the one in Tom's Simple Chess Program. Unfortunately on the RCX we've only got limited memory, so the history is maintained only for the last 30 plays.
All the state variables and constants are declared in globals.h You'll see that there an extra bit of state I maintain to speed up various processes - the king positions, this speeds up check testing.
|
|