Home Previous Bottom Next

Writing a chess program in 99 steps


step 75: command.cpp

#ifndef _CRT_SECURE_NO_DEPRECATE
#define _CRT_SECURE_NO_DEPRECATE
#endif
#include <conio.h>
#include <iostream>
#include "defines.h"
#include "protos.h"
#include "extglobals.h"
#include "board.h"
#include "timer.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)
(...)
// =================================================================
// 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 << "time s              : time per move in seconds" << 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"))
       {
              if (board.nextMove == WHITE_MOVE)
              {
                     board.hashkey ^= KEY.side;
                     board.endOfSearch = 0;
                     board.endOfGame = 0;
              }
              board.nextMove = BLACK_MOVE;
              CMD_BUFF_COUNT = '\0';
              return true;
       }    
 
// =================================================================
// cc: play computer-to-computer
// =================================================================
 
       if (!strcmp(buf, "cc"))
       {
              while (!_kbhit() && !board.isEndOfgame(i, dummy))
              {
                     move = board.think();
                     if (move.moveInt)
                     {
                           makeMove(move);
                           board.endOfGame++;
                           board.endOfSearch = board.endOfGame;
                           board.display();
                     }
                     else
                     {
                           CMD_BUFF_COUNT = '\0';
                           return true;
                     }
              }
              CMD_BUFF_COUNT = '\0';
              return true;
       }
 
// =================================================================
// d: display board
// =================================================================
 
       if (!strcmp(buf, "d"))
       {
              board.display();
              CMD_BUFF_COUNT = '\0';
              return true;
       }
(...)
// =================================================================
// undo: take back last move
// =================================================================
 
       if (!strcmp(buf, "undo"))
       {
              if (board.endOfGame)
              {
                     unmakeMove(board.gameLine[--board.endOfGame].move);
                     board.endOfSearch = board.endOfGame;
                     board.display();
              }
              else
              {
                     std::cout << "already at start of game" << std::endl;
              }
              CMD_BUFF_COUNT = '\0';
              return true;
       }
 
// =================================================================
// white: white to move
// =================================================================
 
       if (!strcmp(buf, "white"))
       {
              if (board.nextMove == BLACK_MOVE)
              {
                     board.hashkey ^= KEY.side;
                     board.endOfSearch = 0;
                     board.endOfGame = 0;
              }
              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;
}

step 76: search.cpp

#include <stdio.h>
#include <iostream>
#include <iomanip>
#include "defines.h"
#include "extglobals.h"
#include "protos.h"
#include "board.h"
#include "timer.h"
 
Move Board::think()
{
       // This is the entry point for search, it is intended to drive iterative deepening ** later **
       // The search stops if (whatever comes first):
       // - there is no legal move (checkmate or stalemate)
       // - there is only one legal move (in this case we don't need to search)
       // **later** - time is up
       // - the search is interrupted by the user, or by winboard
       // - the search depth is reached
 
       int score, legalmoves;
       Move singlemove;
 
//     ===========================================================================
//     Check if the game has ended, or if there is only one legal move,
//  because then we don't need to search:
//     ===========================================================================
       if (isEndOfgame(legalmoves, singlemove)) return NOMOVE;
       if (legalmoves == 1)
       {
              std::cout << "forced move: "; displayMove(singlemove); std::cout << std::endl;
              return singlemove;
       }
 
//     ===========================================================================
//     There is more than legal 1 move, so prepare to search:
//     ===========================================================================
       timer.init();
       displaySearchStats(1, 0, 0);  // display console header
 
       moveBufLen[0] = 0;
       inodes = 0;
       msStart = timer.getms();
//     score = minimax(0, searchDepth);
//     score = alphabeta(0, searchDepth, -LARGE_NUMBER, LARGE_NUMBER);
       score = alphabetapvs(0, searchDepth, -LARGE_NUMBER, LARGE_NUMBER);
       msStop = timer.getms();
       displaySearchStats(2, searchDepth, score);
       return (triangularArray[0][0]);
}
 
