Add null move
[hachu.git] / hachu.c
diff --git a/hachu.c b/hachu.c
index f7846b8..fd6cbfe 100644 (file)
--- a/hachu.c
+++ b/hachu.c
@@ -46,7 +46,7 @@
 #define EMPTY      0\r
 #define EDGE   (1<<11)\r
 #define TYPE   (WHITE|BLACK|EDGE)\r
-#define ABSENT  4095\r
+#define ABSENT  2047\r
 #define INF     8000\r
 #define NPIECES 2000               /* length of piece list    */\r
 \r
@@ -78,15 +78,16 @@ typedef struct {
   char *name, *promoted;\r
   int value;\r
   signed char range[8];\r
+  int whiteKey, blackKey;\r
 } PieceDesc;\r
 \r
 typedef struct {\r
-  int from, to, piece, victim, new, booty, epSquare, epVictim, ep2Square, ep2Victim, savKeyL, savKeyH;\r
+  int from, to, piece, victim, new, booty, epSquare, epVictim, ep2Square, ep2Victim, revMoveCount, savKeyL, savKeyH;\r
 } UndoInfo;\r
 \r
-int stm, xstm, hashKeyH, hashKeyL, framePtr, msp, nonCapts, retMSP, retFirst, level, chuFlag=1;\r
+int stm, xstm, hashKeyH, hashKeyL, framePtr, msp, nonCapts, rootEval, retMSP, retFirst, pvPtr, level, cnt50, chuFlag=1;\r
 int nodes, startTime, tlim1, tlim2;\r
-Move retMove, moveStack[10000], path[100];\r
+Move retMove, moveStack[10000], path[100], repStack[300], pv[1000];\r
 \r
 #define X 36 /* slider              */\r
 #define J -1 /* jump                */\r
@@ -420,11 +421,12 @@ signed char PST[2*BSIZE];
 #define board (rawBoard + 6*BW + 3)\r
 #define dist  (distance + BSIZE)\r
 \r
-int LookUp(char *name, PieceDesc *list)\r
+PieceDesc *\r
+LookUp (char *name, PieceDesc *list)\r
 { // find piece of given name in list of descriptors\r
   int i=0;\r
   while(list->name && strcmp(name, list->name)) i++, list++;\r
-  return (list->name == NULL ? -1 : i);\r
+  return (list->name == NULL ? NULL : list);\r
 }\r
 \r
 void\r
@@ -507,21 +509,24 @@ Compactify (int stm)
 }\r
 \r
 int\r
