Uri Blass wrote:I agree that calculating perft(14) is not a good way to verify the correctness of a move generator.
You are correct.
I did not claim that 
perft(14) would properly exercise all of the generate/execute/retract code in any program.  But it does quite well for those positions which can be reached.  Further, as 
Symbolic is primarily a chess program and not a 
perft() program.  Many, many routines which have nothing to do with 
perft() are exercised.  That's in part why 
Symbolic is a slow 
perft() calculator relative to at least some specialized 
perft() programs.
One way I use to verify move generation is to feed various well-known FEN files (e.g., BWTC, WAC, WCSAC, etc.) into the program being tested and run 
perft() for various depths and checking the results with a known good program.
A more thorough way is to use the random game generator code and compare the generation count for each position between the program being tested and a known good generator.  It's tough for a bug to hide in hundreds of billions random positions.  This technique is also good for testing specialized generators: gainer moves only, checking moves only, etc.
Symbolic's source is 21,135 lines long.  The source specific to it's 
perft() transposition code is less than 0.5% of that, only 100 lines:
Code: Select all
NodeCount Position::PathCountTran(const ui depth, PCTBasePtr baseptr)
{
  typedef enum
  {
    EsInit,
    EsInitPly,
    EsExecute,
    EsTermPly,
    EsTerm,
    EsExit
  } Es;
  NodeCount total;
  Es state = EsInit;
  ui ply;
  PcPIRList pcpirlist;
  PcPIRNodePtr pnptr;
  while (state != EsExit)
    switch (state)
    {
      case EsInit:
        ply = 0;
        pcpirlist.Append(new PcPIRNode());
        pnptr = pcpirlist.GetHead();
        state = EsInitPly;
        break;
      case EsInitPly:
        pnptr->Reset();
        if (ply == depth)
        {
          pnptr->sum = 1;
          state = EsTermPly;
        }
        else
        {
          if (ply == (depth - 1))
          {
            pnptr->sum = CountMoves();
            state = EsTermPly;
          }
          else
          {
            if (baseptr->Probe(*this, (depth - ply), pnptr->sum))
              state = EsTermPly;
            else
            {
              Generate(pnptr->gmvec);
              state = EsExecute;
            };
          };
        };
        break;
      case EsExecute:
        if (pnptr->AllMovesUsed())
        {
          baseptr->Stash(*this, (depth - ply), pnptr->sum);
          state = EsTermPly;
        }
        else
        {
          Execute(pnptr->NextMove());
          ply++;
          if (pcpirlist.GetCount() == ply)
            pcpirlist.Append(new PcPIRNode());
          pnptr = pnptr->GetNext();
          state = EsInitPly;
        };
        break;
      case EsTermPly:
        if (ply == 0)
        {
          total = pnptr->sum;
          state = EsTerm;
        }
        else
        {
          pnptr->GetPrev()->sum += pnptr->sum;
          ply--;
          pnptr = pnptr->GetPrev();
          Retract();
          state = EsExecute;
        };
        break;
      case EsTerm:
        pcpirlist.Reset();
        state = EsExit;
        break;
      default:
        break;
    };
  return total;
}