int Board::alphabetapvs(int ply, int depth, int alpha, int beta)
{
       // PV search
 
       int i, j, movesfound, pvmovesfound, val;
       triangularLength[ply] = ply;
       if (depth == 0) return board.eval();
       // repetition check:
       if (repetitionCount() >= 3) return DRAWSCORE;
       movesfound = 0;
       pvmovesfound = 0;
       moveBufLen[ply+1] = movegen(moveBufLen[ply]);
       for (i = moveBufLen[ply]; i < moveBufLen[ply+1]; i++)
       {
              makeMove(moveBuffer[i]);
              {
                     if (!isOtherKingAttacked())
                     {
                           inodes++;
                           movesfound++;
                           if (!ply) displaySearchStats(3, ply, i);
                           if (pvmovesfound)
                           {
                                  val = -alphabetapvs(ply+1, depth-1, -alpha-1, -alpha);
                          if ((val > alpha) && (val < beta))
                                  {
                                         val = -alphabetapvs(ply+1, depth - 1, -beta, -alpha);   // In case of failure, proceed with normal alphabeta                
                                  }
                           }
                            else val = -alphabetapvs(ply+1, depth-1, -beta, -alpha);          // Normal alphabeta
                           unmakeMove(moveBuffer[i]);
                           if (val >= beta)
                           {
                                  return beta;
                           }
                           if (val > alpha)
                           {
                                  alpha = val;                                                      // both sides want to maximize from *their* perspective
                                  pvmovesfound++;
                                  triangularArray[ply][ply] = moveBuffer[i];                                  // save this move
                                  for (j = ply + 1; j < triangularLength[ply+1]; j++)
                                  {
                                         triangularArray[ply][j] = triangularArray[ply+1][j];   // and append the latest best PV from deeper plies
                                  }
                                  triangularLength[ply] = triangularLength[ply+1];
                                  if (!ply)
                                  {
                                         msStop = timer.getms();
                                         displaySearchStats(2, depth, val);
                                  }
                           }
                     }
                     else unmakeMove(moveBuffer[i]);
              }
       }
 
//     ===========================================================================
//     50-move rule:
//     ===========================================================================
 
       if (fiftyMove >= 100) return DRAWSCORE;
 
//     ===========================================================================
//     Checkmate/stalemate detection:
//     ===========================================================================
 
       if (!movesfound)
       {
              if (isOwnKingAttacked())  return (-CHECKMATESCORE+ply-1);
              else  return (STALEMATESCORE);
       }
 
       return alpha;
}
 
int Board::alphabeta(int ply, int depth, int alpha, int beta)
{
       // Negascout
 
       int i, j, val;
 
       triangularLength[ply] = ply;
       if (depth == 0) return board.eval();
 
       moveBufLen[ply+1] = movegen(moveBufLen[ply]);
       for (i = moveBufLen[ply]; i < moveBufLen[ply+1]; i++)
       {
              makeMove(moveBuffer[i]);
              {
                     if (!isOtherKingAttacked())
                     {
                           inodes++;
                           if (!ply) displaySearchStats(3, ply, i);
                           val = -alphabeta(ply+1, depth-1, -beta, -alpha);
                           unmakeMove(moveBuffer[i]);
                           if (val >= beta)
                           {
                                  return beta;
                           }
                           if (val > alpha)
                           {
                                  alpha = val;                                                                              // both sides want to maximize from *their* perspective
                                  triangularArray[ply][ply] = moveBuffer[i];                                  // save this move
                                  for (j = ply + 1; j < triangularLength[ply + 1]; j++)
                                  {
                                         triangularArray[ply][j] = triangularArray[ply+1][j];   // and append the latest best PV from deeper plies
                                  }
                                  triangularLength[ply] = triangularLength[ply + 1];
                                  if (!ply)
                                  {
                                         msStop = timer.getms();
                                         displaySearchStats(2, depth, val);
                                  }
                           }
                     }
                     else unmakeMove(moveBuffer[i]);
              }
       }
       return alpha;
}
 
