A Board That Can Read PGN

By now I've finished the meat of my chess game and everybody I show it to is impressed. I am frequently asked the question, did you make that by yourself? Honestly I answer, not completely but mostly. No one ever gets from a to z with out being shown a few of the letters that lie in between. Finding resources on making a chess program was rather hard at the time I made my game, but now on the internet there are tons of chess applets made by college students, and often they hand out there source code.

I like to play correspondence chess, and one of my problems playing it was the amount of time it took to open my chess program, load all the moves from a disk and so forth. I came up with the concept of a PGN reader myself, though I know it was around long before. I just never noticed I guess and so I decided to make my own. PGN reading is a rather complex task when you really think about it because it takes the format e4 or Ne4 where e is the end file and 4 is the end rank. The N represents the piece that is moving. The board can't make a move unless it is given a start file and start rank, thus the PGN itself is insufficient for making moves even after its been converted to board array values.

Conversions

Before begginnig the task of trying to read PGN, the game must be able to convert the pgn into board array values. Notice, f3 means on our board the "fifth file" and the "fifth rank". The function for doing these conversions is simple. It accepts the string representing the file or the string representing the rank and returns an integer value corresponing to a position in the board array.

The piece that could move to...

With respect to the board array, f3 means the pawn that can move to f3, and then the board must find this pawn. In the same way Nf3 would mean the knight that can move to f3 and then board must find that knight. Hence, the board must find the start file and start rank in order to make the move object and process moves. If you've made the checking function, this idea is familiar. There can be seperate functions for finding knights, bishops, pawns, queens, rooks, and kings that can move to a given destination. The only difference here is that we needn't check for paths and the function retruns a move object that contains the start square info.

PGN Reader Core