-AddPiece (int stm, int n, PieceDesc *list)\r
+AddPiece (int stm, PieceDesc *list)\r
 {\r
-  int i, j;\r
+  int i, j, *key;\r
   for(i=stm+2; i<=last[stm]; i += 2) {\r
-    if(p[i].value < 10*list[n].value || p[i].value == 10*list[n].value && (p[i].promo < 0)) break;\r
+    if(p[i].value < 10*list->value || p[i].value == 10*list->value && (p[i].promo < 0)) break;\r
   }\r
   last[stm] += 2;\r
   for(j=last[stm]; j>i; j-= 2) p[j] = p[j-2];\r
-  p[i].value = 10*list[n].value;\r
-  for(j=0; j<8; j++) p[i].range[j] = list[n].range[j^4*(WHITE-stm)];\r
+  p[i].value = 10*list->value;\r
+  for(j=0; j<8; j++) p[i].range[j] = list->range[j^4*(WHITE-stm)];\r
   switch(Range(p[i].range)) {\r
     case 1:  p[i].pst = BH; break;\r
     case 2:  p[i].pst = BSIZE; break;\r
     default: p[i].pst = BSIZE + BH; break;\r
   }\r
+  key = (stm == WHITE ? &list->whiteKey : &list->blackKey);\r
+  if(!*key) *key = ~(myRandom()*myRandom());\r
+  p[i].pieceKey = *key;\r
   p[i].promoFlag = 0;\r
   for(j=stm+2; j<= last[stm]; j+=2) {\r
     if(p[j].promo >= i) p[j].promo += 2;\r
@@ -534,8 +539,9 @@ AddPiece (int stm, int n, PieceDesc *list)
 void\r
 SetUp(char *array, PieceDesc *list)\r
 {\r
-  int i, j, k, k2, n, m, nr, color;\r
+  int i, j, n, m, nr, color;\r
   char c, *q, name[3];\r
+  PieceDesc *p1, *p2;\r
   last[WHITE] = 1; last[BLACK] = 0;\r
   for(i=0; ; i++) {\r
 //printf("next rank: %s\n", array);\r
@@ -551,17 +557,17 @@ SetUp(char *array, PieceDesc *list)
        name[0] += 'A' - 'a';\r
        if(name[1]) name[1] += 'A' - 'a';\r
       } else color = WHITE;\r
-      k = LookUp(name, list);\r
-      n = AddPiece(color, k, list);\r
+      p1 = LookUp(name, list);\r
+      n = AddPiece(color, p1);\r
       p[n].pos = j;\r
-      if(list[k].promoted[0]) {\r
-       k2 = LookUp(list[k].promoted, list);\r
-        m = AddPiece(color, k2, list);\r
+      if(p1->promoted[0]) {\r
+       p2 = LookUp(p1->promoted, list);\r
+        m = AddPiece(color, p2);\r
        if(m <= n) n += 2;\r
        p[n].promo = m;\r
-       p[n].promoFlag = IsUpwardCompatible(list[k2].range, list[k].range) * DONT_DEFER + CAN_PROMOTE;\r
-       if(Forward(list[k].range)) p[n].promoFlag |= LAST_RANK; // Pieces that only move forward can't defer on last rank\r
-       if(!strcmp(list[k].name, "N")) p[n].promoFlag |= CANT_DEFER; // Knights can't defer on last 2 ranks\r
+       p[n].promoFlag = IsUpwardCompatible(p2->range, p1->range) * DONT_DEFER + CAN_PROMOTE;\r
+       if(Forward(p1->range)) p[n].promoFlag |= LAST_RANK; // Pieces that only move forward can't defer on last rank\r
+       if(!strcmp(p1->name, "N")) p[n].promoFlag |= CANT_DEFER; // Knights can't defer on last 2 ranks\r
        p[n].promoFlag &= n&1 ? P_WHITE : P_BLACK;\r
        p[m].promo = -1;\r
        p[m].pos = ABSENT;\r
@@ -616,7 +622,6 @@ Init()
 \r
   // hash key tables\r
   for(i=0; i<BSIZE; i++) squareKey[i] = ~(myRandom()*myRandom());\r
-  for(i=0; i<NPIECES; i++) p[i].pieceKey = ~(myRandom()*myRandom());\r
 \r
   // board edge\r
   for(i=0; i<BSIZE + 11*BW + 6; i++) rawBoard[i] = EDGE;\r
@@ -999,6 +1004,7 @@ MakeMove(Move m, UndoInfo *u)
   u->piece = board[u->from];\r
   board[u->from] = EMPTY;\r
   u->booty = 0;\r
+  u->revMoveCount = cnt50++;\r
   u->savKeyL = hashKeyL;\r
   u->savKeyH = hashKeyH;\r
 \r
@@ -1010,6 +1016,7 @@ MakeMove(Move m, UndoInfo *u)
       p[u->piece].pos = ABSENT;\r
       u->new = p[u->piece].promo;\r
       u->booty = p[u->new].value - p[u->piece].value;\r
+      cnt50 = 0; // promotion irreversible\r
     }\r
   } else u->new = u->piece;\r
 \r
@@ -1035,12 +1042,13 @@ MakeMove(Move m, UndoInfo *u)
     hashKeyL ^= p[u->ep2Victim].pieceKey * squareKey[u->ep2Square];\r
     hashKeyH ^= p[u->ep2Victim].pieceKey * squareKey[u->ep2Square+BH];\r
     if(p[u->piece].value != 1000 && p[u->epVictim].value == 1000) deferred |= PROMOTE; // flag non-Lion x Lion\r
+    cnt50 = 0; // double capture irreversible\r
   } else u->epVictim = EMPTY;\r
 \r
   u->victim = board[u->to];\r
   p[u->victim].pos = ABSENT;\r
   u->booty += p[u->victim].value + PST[p[u->victim].pst + u->to];\r
-//  if(p[u->victim].value == 1000 && p[u->piece].value != 1000) deferred |= PROMOTE; // flag non-Lion x Lion\r
+  if(u->victim != EMPTY) cnt50 = 0; // capture irreversible\r
 \r
   p[u->new].pos = u->to;\r
   board[u->to] = u->new;\r
@@ -1072,6 +1080,7 @@ UnMake(UndoInfo *u)
   p[u->piece].pos = u->from; // this can be the same as above\r
   board[u->from] = u->piece;\r
 \r
+  cnt50 = u->revMoveCount;\r
   hashKeyL = u->savKeyL;\r
   hashKeyH = u->savKeyH;\r
 }\r
@@ -1190,6 +1199,7 @@ int
 Search (int alpha, int beta, int difEval, int depth, int oldPromo, int promoSuppress)\r
 {\r
   int i, j, k, firstMove, oldMSP = msp, curMove, sorted, bad, phase, king, iterDep, replyDep, nextVictim, to, defer, dubious, bestMoveNr;\r
+  int myPV = pvPtr;\r
   int score, bestScore, curEval, iterAlpha;\r
   Move move, nullMove;\r
   UndoInfo tb;\r
@@ -1212,6 +1222,9 @@ if(PATH) printf("search(%d) %d-%d eval=%d, stm=%d\n",depth,alpha,beta,difEval,st
   alpha -= (alpha < curEval);\r
   beta  -= (beta <= curEval);\r
 \r
+  nodes++;\r
+  pv[pvPtr++] = 0; // start empty PV, directly behind PV of parent\r
+\r
   firstMove = curMove = sorted = msp += 20; // leave 20 empty slots in front of move list\r
   phase = 0; iterDep=1; replyDep = (depth < 1 ? depth : 1) - 1;\r
   do {\r
@@ -1276,6 +1289,11 @@ if(flag && depth>= 0) printf("phase=%d: first/curr/last = %d / %d / %d\n", phase
            break;\r
          case 7: // bad captures\r
          case 8: // PV null move\r
+           phase = 9;\r
+           if(nullMove != ABSENT) {\r
+             moveStack[msp++] = nullMove + (nullMove << SQLEN) | DEFER; // kludge: setting DEFER guarantees != 0, and has no effect\r
+           }\r
+printf("# %d. sqr = %08x null = %08x\n", msp, nullMove, moveStack[msp-1]);\r
          case 9:\r
            goto cutoff;\r
        }\r
@@ -1295,10 +1313,18 @@ if(flag & depth >= 0) printf("%2d:%d extract %d/%d\n", depth, iterDep, curMove,
        if(move == 0) continue; // skip invalidated move\r
       }\r
 if(flag & depth >= 0) printf("%2d:%d found %d/%d\n", depth, iterDep, curMove, msp);\r
+\r
       // RECURSION\r
       stm ^= WHITE;\r
-      defer = MakeMove(moveStack[curMove], &tb);\r
-path[level++] = moveStack[curMove];\r
+      defer = MakeMove(move, &tb);\r
+\r
+      for(i=2; i<=cnt50; i+=2) if(repStack[level-i+200] == hashKeyH) {\r
+       moveStack[curMove] = 0; // erase forbidden move\r
+       score = -INF; goto repetition;\r
+      }\r
+      repStack[level+200] = hashKeyH;\r
+\r
+path[level++] = move;\r
 attacks += 2*BSIZE;\r
 MapFromScratch(attacks); // for as long as incremental update does not work.\r
 if(PATH) pmap(attacks, stm);\r
@@ -1311,7 +1337,7 @@ if(PATH) pmap(attacks, stm);
          if(promoSuppress & PROMOTE) score = -INF; // non-Lion captures Lion after opponent did same\r
          defer |= PROMOTE;                         // if we started, flag  he cannot do it in reply\r
        }\r
-        if(score == -INF) { moveStack[curMove] = 0; goto abortMove; }\r
+        if(score == -INF) { moveStack[curMove] = 0; goto abortMove; } // zap illegal moves\r
       }\r
 #if 1\r
       score = -Search(-beta, -iterAlpha, -difEval - tb.booty, replyDep, promoSuppress & ~PROMOTE, defer);\r
@@ -1319,14 +1345,14 @@ if(PATH) pmap(attacks, stm);
       score = 0;\r
 #endif\r
     abortMove:\r
-\r
-\r
 attacks -= 2*BSIZE;\r
 level--;\r
+    repetition:\r
       UnMake(&tb);\r
       xstm = stm; stm ^= WHITE;\r
 #if 1\r
 if(PATH) printf("%d:%2d:%d %3d %6x %-10s %6d %6d\n", level, depth, iterDep, curMove, moveStack[curMove], MoveToText(moveStack[curMove], 0), score, bestScore);\r
+\r
       // ALPHA-BETA STUFF\r
       if(score > bestScore) {\r
        bestScore = score; bestMoveNr = curMove;\r
@@ -1347,6 +1373,10 @@ if(PATH) printf("%d:%2d:%d %3d %6x %-10s %6d %6d\n", level, depth, iterDep, curM
            // update killer\r
            goto cutoff;\r
          }\r
+         { int i=pvPtr;\r
+           for(pvPtr = myPV+1; pv[pvPtr++] = pv[i++]; ); // copy daughter PV\r
+           pv[myPV] = move;                              // behind our move (pvPtr left at end of copy)\r
+         }\r
        }\r
 \r
       }\r
@@ -1360,7 +1390,8 @@ if(PATH) printf("%d:%2d:%d %3d %6x %-10s %6d %6d\n", level, depth, iterDep, curM
   } while(++iterDep <= depth); // next depth\r
   retMSP = msp;\r
   retFirst = firstMove;\r
-  msp = oldMSP;\r
+  msp = oldMSP; // pop move list\r
+  pvPtr = myPV; // pop PV\r
   retMove = bestMoveNr ? moveStack[bestMoveNr] : 0;\r
 if(flag && depth >= 0) printf("return %d: %d %d\n", depth, bestScore, curEval);\r
   return bestScore + (bestScore < curEval);\r
@@ -1468,15 +1499,23 @@ int lastLift, lastPut;
 int\r
 MakeMove2 (int stm, MOVE move)\r
 {\r
+  int i;\r
   sup0 = sup1; sup1 = sup2;\r
   sup2 = MakeMove(move, &undoInfo);\r
+  rootEval = -rootEval - undoInfo.booty;\r
+  for(i=0; i<200; i++) repStack[i] = repStack[i+1];\r
+  repStack[199] = hashKeyH;\r
+printf("# makemove %08x %c%d %c%d\n", move, sup1%BW+'a', sup1/BW, sup2%BW+'a', sup2/BW);\r
   return stm ^ WHITE;\r
 }\r
 \r
 void\r
 UnMake2 (MOVE move)\r
 {\r
+  int i;\r
+  rootEval = -rootEval - undoInfo.booty;\r
   UnMake(&undoInfo);\r
+  for(i=200; i>0; i--) repStack[i] = repStack[i-1];\r
   sup2 = sup1; sup1 = sup0;\r
 }\r
 \r
@@ -1485,6 +1524,7 @@ Setup2 (char *fen)
 {\r
   SetUp(chuArray, chuPieces);\r
   sup0 = sup1 = sup2 = ABSENT;\r
+  rootEval = cnt50 = hashKeyH = hashKeyL = 0;\r
   return WHITE;\r
 }\r
 \r
@@ -1498,6 +1538,7 @@ MoveToText (MOVE move, int multiLine)
 {\r
   static char buf[50];\r
   int f = move>>SQLEN & SQUARE, g = f, t = move & SQUARE;\r
+  if(f == t) { sprintf(buf, "@@@@"); return buf; } // null-move notation in WB protocol\r
   buf[0] = '\0';\r
   if(t >= SPECIAL) { // kludgy! Print as side effect non-standard WB command to remove victims from double-capture (breaks hint command!)\r
     int e = f + epList[t - SPECIAL];\r
@@ -1530,6 +1571,7 @@ MOVE
 ParseMove (char *moveText)\r
 {\r
   int i, j, f, t, t2, e, ret, deferred=0;\r
+  char c = moveText[0];\r
   moveText += ReadSquare(moveText, &f);\r
   moveText += ReadSquare(moveText, &t); t2 = t;\r
   if(*moveText == ',') {\r
@@ -1546,9 +1588,12 @@ ParseMove (char *moveText)
   }\r
   ret = f<<SQLEN | t2;\r
   if(*moveText == '+') ret |= PROMOTE;\r
+printf("# suppress = %c%d\n", sup1%BW+'a', sup1/BW);\r
 MapFromScratch(attacks);\r
   Search(-INF-1, INF+1, 0, 1, sup1, sup2);\r
   for(i=retFirst; i<retMSP; i++) {\r
+    if(moveStack[i] == INVALID) continue;\r
+    if(c == '@' && (moveStack[i] & SQUARE) == (moveStack[i] >> SQLEN & SQUARE)) break; // any null move matches @@@@\r
     if((moveStack[i] & (PROMOTE | DEFER-1)) == ret) break;\r
     if((moveStack[i] & DEFER-1) == ret) deferred = i; // promoted version of entered non-promotion is legal\r
   }\r
@@ -1634,7 +1679,7 @@ SearchBestMove (int stm, int timeLeft, int mps, int timeControl, int inc, int ti
   tlim2 = 1.9*targetTime;\r
   nodes = 0;\r
 MapFromScratch(attacks);\r
-  score = Search(-INF-1, INF+1, 0, 20, sup1, sup2);\r
+  score = Search(-INF-1, INF+1, rootEval, 20, sup1, sup2);\r
   *move = retMove;\r
   *ponderMove = INVALID;\r
   return score;\r