int Board::minimax(int ply, int depth)
{
       // Negamax
 
       int i, j, val, best;
       best = -LARGE_NUMBER;
       triangularLength[ply] = ply;
       if (depth == 0) return board.eval();
       moveBufLen[ply+1] = movegen(moveBufLen[ply]);
       for (i = moveBufLen[ply]; i < moveBufLen[ply+1]; i++)
       {
              makeMove(moveBuffer[i]);
              {
                     if (!isOtherKingAttacked())
                     {
                           inodes++;
                           if (!ply) displaySearchStats(3, ply, i);
                           val = -minimax(ply+1, depth-1);                                 // note the minus sign
                           unmakeMove(moveBuffer[i]);
                           if (val > best)                                                 // both sides want to maximize from *their* perspective
                           {
                                  best = val;
                                  triangularArray[ply][ply] = moveBuffer[i];                                  // save this move
                                  for (j = ply + 1; j < triangularLength[ply + 1]; j++)
                                  {
                                         triangularArray[ply][j] = triangularArray[ply+1][j];   // and append the latest best PV from deeper plies
                                  }
                                  triangularLength[ply] = triangularLength[ply + 1];
                                  if (!ply)
                                  {
                                         msStop = timer.getms();
                                         displaySearchStats(2, depth, val);
                                  }
                           }
                     }
                     else unmakeMove(moveBuffer[i]);
              }
       }
       return best;
}
 
 
void Board::displaySearchStats(int mode, int depth, int score)
{
       // displays various search statistics
       // only to be used when ply = 0
       // mode = 1 : display header
       // mode = 2 : display full stats, including score and latest PV
       // mode = 3 : display current root move that is being searched
       //                     depth = ply, score = loop counter in the search move list
 
       char sanMove[12];
       U64 dt, hh, mm, ss;
 
       switch (mode)
       {
              case 1:
                           std::cout << "depth  score   nodes     time  knps PV" << std::endl;
                           break;
 
              case 2:
                           dt = msStop - msStart;
 
                           // depth
                           printf("%5d ", depth);
 
                           // score
                           printf("%+6.2f ", float(score/100.0));
 
                           // nodes searched
                           if      (inodes > 100000000) printf("%6.0f%c ", float(inodes/1000000.0), 'M');
                           else if (inodes > 10000000) printf("%6.2f%c ", float(inodes/1000000.0), 'M');
                           else if (inodes > 1000000) printf("%6.0f%c ", float(inodes/1000.0),    'K');
                           else if (inodes > 100000)  printf("%6.1f%c ", float(inodes/1000.0),    'K');
                           else if (inodes > 10000)   printf("%6.2f%c ", float(inodes/1000.0),    'K');
                           else                                     printf("%7d ", inodes);
 
                           // search time
                           if (dt > 3600000)
                           {     
                                  hh = dt/3600000;
                                  mm = (dt - 3600000*hh)/60000;
                                  ss = (dt - 3600000*hh - 60000*mm)/1000;
                                  printf("%02d%c", hh, ':');
                                  printf("%02d%c", mm, ':');
                                  printf("%02d ", ss);
                           }
                           else if (dt > 60000)
                           {     
                                  mm = dt/60000;
                                  ss = (dt - 60000*mm)/1000;
                                  printf("   %02d%c", mm, ':');
                                  printf("%02d ", ss);
                           }
                           else if (dt > 10000)       printf(" %6.1f%c ", float(dt/1000.0), 's');
                           else if (dt > 1000)               printf(" %6.2f%c ", float(dt/1000.0), 's');
                           else if (dt > 0)                  printf(" %5dms ", dt);
                           else                                     printf("     0ms ");
 
                           // search speed
                           if (dt > 0) std::cout << std::setw(5) << (inodes/dt) << " ";
                           else          std::cout << "    - ";
 
                           // PV
                           displayPV();
                           break;
 
              case 3: // Note that the numbers refer to pseudo-legal moves:
                           printf("%12s (%2d/%2d)%16s", " ", score+1, moveBufLen[depth+1]-moveBufLen[depth], " ");
                           unmakeMove(moveBuffer[score]);
                           toSan(moveBuffer[score], sanMove);
                std::cout << sanMove;
                           makeMove(moveBuffer[score]);
                           printf("...    \r");
                           std::cout.flush();
                           break;
 
              default: break;
       }
 
       return;
}
 