The PGN Reader itself can be a simple object. All it does is read PGN from the board using objects provided by the programming language that are able to read input streams. In JAVA I used a string reader and I used a buffered reader (for passing to the StreamTokenizer), and a string tokenizer which I set to read only specific tokens. In PGN these tokens are easy to pick out and set. You'll wish to read all of the piece symbols and file symbols (for pawns). When the tokenizer reads these tokens, it will copy everything beggining with that token up to the first occurence of white space. So, if the user typed in 1.Nf3 the tokenizer would allow the reader object to skip the "1." string and copy the "Nf3" string which you could then process in you program. Please refer to one of those sites I was telling you about for more on the PGN reader, but for now, here's my simple PGN reader class.

    PGNReader{

        //the JAVA objects for reading the input stream
        StringReader sr;
        StringTokenizer st;
        BufferedReader r;

        //holds all the moves
        String[] moves;
        //counts through the array
        int counter;
        //holds the token type
        int tokenType;
        PGNReader(){
            moves= new String[200];
        }

        //the function to read PGN
        void readPGN(String args){
            //initialize all elements
            sr= new StringReader(args);
            r= new BufferedReader(sr);
            st= new StreamTokenizer(r);
            counter= 0;
            setTable(); //set what tokens the tokenizer will read
            {
                (tokenType = st.nextToken(); tokenType ! = StreamTokenizer.TT_EOF; tokenType = st.nextToken()){
                    //if its a word read it
                    (tokenType == StreamTokenizer.TT_WORD){
                        (st.sval != null){
                            moves[counter] = st.sval; //place the string into the array
                            counter++;
                        }
                    }
                }
            }
            catch(IOException e){}
        }
        //set the table
        void setTable(){}
        //return the moves contained by the PGNReader
        String[] getMoves(){}

A Wondrous, Beautiful Function!

The PGN reader class and the findPiece functions culminate into one large function that reads PGN and places it into the move array. I place this function into my move array class, and after it's executed the move array is filled with all the moves contained in the original PGN. This is a beautiful function which may unearth some dark spots in you understanding of the move array or PGN reading if there are any

//this function is executed until there is not more PGN left to be read

private Move analyzeMoveArray(MoveAnalyzer m, String s, int i, boolean white, Board b){
     boolean castled;
     int playerToMove;
     boolean capturing;
     boolean ambiguous = false;
     int startRow = 0, startCol, endRow, endCol;
     StringConverter c = new StringConverter();
     castled = false;
     capturing = false;
//first kingside castling
     if(s.equals("O-O")){
         //we know the startRow and endRow and the the startCol and endCol
            if(white)
            {
                [counter.getMoveCount()].setSquare(4, 7, 6, 7);
                (Moves[counter.getMoveCount()], 7, 0, true);
            }
            else{
                 Moves[counter.getMoveCount()].setSquare(4, 0, 6, 0);
                 handleCastle(Moves[counter.getMoveCount()], 7, 1, true);
             }         castled = true;
     }
     //queensideCastling
     else if(s.equals("O-O-O")){
         if(white){
             Moves[counter.getMoveCount()].setSquare(4, 7, 2, 7);
             handleCastle(Moves[counter.getMoveCount()], 8, 0, true);
         }
         else{
             Moves[counter.getMoveCount()].setSquare(4, 0, 2, 0);
             handleCastle(Moves[counter.getMoveCount()], 7, 1, true);
         }
         castled = true;
    }
     //pawn moves
     else if(s.length() == 2){
         endRow= c.readString(s.charAt(0), false);
         endCol = c.readString(s.charAt(1), true);
         Moves[counter.getMoveCount()].setEndSquare(endRow, endCol);
     }
    else if(s.length() == 3){
         endRow = c.readString(s.charAt(1), false);
         endCol = c.readString(s.charAt(2), true);
         Moves[counter.getMoveCount()].setEndSquare(endRow, endCol);
     }
     //captures
     else if(s.length() == 4){
         if(s.charAt(1) == 'x'){
             capturing = true;
             endRow = c.readString(s.charAt(2), false);
             endCol = c.readString(s.charAt(3), true);
             Moves[counter.getMoveCount()].setEndSquare(endRow, endCol);
             printOut("capturing PGN move. endRow = " + endRow + " endCol = " + endCol);
         }
         else{
             endRow = c.readString(s.charAt(2), false);
             endCol = c.readString(s.charAt(3), true);
             startRow = c.readString(s.charAt(1), false);
             ambiguous = true;
             Moves[counter.getMoveCount()].setEndSquare(endRow, endCol);
        }
    }
    if(!castled){
         if(isEven(counter.getMoveCount()))
             playerToMove = 0;
        else
             playerToMove = 1;

        switch(s.charAt(0)){
             case 'N' :   [counter.getMoveCount()].setSquare(m.findKnight(b, playerToMove, Moves[counter.getMoveCount()], ambiguous, startRow));//find the knight's start square
              break;
             case 'B' :   [counter.getMoveCount()].setSquare(m.findBISHOP(b, playerToMove, Moves[counter.getMoveCount()]));//find the bishop's start square
        break;
            case 'K' :   [counter.getMoveCount()].setSquare(m.findKing(b, Moves[counter.getMoveCount()]));//find the king's start square
             break;
             case 'R' :  [counter.getMoveCount()].setSquare(m.findRook(b, playerToMove, Moves[counter.getMoveCount()], ambiguous, startRow));//find the rook's start square
             break;
             case 'Q' :   [counter.getMoveCount()].setSquare(m.findQueen(b, playerToMove, Moves[counter.getMoveCount()]));//find the queens start square
            break;
             case 'a' :
                 case 'b':
                     case'c':
                         case 'd':
                             case 'e':
                                 case 'f':
                                     case 'g':
                                         case 'h':
                                            [counter.getMoveCount()].setSquare(m.findPawn(b, playerToMove, Moves[counter.getMoveCount()], capturing));//find the pawn's start square
                                             break;
            default : printOut("Invalid piece type");
        }
    }
    if(DEBUG)
         printOut("\nthe endRow = " + Moves[counter.getMoveCount()].getEndRow() + "\nendCol = " + Moves[counter.getMoveCount()].getEndCol());
       return Moves[counter.getMoveCount()];
}

start