09 Reading a FEN string
At this point it's good to have a function to read a chess position
from a pgn-file. When we have a move generator, being able to read and
setup positions will make the testing of the move generator a lot
easier. The code below opens a file, reads the numberth
chess position found, puts that information in the
board.square-array and calls
board.initFromSquares to initialize the board.
step 18: readfen.cpp
#ifndef _CRT_SECURE_NO_DEPRECATE
// suppress MS security warnings
#define _CRT_SECURE_NO_DEPRECATE 1
#endif
#include <iostream>
#include "defines.h"
#include "protos.h"
#include "extglobals.h"
BOOLTYPE readFen(char
*filename, int number)
{
int numberf;
char s[180];
char
fenwhite[80];
char
fenblack[80];
char
fen[100];
char
fencolor[2];
char
fencastling[5];
char
fenenpassant[3];
char
temp[80];
int
fenhalfmoveclock;
int
fenfullmovenumber;
BOOLTYPE returnValue;
FILE * fp;
returnValue = false;
if (number <=
0) return returnValue;
// open the file for
read and scan through until we find the number-th position:
fp=fopen(filename,
"rt");
if (fp !=
NULL)
{
numberf = 0;
while
(fscanf(fp, "%s", s) != EOF)
{
if
(!strcmp(s, "[White"))
{
fscanf(fp,
"%s", fenwhite);
// remove first (") and last two characters ("]) from fenwhite:
strcpy(temp,
"");
strncat(temp, fenwhite,
strlen(fenwhite)-2);
strcpy(temp, temp+1);
strcpy(fenwhite, temp);
}
if
(!strcmp(s, "[Black"))
{
fscanf(fp,
"%s", fenblack);
// remove first (") and last two characters ("]) from fenblack:
strcpy(temp,
"");
strncat(temp, fenblack,
strlen(fenblack)-2);
strcpy(temp, temp+1);
strcpy(fenblack, temp);
}
if
(!strcmp(s, "[FEN"))
{
// position found, so increment numberf.
// we already have fenwhite and fenblack.
numberf++;
if (numberf == number)
{
fscanf(fp,
"%s", fen);
fscanf(fp,
"%s", fencolor);
// b or w
fscanf(fp,
"%s", fencastling);
// -, or KQkq
fscanf(fp,
"%s", fenenpassant);
// -, or e3, or b6, etc
fscanf(fp,
"%d", &fenhalfmoveclock);
// int, used for the fifty move draw rule
fscanf(fp,
"%d", &fenfullmovenumber);
// int. start with 1, It is incremented
after move by Black
std::cout <<
std::endl << "winglet> fen #" <<
numberf << " in " << filename <<
":" << std::endl << std::endl;
std::cout <<
" White: " << fenwhite <<
std::endl;
std::cout <<
" Black: " << fenblack <<
std::endl;
std::cout <<
" " << &fen[1] << std::endl;
if (fencolor[0] ==
'w')
{
std::cout
<< " wt to move next" <<
std::endl;
}
else
{
std::cout
<< " bl to move next" <<
std::endl;
}
std::cout <<
" Castling: " << fencastling <<
std::endl;
std::cout <<
" EP square: " << fenenpassant <<
std::endl;
std::cout <<
" Fifty move count: " <<
fenhalfmoveclock << std::endl;
std::cout <<
" Move number: " <<
fenfullmovenumber << std::endl << std::endl;
}
}
}
if (numberf
< number)
{
printf("winglet>
only %d fens present in %s, fen #%d not found\n",
numberf, filename, number);
returnValue =
false;
}
else
{
setupFen(fen, fencolor,
fencastling, fenenpassant, fenhalfmoveclock, fenfullmovenumber);
returnValue =
true;
}
fclose(fp);
}
else
{
printf("winglet>
error opening file: %s\n", filename);
returnValue =
false;
}
return
returnValue;
}
void setupFen(char *fen,
char *fencolor,
char *fencastling,
char *fenenpassant,
int fenhalfmoveclock,
int fenfullmovenumber)
{
int i, file,
rank, counter, piece;
int
whiteCastle, blackCastle, next, epsq;
piece = 0;
for (i = 0; i
< 64; i++)
{
board.square[i] = EMPTY;
}
// loop over the
FEN string characters, and populate board.square[]
// i is used as
index for the FEN string
// counter is the
index for board.square[], 0..63
// file and rank
relate to the position on the chess board, 1..8
// There is no
error/legality checking on the FEN string!!
file = 1;
rank = 8;
i = 0;
counter = 0;
while
((counter < 64) && (fen[i] != '\0'))
{
// '1'
through '8':
if (((int)
fen[i] > 48) && ((int) fen[i] < 57))
{
file+= (int)
fen[i] - 48;
counter+= (int)
fen[i] - 48;
}
else
// other
characters:
{
switch
(fen[i])
{
case '/':
rank--;
file = 1;
break;
case 'P':
board.square[BOARDINDEX[file][rank]] = WHITE_PAWN;
file += 1;
counter += 1;
break;
case 'N':
board.square[BOARDINDEX[file][rank]] = WHITE_KNIGHT;
file += 1;
counter += 1;
break;
case 'B':
board.square[BOARDINDEX[file][rank]] = WHITE_BISHOP;
file += 1;
counter += 1;
break;
case 'R':
board.square[BOARDINDEX[file][rank]] = WHITE_ROOK;
file += 1;
counter += 1;
break;
case 'Q':
board.square[BOARDINDEX[file][rank]] = WHITE_QUEEN;
file += 1;
counter += 1;
break;
case 'K':
board.square[BOARDINDEX[file][rank]] = WHITE_KING;
file += 1;
counter += 1;
break;
case 'p':
board.square[BOARDINDEX[file][rank]] = BLACK_PAWN;
file += 1;
counter += 1;
break;
case 'n':
board.square[BOARDINDEX[file][rank]] = BLACK_KNIGHT;
file += 1;
counter += 1;
break;
case 'b':
board.square[BOARDINDEX[file][rank]] = BLACK_BISHOP;
file += 1;
counter += 1;
break;
case 'r':
board.square[BOARDINDEX[file][rank]] = BLACK_ROOK;
file += 1;
counter += 1;
break;
case 'q':
board.square[BOARDINDEX[file][rank]] = BLACK_QUEEN;
file += 1;
counter += 1;
break;
case 'k':
board.square[BOARDINDEX[file][rank]] = BLACK_KING;
file += 1;
counter += 1;
break;
default:
break;
}
}
i++;
}
next = WHITE_MOVE;
if
(fencolor[0] == 'b') next =
BLACK_MOVE;
whiteCastle = 0;
blackCastle = 0;
if (strstr(fencastling,
"K")) whiteCastle += CANCASTLEOO;
if (strstr(fencastling,
"Q")) whiteCastle += CANCASTLEOOO;
if (strstr(fencastling,
"k")) blackCastle += CANCASTLEOO;
if (strstr(fencastling,
"q")) blackCastle += CANCASTLEOOO;
if (strstr(fenenpassant,
"-"))
{
epsq = 0;
}
else
{
// translate
a square coordinate (as string) to int (eg 'e3' to 20):
epsq = ((int)
fenenpassant[0] - 96) + 8 * ((int)
fenenpassant[1] - 48) - 9;
}
board.initFromSquares(board.square, next,
fenhalfmoveclock, whiteCastle , blackCastle , epsq);
}
readFen and
setupFen need to be
added in protos.h
step 19: protos.h
#ifndef WINGLET_PROTOS_H
#define WINGLET_PROTOS_H
unsigned
int bitCnt(BitMap);
void dataInit();
void displayBitmap(BitMap);
BOOLTYPE doCommand(const
char *);
unsigned
int
firstOne(BitMap);
void info();
unsigned
int lastOne(BitMap);
void readCommands();
BOOLTYPE readFen(char
*, int);
void setupFen(char *,
char *,
char *, char *,
int , int
);
#endif
There's some changes that we need to make in
commands.cpp. First, add the
_CRT_SECURE_NODEPRECATE definition at the
beginning of the file. It will suppress security warnings that don't
make sense. Apparently, Microsoft's intention is to have you use their
(non-portable) versions of functions.
The call to
readFen is also added in
doCommand:
step 20: command.cpp
#ifndef _CRT_SECURE_NO_DEPRECATE
#define _CRT_SECURE_NO_DEPRECATE 1
#endif
#include
<iostream>
#include
"defines.h"
#include
"protos.h"
#include
"extglobals.h"
#include
"board.h"
void readCommands()
{
int nextc;
if (board.nextMove
== WHITE_MOVE)
{
std::cout <<
"wt> ";
}
else
{
std::cout <<
"bl> ";
}
std::cout.flush();
//
===========================================================================
// Read a command and call doCommand:
//
===========================================================================
while ((nextc
= getc(stdin)) != EOF)
{
if (nextc
== '\n')
{
CMD_BUFF[CMD_BUFF_COUNT] =
'\0';
while
(CMD_BUFF_COUNT)
{
if (!doCommand(CMD_BUFF))
return;
}
if
(board.nextMove == WHITE_MOVE)
{
std::cout <<
"wt> ";
}
else
{
std::cout <<
"bl> ";
}
std::cout.flush();
}
else
{
if
(CMD_BUFF_COUNT >= MAX_CMD_BUFF-1)
{
std::cout <<
"Warning: command buffer full !! "
<< std::endl;
CMD_BUFF_COUNT = 0;
}
CMD_BUFF[CMD_BUFF_COUNT++] =
nextc;
}
}
}
BOOLTYPE doCommand(const
char *buf)
{
char
userinput[80];
int number;
//
=================================================================
// return when command buffer is empty
//
=================================================================
if (!strcmp(buf,
""))
{
CMD_BUFF_COUNT =
'\0';
return
true;
}
//
=================================================================
// help, h, or ?: show this help
//
=================================================================
if ((!strcmp(buf,
"help")) || (!strcmp(buf,
"h")) || (!strcmp(buf,
"?")))
{
std::cout << std::endl <<
"help:" << std::endl;
std::cout <<
"black : BLACK to move"
<< std::endl;
std::cout <<
"cc : play
computer-to-computer " << std::endl;
std::cout <<
"d : display board "
<< std::endl;
std::cout <<
"exit : exit program "
<< std::endl;
std::cout <<
"eval : show static
evaluation of this position" << std::endl;
std::cout <<
"game : show game moves "
<< std::endl;
std::cout <<
"go : computer next
move " << std::endl;
std::cout <<
"help, h, or ? : show this help "
<< std::endl;
std::cout <<
"info : display variables
(for testing purposes)" << std::endl;
std::cout <<
"move e2e4, or h7h8q : enter a move (use
this format)" << std::endl;
std::cout <<
"moves : show all legal
moves" << std::endl;
std::cout <<
"new : start new game"
<< std::endl;
std::cout <<
"perf : benchmark a
number of key functions" << std::endl;
std::cout <<
"perft n : calculate raw
number of nodes from here, depth n " << std::endl;
std::cout <<
"quit : exit program "
<< std::endl;
std::cout <<
"r : rotate board "
<< std::endl;
std::cout <<
"readfen filename n : reads #-th FEN
position from filename" << std::endl;
std::cout <<
"sd n : set the search
depth to n" << std::endl;
std::cout <<
"setup : setup board... "
<< std::endl;
std::cout <<
"undo : take back last
move" << std::endl;
std::cout <<
"white : WHITE to move"
<< std::endl;
std::cout << std::endl;
CMD_BUFF_COUNT =
'\0';
return
true;
}
//
=================================================================
// black: black to move
//
=================================================================
if (!strcmp(buf,
"black"))
{
board.nextMove = BLACK_MOVE;
CMD_BUFF_COUNT =
'\0';
return
true;
}
//
=================================================================
// d: display board
//
=================================================================
if (!strcmp(buf,
"d"))
{
board.display();
CMD_BUFF_COUNT =
'\0';
return
true;
}
//
=================================================================
// exit or quit: exit program
//
=================================================================
if ((!strcmp(buf,
"exit")) || (!strcmp(buf,
"quit")))
{
CMD_BUFF_COUNT =
'\0';
return
false;
}
//
=================================================================
// info: display variables (for testing
purposes)
//
=================================================================
if (!strcmp(buf,
"info"))
{
info();
CMD_BUFF_COUNT =
'\0';
return
true;
}
//
=================================================================
// new: start new game
//
=================================================================
if (!strcmp(buf,
"new"))
{
dataInit();
board.init();
board.display();
CMD_BUFF_COUNT =
'\0';
return
true;
}
//
=================================================================
// r: rotate board
//
=================================================================
if (!strcmp(buf,
"r"))
{
board.viewRotated = !board.viewRotated;
board.display();
CMD_BUFF_COUNT =
'\0';
return
true;
}
//
=================================================================
// readfen filename n : reads #-th FEN
position from filename
//
=================================================================
if (!strncmp(buf,
"readfen", 7))
{
sscanf(buf+7,"%s
%d", userinput, &number);
board.init();
readFen(userinput, number);
board.display();
CMD_BUFF_COUNT =
'\0';
return
true;
}
//
=================================================================
// white: white to move
//
=================================================================
if (!strcmp(buf,
"white"))
{
board.nextMove = WHITE_MOVE;
CMD_BUFF_COUNT =
'\0';
return
true;
}
//
=================================================================
// unknown command
//
=================================================================
std::cout << "
command not implemented: " << buf <<
", type 'help' for more info" <<
std::endl;
CMD_BUFF_COUNT =
'\0';
return
true;
}
The program can now read FEN strings.
Later we will be using BT2450.pgn as
a test suite to estimate winglet's chess
playing strength (ELO-rating). This suite was developed by Hubert
Bednorz and Fred Toennissen to measure the tactical capability of chess
engines, as opposed to strategic/positional strength.
How does that work? A chess engine is
given 15 minutes (900 seconds) to analyze each position. If a
position is solved, the solution time is recorded in seconds. It
doesn't count as a solution if the engine finds the move and then
changes its mind. If the engine finds the move, changes its
mind then finds the move again, that 2nd time is used. Any
solution that is not found scores as 900 seconds. The 30 times are
averaged and subtracted from 2450 to give the estimated ELO rating. So,
if no solution is found, the estimated ELO rating will be 2450-900=1550.
If the average time is 8 minutes (480 seconds), then the estimated ELO
rating is 2450-480=1970.
The suite has 30 test
positions. You can download the file to test the new
FEN string reading functionality. Note that this file must be placed in the correct folder
in order for winglet to find it,
which is your main project folder (one level up from the Debug or
Release folder).

√ browse
through BT2450.pgn, to see the positions.
The command syntax
is "readfen BT2450.pgn 1" for the first position, etc.
|
|