BOOLTYPE Board::isEndOfgame(int &legalmoves, Move &singlemove)
{
       // Checks if the current position is end-of-game due to:
       // checkmate, stalemate, 50-move rule, or insufficient material
 
       int whiteknights, whitebishops, whiterooks, whitequeens, whitetotalmat;
       int blackknights, blackbishops, blackrooks, blackqueens, blacktotalmat;
 
       // are we checkmating the other side?
       int i;
       if (isOtherKingAttacked())
       {
              if (nextMove) std::cout << "1-0 {Black mates}" << std::endl;
              else std::cout << "1-0 {White mates}" << std::endl;
              return true;
       }
 
       // how many legal moves do we have?
       legalmoves = 0;
       moveBufLen[0] = 0;
       moveBufLen[1] = movegen(moveBufLen[0]);
       for (i = moveBufLen[0]; i < moveBufLen[1]; i++)
       {
              makeMove(moveBuffer[i]);
              if (!isOtherKingAttacked())
              {
                     legalmoves++;
                     singlemove = moveBuffer[i];
              }
              unmakeMove(moveBuffer[i]);
       }
 
       // checkmate or stalemate?
       if (!legalmoves)
       {
              if (isOwnKingAttacked())
              {
                     if (nextMove) std::cout << "1-0 {White mates}" << std::endl;
                     else std::cout << "1-0 {Black mates}" << std::endl;
              }
              else std::cout << "1/2-1/2 {stalemate}" << std::endl;
              return true;
       }
 
       // draw due to insufficient material:
    if (!whitePawns && !blackPawns)
       {
              whiteknights = bitCnt(whiteKnights);
              whitebishops = bitCnt(whiteBishops);
              whiterooks = bitCnt(whiteRooks);
              whitequeens = bitCnt(whiteQueens);
              whitetotalmat = 3 * whiteknights + 3 * whitebishops + 5 * whiterooks + 10 * whitequeens;
              blackknights = bitCnt(blackKnights);
              blackbishops = bitCnt(blackBishops);
              blackrooks = bitCnt(blackRooks);
              blackqueens = bitCnt(blackQueens);
              blacktotalmat = 3 * blackknights + 3 * blackbishops + 5 * blackrooks + 10 * blackqueens;
 
              // king versus king:
              if ((whitetotalmat == 0) && (blacktotalmat == 0))
              {
                     std::cout << "1/2-1/2 {material}" << std::endl;
                     return true;
              }
 
              // king and knight versus king:
              if (((whitetotalmat == 3) && (whiteknights == 1) && (blacktotalmat == 0)) ||
                    ((blacktotalmat == 3) && (blackknights == 1) && (whitetotalmat == 0)))
              {
                     std::cout << "1/2-1/2 {material}" << std::endl;
                     return true;
              }
 
        // 2 kings with one or more bishops, all bishops on the same colour:
              if ((whitebishops + blackbishops) > 0)
              {
                     if ((whiteknights == 0) && (whiterooks == 0) && (whitequeens == 0) &&
                           (blackknights == 0) && (blackrooks == 0) && (blackqueens == 0))
                     {
                           if (!((whiteBishops | blackBishops) & WHITE_SQUARES) ||
                !((whiteBishops | blackBishops) & BLACK_SQUARES))
                           {
                                  std::cout << "1/2-1/2 {material}" << std::endl;
                                  return true;
                           }
                     }
              }
       }
 
       // draw due to repetition:
       if (repetitionCount() >= 3)
       {
              std::cout << "1/2-1/2 {repetition}" << std::endl;
              return true;
 
       }
 
       // draw due to 50 move rule:
       if (fiftyMove >= 100)
       {
              std::cout << "1/2-1/2 {50-move rule}" << std::endl;
              return true;
       }
 
       return false;
}
 
int Board::repetitionCount()
{
       //     repetitionCount is used to detect threefold repetitions of the current position
 
       int i, ilast, rep;
       rep = 1;                                              // current position has occurred once, namely now!
       ilast = board.endOfSearch - board.fiftyMove;          // we don't need to go back all the way
       for (i = board.endOfSearch - 2; i >= ilast; i -= 2)   // we can skip every other position
       {
              if (gameLine[i].key == board.hashkey) rep++;
       }
       return rep;
}

 

  if the computer is playing a game against itself at search depth=4, the game will end in a draw due to a threefold repetition, after 57 moves.


Home Previous Top Next

last update: Sunday 19 June 2011