#include <string.h>
#define DEBUG 0
-
+#define IDQS /* Iteratively deepening QS */
#define LMR 2
#define ON 1
typedef struct { // 12 bytes
unsigned int lock;
- short int score;
+ short int score, lim;
unsigned short int move;
unsigned char depth;
unsigned char flags;
unsigned char checker;
- char age;
+ char age[3];
} HashEntry;
typedef struct {
int pstEval, newEval, bulk, tpGain;
int move, wholeMove, depth;
int checker, checkDir, checkDist, xking;
+ int lim; // for returning upper end of score interval
} StackFrame;
typedef struct { // move stack sectioning
int killer1 = killers[ply][0], killer2 = killers[ply][1], hashMove;
int bestNr, bestScore, startAlpha, startScore, resultDepth, iterDepth=0, originalReduction = reduction;
int hit, hashKeyH, ran=0, ipMask=0;
- int curEval, anaEval, score;
+ int curEval, anaEval, score, upperScore, minScore = -INF, maxScore = INF;
// legality
int earlyGen = (ff->fromPiece == stm+31); // King was moved
- if(ply > 90) { if(DEBUG) Dump("maxply"); ff->depth = 0; return -ff->newEval+150; }
+ if(ply > 90) { if(DEBUG) Dump("maxply"); ff->depth = 0; ff->lim = ff->newEval-150; return -ff->newEval+150; }
f.xking = location[stm+31]; // opponent King, as stm not yet toggled
if(!earlyGen && ff->mutation > 0) { // if other piece was moved (not dropped!), abort with +INF score if it was pinned
if(Pinned(stm, ff->fromSqr, f.xking)) return INF;
}
reduction = 0; // checks are not reduced
}
- if((entry->flags & H_LOWER || entry->score <= alpha) && (entry->flags & H_UPPER || entry->score >= beta)) { // compatible bound
+ if(score >= beta || entry->lim <= alpha || entry->score == entry->lim) { // only take hash cuts from fully resolved results, unless they fail low or high
d += (score >= beta & entry->depth >= LMR)*reduction; // fail highs need to satisfy reduced depth only, so we fake higher depth than actually found
if((score > alpha && d >= depth || d >= maxDepth) && ply) { // sufficient depth
- ff->depth = d + 1; return entry->score; // depth was sufficient, take hash cutoff
+ ff->depth = d + 1; ff->lim = -entry->score; return entry->lim; // depth was sufficient, take hash cutoff
}
}
hashMove = entry->move;
if(!ff->victim) board[ff->toSqr] = stm + 31 ^ COLOR; // kludge: after castling we temporarily make Rook a second King to catch passing through check
kingCapt = MoveGen(stm, &m, f.rights);
board[ff->toSqr] = ff->toPiece; // undo kludge damage
- if(kingCapt) { moveSP = oldSP; ff->depth = MAXPLY; return INF; } // make sure we detect if he moved into check
+ if(kingCapt) { moveSP = oldSP; ff->depth = MAXPLY; ff->lim = -INF; return INF; } // make sure we detect if he moved into check
}
if((++nodeCount & 0xFFF) == 0) abortFlag |= TimeIsUp(3); // check time limit every 4K nodes
beta -= (beta <= curEval);
if(ff->checker == CK_NONE) killers[ply+1][0] = killers[ply+1][1] /* = killers[ply+1][2]*/ = 0;
else if(ply > 0) killers[ply+1][0] = killers[ply-1][0], killers[ply+1][1] = killers[ply-1][1]; // inherit killers after check+evasion
- if(-INF >= beta) { moveSP = oldSP; ff->depth = MAXPLY; return -INF+1; }
+ if(-INF >= beta) { moveSP = oldSP; ff->depth = MAXPLY; ff->lim = INF-1; return INF; }
// check test
if(depth <= 0) { // QS
if(ff->checker != CK_NONE && ff->tpGain > 0) anaEval = 50-INF; // forbid stand pat if horizon check tossed material
if(anaEval > alpha) {
- if(anaEval >= beta) { ff->depth = 1; moveSP = oldSP; anaSP = oldAna; return anaEval + (anaEval < curEval); } // stand-pat cutoff
+ if(anaEval >= beta) { ff->depth = 1; ff->lim = -anaEval - (anaEval < curEval); moveSP = oldSP; anaSP = oldAna; return INF; } // stand-pat cutoff
alpha = startScore = anaEval; maxDepth = 0; // we will not fail low, so no extra iterations
}
if(maxDepth <= 0) {
if(board[toDecode[hashMove&255]] == 0) hashMove = 0;
#ifdef IDQS
- if(ply >= depthLimit) { ff->depth = 1; moveSP = oldSP; anaSP = oldAna; return anaEval + 150; } // hit depth limit; give hefty stm bonus
+ if(ply >= depthLimit) { ff->depth = 1; ff->lim = -anaEval-150; moveSP = oldSP; anaSP = oldAna; return anaEval + 150; } // hit depth limit; give hefty stm bonus
if(depthLimit == MAXPLY) depthLimit = ply+10; // we just entered pure QS; set up depth limit
#endif
}
deprec[ply] = maxDepth << 16 | depth << 8; path[ply++] = 0;
score = -Search(stm, -beta, 1-beta, &f, nullDepth, 0, nullDepth);
ply--;
- if(score >= beta) { ff->depth = f.depth + originalReduction + 3; moveSP = oldSP; anaSP = oldAna; return beta + 1; }
+ if(score >= beta) { ff->depth = f.depth + originalReduction + 3; ff->lim = -beta-1; moveSP = oldSP; anaSP = oldAna; return INF; }
}
// move generation
if(!earlyGen) { // generate moves if we had not done so yet
if(MoveGen(stm, &m, f.rights)) { // impossible (except for hash collision giving wrong in-check status)
- if(DEBUG) Dump("King capture"); ff->depth = MAXPLY; moveSP = oldSP; anaSP = oldAna; return INF;
+ if(DEBUG) Dump("King capture"); ff->depth = MAXPLY; ff->lim = -INF; moveSP = oldSP; anaSP = oldAna; return INF;
}
if(f.checkDist && maxDepth <= 1) ipMask = SafeIP(&f);
}
bonus += bonus >> 1;
int newEval = curEval + bonus;
if(newEval > alpha) {
- if(newEval >= beta) { ff->depth = 1; moveSP = oldSP; depthLimit = oldLimit; anaSP = oldAna; return newEval; } // stand-pat cutoff
+ if(newEval >= beta) { ff->depth = 1; ff->lim = -newEval; moveSP = oldSP; depthLimit = oldLimit; anaSP = oldAna; return INF; } // stand-pat cutoff
alpha = startScore = newEval; maxDepth = 0; // we will not fail low, so no extra iterations
}
}
+ again: // QS IDD loop
do { // IID loop
int curMove, highDepth;
iterDepth++;
highDepth = (iterDepth > depth ? iterDepth : depth) - 1; // reply depth for high-failing moves
alpha = startAlpha;
pvPtr = pvStart; *pvPtr++ = 0; // empty PV
- bestScore = startScore; bestNr = 0; // kludge: points to 0 entry in moveStack
+ bestScore = upperScore = startScore; bestNr = 0; // kludge: points to 0 entry in moveStack
resultDepth = MAXPLY;
m.stage &= 3;
for(curMove=m.firstMove; m.stage<4; curMove++) {
m.unsorted = curMove + 1; // sorted set now includes move
} else {
if(maxDepth <= 0) {
- resultDepth = 0; if(bestScore < anaEval) bestScore = anaEval;
+ resultDepth = 0; if(upperScore < anaEval) upperScore = anaEval;
if(m.stage) break;
moveSP = curMove;
if(ff->checker != CK_NONE && depthLimit != MAXPLY && oldLimit == MAXPLY) { // last move before QS was evasion
else if(gain == pawn || gain >= (400<<21)) score = INF-1; // quasi-repeat with extra piece in hand
else if(gain == -pawn || gain <= (-400<<21)) score = 1-INF; // or with one piece less
else goto search;// traded one hand piece for another; could still lead somewhere
- f.depth = (score >= beta ? highDepth+1 : iterDepth); // minimum required depth
+ f.lim = score; f.depth = (score >= beta ? highDepth+1 : iterDepth); // minimum required depth
*pvPtr = 0; // fake that daughter returned empty PV
} else { // not a repeat: search it
int lmr;
// recursion
deprec[ply] = (f.checker != CK_NONE ? f.checker : 0)<<24 | maxDepth<<16 | depth<< 8 | iterDepth; path[ply++] = moveStack[curMove] & 0xFFFF;
score = -Search(stm, -beta, -alpha+ran, &f, iterDepth-1, lmr, highDepth);
- if(ran && score < INF-100 && score > 100-INF) score += ran;
+ if(ran && score < INF-100 && score > 100-INF) score += ran, f.lim += ran;
ply--;
repKey[index] = oldRepKey; repDep[index] = oldRepDep;
// unmake
UnMake(&f);
- } else score = -INF, f.depth = MAXPLY;
+ } else score = f.lim = -INF, f.depth = MAXPLY;
if(PATH){
int m=moveStack[curMove];
printf("%d:%d:%d %2d. %08x %c%d%c%d %6d %6d %6d\n",ply,depth,iterDepth,curMove,m,(m>>8&255)%22+'a',(m>>8&255)/22+1,toDecode[m&255]%22+'a',toDecode[m&255]/22+1,f.pstEval,score,bestScore);
// minimaxing
if(f.depth < resultDepth) resultDepth = f.depth;
+ if(f.lim > upperScore) upperScore = f.lim;
if(score > bestScore) {
bestScore = score;
if(f.checker == CK_NONE && curMove >= m.nonCapts && moveStack[curMove] != killers[ply][1])
killers[ply][0] = killers[ply][1], killers[ply][1] = moveStack[curMove];
resultDepth = f.depth;
- goto cutoff; // done with this node
+ upperScore = INF; goto cutoff; // done with this node
}
tail = pvPtr; pvPtr = pvStart; *pvPtr++ = moveStack[curMove]; // alpha < score < beta: move starts new PV
while(*pvPtr++ = *tail++); // copy PV of daughter node behind it (including 0 sentinel)
} while(iterDepth < maxDepth && (ply || !TimeIsUp(1))); // IID loop
- if(bestScore == -INF) { // we are mated!
+ if(upperScore == -INF) { // we are mated!
if(perpLoses) { // Shogi
- if(ff->fromSqr == handSlot[stm]) bestScore = INF; // mated by Pawn drop, so we win!
- } else if(f.checker == CK_NONE) bestScore = 0; // stalemate in zh is draw
+ if(ff->fromSqr == handSlot[stm]) bestScore = upperScore = INF; // mated by Pawn drop, so we win!
+ } else if(f.checker == CK_NONE) bestScore = upperScore = 0; // stalemate in zh is draw
}
cutoff:
+
+#ifdef IDQS
+ if(depthLimit != MAXPLY) { // we are in iteratively deepening QS
+ if(bestScore < minScore) { bestScore = minScore; if(upperScore < bestScore) upperScore = bestScore; } // when we aspired with the previous result, fail high and low must mean score is on edge
+ if(upperScore > maxScore) { upperScore = maxScore; if(upperScore < bestScore) bestScore = upperScore; }
+ if(oldLimit == MAXPLY && bestScore != upperScore && bestScore < beta && upperScore > alpha) { // we are in the root of QS and the score is unresolved
+ depthLimit+=10; alpha = startScore = curEval; iterDepth = 0; // increase depth limit and search again
+ goto again;
+ }
+ }
+#endif
+
// delayed-loss bonus
bestScore += (bestScore < curEval);
+ upperScore += (upperScore < curEval);
resultDepth -= (f.checker != CK_NONE); // store unextended depth
// hash store
entry->lock = hashKeyH;
entry->move = moveStack[bestNr]; // if no move was found, bestNr = 0, and moveStack[0] contains INVALID
entry->score = bestScore;
+ entry->lim = upperScore;
entry->depth = resultDepth;
entry->flags = (bestScore > alpha)*H_LOWER + (bestScore < beta)*H_UPPER;
entry->checker = f.checker + 11*(f.checkDist != 0); // encode distant check as off-board checker
// return results
moveSP = oldSP; anaSP = oldAna; pvPtr = pvStart; depthLimit = oldLimit;
ff->depth = resultDepth + 1 + originalReduction; // report valid depth as seen from parent
- return bestScore;
+ ff->lim = -bestScore;
+ return upperScore;
}
int gameMove[MAXMOVES]; // holds the game history