Updating to version 1.3, release made by Mike Vanier (mvanier@bbb.caltech.edu).
[gnushogi.git] / gnushogi / search.c
diff --git a/gnushogi/search.c b/gnushogi/search.c
new file mode 100644 (file)
index 0000000..7a89ef7
--- /dev/null
@@ -0,0 +1,1592 @@
+/*
+ * FILE: search.c
+ *
+ * ----------------------------------------------------------------------
+ * Copyright (c) 1993, 1994, 1995 Matthias Mutz
+ * Copyright (c) 1999 Michael Vanier and the Free Software Foundation
+ *
+ * GNU SHOGI is based on GNU CHESS
+ *
+ * Copyright (c) 1988, 1989, 1990 John Stanback
+ * Copyright (c) 1992 Free Software Foundation
+ *
+ * This file is part of GNU SHOGI.
+ *
+ * GNU Shogi is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 1, or (at your option) any
+ * later version.
+ *
+ * GNU Shogi is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNU Shogi; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * ----------------------------------------------------------------------
+ *
+ */
+
+#include "gnushogi.h"
+
+#if !defined OLDTIME && defined HAVE_GETTIMEOFDAY
+double pow();
+#endif
+
+short background = 0;
+static short DepthBeyond;
+unsigned short PrVar[MAXDEPTH];
+extern short recycle, ISZERO;
+extern void FlagString(unsigned short flags, char *s);
+
+#ifdef NULLMOVE
+short null;         /* Null-move already made or not */
+short PVari;        /* Is this the PV */
+#endif
+
+short zwndw;
+
+
+
+/* ............    MOVE GENERATION & SEARCH ROUTINES    .............. */
+
+
+
+/*
+ *  Check for draw by fourfold repetition
+ *  (same side, same captures, same board).
+ *  WARNING: this is not save (sp? safe?) yet due to possible hash collisions.
+ */
+
+short
+repetition()
+{
+    short i, cnt = 0;
+
+#ifndef NOREPETITION
+    struct GameRec  *g;
+
+    if (GameCnt > Game50 + 6)
+    {
+        for (i = GameCnt - 1; i >= Game50; i -= 2)
+        {
+            g = &GameList[i];
+
+            if (g->hashkey == hashkey && g->hashbd == hashbd)
+                cnt++;
+        }
+    }
+#endif
+
+    return cnt;
+}
+
+
+
+int plyscore, globalscore;
+
+
+/*
+ * Find the best move in the tree between indexes p1 and p2. Swap the best
+ * move into the p1 element.
+ */
+
+int
+pick(short p1, short p2)
+{
+    struct leaf  *p, *q, *r, *k;
+    short s0;
+    struct leaf temp;
+
+    k = p = &Tree[p1];
+    q = &Tree[p2];
+    s0 = p->score;
+
+    for (r = p + 1; r <= q; r++)
+    {
+        if ((r->score) > s0)
+        {
+            s0 = r->score;
+            p = r;
+        }
+    }
+
+    if (p != k)
+    {
+        temp = *p;
+        *p = *k;
+        *k = temp;
+        return true;
+    }
+
+    return false;
+}
+
+int bookflag = false;
+int Jscore = 0;
+
+int TCcount;
+long TCleft = 0;
+
+
+
+
+/*
+ * Select a move by calling function search() at progressively deeper ply
+ * until time is up or a mate or draw is reached. An alpha-beta window of
+ * -Awindow to +Bwindow points is set around the score returned from the
+ * previous iteration. If Sdepth != 0 then the program has correctly
+ * predicted the opponents move and the search will start at a depth of
+ * Sdepth + 1 rather than a depth of 1.
+ */
+
+void
+SelectMove(short side, SelectMove_mode iop)
+{
+    static short i, tempb, tempc, tempsf, tempst, xside, rpt;
+    static short alpha, beta, score;
+    static struct GameRec  *g;
+    short sqking, in_check, blockable;
+
+#ifdef BOOKTEST
+    printf("hashbd = %ld (hashkey >> 16)|side = %d\n",
+           hashbd, (hashkey >> 16)|side);
+#endif
+
+    flag.timeout = false;
+    flag.back = false;
+    flag.musttimeout = false;
+
+    xside = side ^ 1;
+
+#if ttblsz
+    recycle = (GameCnt % rehash) - rehash;
+#endif
+
+    ExaminePosition(side);
+
+    /* if background mode set to infinite */
+    if (iop == BACKGROUND_MODE)
+    {
+        background = true;
+        /* if background mode set response time to infinite */
+        ResponseTime = 9999999;
+    }
+    else
+    {
+        player = side;
+        SetResponseTime(side);
+    }
+
+#ifdef QUIETBACKGROUND
+    if (!background)
+#endif /* QUIETBACKGROUND */
+        ShowResponseTime();
+
+    ExtraTime = 0;
+
+    score = ScorePosition(side);
+
+#ifdef QUIETBACKGROUND
+    if (!background)
+#endif /* QUIETBACKGROUND */
+        ShowSidetoMove();
+
+#ifdef QUIETBACKGROUND
+    if (!background)
+#endif /* QUIETBACKGROUND */
+        SearchStartStuff(side);
+
+#ifdef HISTORY
+    array_zero(history, sizeof_history);
+#endif
+
+    FROMsquare = TOsquare = -1;
+    PV = 0;
+
+    if (iop == FOREGROUND_MODE)
+        hint = 0;
+
+    /*
+     * If the last move was the hint, select the computed answer to the
+     * hint as first move to examine.
+     */
+
+#if MAXDEPTH > 3
+    if (GameCnt > 0)
+    {
+        SwagHt = (GameList[GameCnt].gmove == PrVar[2]) ? PrVar[3] : 0;
+    }
+    else
+#endif
+        SwagHt = 0;
+
+
+    for (i = 0; i < MAXDEPTH; i++)
+        PrVar[i] = killr0[i] = killr1[i] = killr2[i] = killr3[i] = 0;
+
+    /* set initial window for search */
+
+    if (flag.tsume)
+    {
+        alpha =  -(SCORE_LIMIT + 999);
+        beta = SCORE_LIMIT + 999;
+    }
+    else
+    {
+        alpha = score - ((computer == white) ? BAwindow : WAwindow);
+        beta  = score + ((computer == white) ? BBwindow : WBwindow);
+    }
+
+    rpt = 0;
+    TrPnt[1] = 0;
+    root = &Tree[0];
+
+    sqking = PieceList[side][0];
+    in_check = (board[sqking] == king)
+        ? SqAttacked(sqking, side^1, &blockable)
+        : false;
+
+    MoveList(side, 1, in_check, blockable);
+
+    for (i = TrPnt[1]; i < TrPnt[2]; i++)
+    {
+        if (!pick(i, TrPnt[2] - 1))
+            break;
+    }
+
+    /* Can I get a book move? */
+
+    if (flag.regularstart && Book)
+    {
+        flag.timeout = bookflag = OpeningBook(&hint, side);
+
+        if (TCflag)
+            ResponseTime += ResponseTime;
+    }
+
+    /* Zero stats for hash table. */
+
+    reminus = replus = 0;
+    GenCnt = NodeCnt = ETnodes = EvalNodes = HashCnt
+        = FHashAdd = HashAdd = FHashCnt = THashCol = HashCol = 0;
+
+    globalscore = plyscore = score;
+    Jscore = 0;
+    zwndw = 20;
+
+
+    /********************* main loop ********************************/
+
+    Sdepth = (MaxSearchDepth < (MINDEPTH - 1))
+        ? MaxSearchDepth
+        : (MINDEPTH - 1);
+
+    while (!flag.timeout)
+    {
+        /* go down a level at a time */
+        Sdepth++;
+
+#ifdef NULLMOVE
+        null = 0;
+        PVari = 1;
+#endif
+
+        /* terminate search at DepthBeyond ply past goal depth */
+        if (flag.tsume)
+            DepthBeyond = Sdepth;
+        else
+#if defined SLOW_CPU
+            DepthBeyond = Sdepth + ((Sdepth == 1) ? 3 : 5);
+#else
+        DepthBeyond = Sdepth + ((Sdepth == 1) ? 7 : 11);
+#endif
+
+# ifdef QUIETBACKGROUND
+        if (!background)
+#endif /* QUIETBACKGROUND */
+            ShowDepth(' ');
+
+        /* search at this level returns score of PV */
+        score = search(side, 1, Sdepth, alpha, beta, PrVar, &rpt);
+
+        /* save PV as killer */
+        for (i = 1; i <= Sdepth; i++)
+            killr0[i] = PrVar[i];
+
+        /* low search failure re-search with (-inf, score) limits  */
+        if (score < alpha)
+        {
+            reminus++;
+#ifdef QUIETBACKGROUND
+            if (!background)
+#endif /* QUIETBACKGROUND */
+                ShowDepth('-');
+
+            if (TCflag && TCcount < MAXTCCOUNTR)
+            {
+                if (hard_time_limit)
+                    ExtraTime += (MAXTCCOUNTR - TCcount) * TCleft;
+                else
+                    ExtraTime += (8 * TCleft);
+
+                TCcount = MAXTCCOUNTR - 1;
+            }
+
+            score = search(side, 1, Sdepth, -(SCORE_LIMIT + 999),
+                           (SCORE_LIMIT + 999), PrVar, &rpt);
+        }
+        /* high search failure re-search with (score, +inf) limits */
+        else if (score > beta && !(root->flags & exact))
+        {
+            replus++;
+#ifdef QUIETBACKGROUND
+            if (!background)
+#endif /* QUIETBACKGROUND */
+                ShowDepth('+');
+
+            score = search(side, 1, Sdepth, -(SCORE_LIMIT + 999),
+                           (SCORE_LIMIT + 999), PrVar, &rpt);
+        }
+
+        /**************** out of search ***********************************/
+        CheckForTimeout(score, globalscore, Jscore, zwndw);
+
+        /************************ time control ****************************/
+
+        /* save PV as killer */
+        for (i = 1; i <= Sdepth + 1; i++)
+            killr0[i] = PrVar[i];
+
+        if (!flag.timeout)
+            Tscore[0] = score;
+
+        /* if (!flag.timeout) */
+        /*
+          for (i = TrPnt[1] + 1; i < TrPnt[2]; i++)
+          if (!pick (i, TrPnt[2] - 1))
+          break;
+        */
+
+        /* if done or nothing good to look at quit */
+        if ((root->flags & exact) || (score < -SCORE_LIMIT))
+            flag.timeout = true;
+
+        /* find the next best move put below root */
+
+        if (!flag.timeout)
+        {
+#if !defined NODYNALPHA
+            Jscore = (plyscore + score) >> 1;
+#endif
+            zwndw = 20 + abs(Jscore / 12);
+            plyscore = score;
+
+            /* recompute search window */
+            beta = score + ((computer == white) ? BBwindow : WBwindow);
+#if !defined NODYNALPHA
+            alpha = ((Jscore < score) ? Jscore : score)
+                - ((computer == white) ? BAwindow : WAwindow)
+                - zwndw;
+#else
+            alpha = score - ((computer == white) ? BAwindow : WAwindow);
+#endif
+        }
+
+#ifdef QUIETBACKGROUND
+        if (!background)
+#endif /* QUIETBACKGROUND */
+            ShowResults(score, PrVar, '.');
+    }
+
+    /********************** end of main loop ***************************/
+
+    /* background mode */
+    if (iop == BACKGROUND_MODE)
+        return;
+
+    if (rpt >= 3)
+    {
+        root->flags |= draw;
+        DRAW = CP[101];     /* Repetition */
+    }
+    else
+    {
+        /*
+         * If there are no moves and we're not in check (stalemate) then
+         * it's mate in shogi (whereas it's a draw in chess).
+         */
+
+        if (GameCnt == MAXMOVES)
+        {
+            root->flags |= draw;
+            DRAW = CP[80];      /* Max Moves */
+        }
+    }
+
+    /* not in book so set hint to guessed move for other side */
+    if (!bookflag)
+        hint = ((PrVar[1]) ? PrVar[2] : 0);
+
+    /* if not mate or draw make move and output it */
+    if (((score > -(SCORE_LIMIT + 999))
+         && (rpt <= 3)) || (root->flags & draw))
+    {
+        MakeMove(side, &Tree[0], &tempb, &tempc,
+                 &tempsf, &tempst, &INCscore);
+        algbr(root->f, root->t, (short) root->flags);
+    }
+    else
+    {
+        algbr(0, 0, 0);       /* Zero move string when mate. */
+        root->score = score;  /* When mate, ignore distinctions!
+                               * --SMC */
+    }
+
+    g = &GameList[GameCnt];
+
+    if ((g->flags & capture) && (g->piece == king))
+        flag.mate = flag.illegal = true;
+
+    /* If Time Control get the elapsed time */
+    if (TCflag)
+        ElapsedTime(COMPUTE_AND_INIT_MODE);
+
+    /* update time control info */
+    OutputMove();
+
+    /* if mate set flag */
+    if ((score == -(SCORE_LIMIT + 999) || score == (SCORE_LIMIT + 998)))
+        flag.mate = true;
+
+    /* add move to game list */
+    g->score = score;
+    g->nodes = NodeCnt;
+    g->time = (et +50)/100;
+    /* g->time = TCcount; */
+    g->depth = Sdepth;
+
+    /* update time control info */
+    if (TCflag)
+    {
+        TimeControl.clock[side] -= (et + OperatorTime);
+        timecomp[compptr] = (et + OperatorTime);
+
+        /* finished our required moves - setup the next set */
+        --TimeControl.moves[side];
+    }
+
+    /* check for end conditions */
+    if ((root->flags & draw) /* && flag.bothsides */)
+    {
+        flag.mate = true;
+    }
+    else if (GameCnt == MAXMOVES)
+    {
+        flag.mate = true;
+    }
+    /* out of move store, you lose */
+    else
+    {
+        /* switch to other side */
+        player = xside;
+    }
+
+    /* if mate clear hint */
+    if (flag.mate)
+        hint = 0;
+
+    Sdepth = 0;
+}
+
+
+
+/*
+ * Perform an alpha-beta search to determine the score for the current
+ * board position.  If depth <= 0 only capturing moves and responses to
+ * check are generated and searched, otherwise all moves are processed. The
+ * search depth is modified for check evasions, certain re-captures and
+ * threats.  Extensions may continue for up to 11 ply beyond the nominal
+ * search depth.
+ */
+
+int
+search(short side,
+       short ply,
+       short depth,
+       short alpha,
+       short beta,
+       unsigned short *bstline,
+       short *rpt)
+{
+    short j, pnt;
+    short tempb, tempc, tempsf, tempst;
+    short xside, pbst, score, rcnt, in_check, blockable;
+    unsigned short mv, nxtline[MAXDEPTH];
+    struct leaf  *node, tmp;
+    short best = -(SCORE_LIMIT + 3000);
+    short bestwidth = 0;
+    short mustcut;
+
+#ifdef NULLMOVE
+    short PVsave;
+    short PVarisave;
+#endif
+
+    NodeCnt++;
+
+    /* look every ZNODE nodes for a timeout */
+#ifdef NULLMOVE
+    if (!null)
+    {
+#endif
+        if (NodeCnt > ETnodes)
+        {
+            ElapsedTime(COMPUTE_MODE);
+
+            if (flag.back)
+            {
+                flag.back = false;
+                flag.timeout = true;
+                flag.musttimeout = false;
+            }
+            else if (TCflag || MaxResponseTime)
+            {
+                if ((et >= (ResponseTime + ExtraTime))
+                    && (Sdepth > MINDEPTH))
+                {
+                    /* try to extend to finish ply */
+                    if (flag.back || (TCflag && TCcount < MAXTCCOUNTX))
+                    {
+                        flag.back = false;
+                        flag.musttimeout = true;
+                        TCcount++;
+                        ExtraTime += TCleft;
+                    }
+                    else
+                    {
+                        flag.back = false;
+                        flag.timeout = true;
+                        flag.musttimeout = false;
+                    }
+                }
+            }
+            else if (flag.back)
+            {
+                flag.back = false;
+                flag.timeout = true;
+                flag.musttimeout = false;
+            }
+
+#ifdef QUIETBACKGROUND
+            if (!background)
+#endif
+                ShowResponseTime();
+        }
+        else if (!TCflag && flag.musttimeout && Sdepth > MINDEPTH)
+        {
+            flag.timeout = true;
+            flag.musttimeout = false;
+        }
+#ifdef NULLMOVE
+    }
+#endif
+
+    xside = side ^ 1;
+    score = evaluate(side, ply, alpha, beta,
+                     INCscore, &in_check, &blockable);
+
+    /*
+     * check for possible repitition if so call repitition - rpt is
+     * repeat count
+     */
+
+    if ((ply <= Sdepth + 3) && rpthash[side][hashkey & 0xFF] > 0)
+    {
+        *rpt = repetition();
+
+        /*
+         * repeat position >3 don't need to return score it's taken
+         * care of above
+         */
+
+        if (*rpt == 1)
+        {
+            score /= 3;
+            score *= 2;
+        }
+        else if (*rpt == 2)
+            score /= 2;
+    }
+    else
+    {
+        *rpt = 0;
+    }
+
+    /* score > SCORE_LIMIT its a draw or mate */
+    if (score > SCORE_LIMIT)
+    {
+        bstline[ply] = 0;
+        return score;
+    }
+
+    /* Do we need to add depth because of special conditions */
+    /* if in check or in capture sequence search deeper */
+
+    /***************** depth extensions *****************/
+
+    if (depth > 0)
+    {
+        /* Allow opponent a chance to check again */
+        if (in_check)
+        {
+            if (depth < 2)
+                depth = 2;
+        }
+        else if (flag.rcptr
+                 && (score > alpha) && (score < beta)
+                 && (ply > 2)
+                 && CptrFlag[ply - 1] && CptrFlag[ply - 2])
+        {
+            if (hard_time_limit)
+            {
+                if (!flag.timeout)
+                    ++depth;
+            }
+            else
+            {
+                ++depth;
+            }
+
+        }
+    }
+    else
+    {
+        short timeout = 0;
+
+        if (hard_time_limit)
+            timeout = flag.timeout;
+
+        if ((score >= alpha)
+            && (in_check
+                || ((!timeout && (hung[side] > 1))
+                    && (ply == Sdepth + 1))))
+        {
+            depth = 1;
+        }
+        else if ((score <= beta)
+                 && (((ply < Sdepth + 4) && (ply > 4))
+                     && ChkFlag[ply - 2]
+                     && ChkFlag[ply - 4]
+                     && (ChkFlag[ply - 2] != ChkFlag[ply - 4])))
+        {
+            depth = 1;
+        }
+    }
+
+    /***************************************************/
+    /* try the local transition table if it's there */
+
+#if ttblsz
+    if (/* depth > 0 && */ flag.hash && ply > 1)
+    {
+        if (use_ttable
+            && ProbeTTable(side, depth, ply, &alpha, &beta, &score) == true)
+        {
+            bstline[ply] = PV;
+            bstline[ply + 1] = 0;
+
+            if (beta == -((SCORE_LIMIT + 1000) * 2))
+                return score;
+
+            if (alpha > beta)
+                return alpha;
+        }
+
+#ifdef HASHFILE
+        /* ok try the transition file if its there */
+        else if (hashfile
+                 && (depth > HashDepth)
+                 && (GameCnt < HashMoveLimit)
+                 && (ProbeFTable(side, depth, ply, &alpha, &beta, &score)
+                     == true))
+        {
+            PutInTTable(side, score, depth, ply, alpha, beta, PV);
+            bstline[ply] = PV;
+            bstline[ply + 1] = 0;
+
+            if (beta == -((SCORE_LIMIT + 1000) * 2))
+                return score;
+
+            if (alpha > beta)
+            {
+                return alpha;
+            }
+        }
+#endif /* HASHFILE */
+    }
+#endif /* ttblsz */
+
+    if (TrPnt[ply] > (TREE - 300))
+        mustcut = true;
+    else
+        mustcut = false;
+
+    /*
+     * If more then DepthBeyond ply past goal depth or at goal depth and
+     * score > beta quit - means we are out of the window.
+     */
+
+    if (mustcut || (ply > DepthBeyond) || ((depth < 1) && (score > beta)))
+        return score;
+
+    /*
+     * If below first ply and not at goal depth generate all moves else
+     * only capture moves.
+     */
+
+    if (ply > 1)
+    {
+        if ((depth > 0) || (ply < (SDEPTHLIM))
+            || (background && (ply < Sdepth + 2)))
+            MoveList(side, ply, in_check, blockable);
+        else
+            CaptureList(side, ply, in_check, blockable);
+    }
+
+    /* no moves return what we have */
+
+    /*
+     * normally a search will continue til past goal and no more capture
+     * moves exist
+     */
+
+    /* unless it hits DepthBeyond */
+    if (TrPnt[ply] == TrPnt[ply + 1])
+        return score;
+
+    /* if not at goal set best = -inf else current score */
+    best = (depth > 0) ? -(SCORE_LIMIT + 3000) : score;
+
+#ifdef NULLMOVE
+
+    PVarisave = PVari;
+
+    /* CHECKME: is the & really an && here? */
+    if (!null  &&                        /* no previous null-move */
+        !PVari &&                        /* no null-move during the PV */
+        (ply > 2) &                      /* not at ply 1 */
+        (ply <= Sdepth) &&
+        (depth > 3) &&
+        !in_check)                       /* no check */
+        /* enough material such that zugzwang is unlikely
+         * but who knows which value is suitable? */
+    {
+        /*
+           OK, we make a null move, i.e.  this means we have nothing to do
+           but we have to keep the some arrays up to date otherwise gnushogi
+           gets confused.  Maybe somebody knows exactly which information is
+           important and which isn't.
+
+           Another idea is that we try the null-move first and generate the
+           moves later.  This may save time but we have to take care that
+           PV and other variables contain the right value so that the move
+           ordering works right.
+        */
+
+        struct GameRec  *g;
+
+        nxtline[ply + 1] = 0;
+        CptrFlag[ply] = 0;
+        TesujiFlag[ply] = 0;
+        Tscore[ply] = score;
+        PVsave = PV;
+        PV = 0;
+        null = 1;
+        g = &GameList[++GameCnt];
+        g->hashkey = hashkey;
+        g->hashbd = hashbd;
+        FROMsquare = TOsquare = -1;
+        g->Game50 = Game50;
+        g->gmove = -1;
+        g->flags = 0;
+        g->piece = 0;
+        g->color = neutral;
+
+        best = -search(xside, ply + 1, depth - 2,
+                       -beta - 1, -beta, nxtline, &rcnt);
+        null = 0;
+        PV = PVsave;
+        GameCnt--;
+
+        if (best < alpha)
+        {
+            best = -(SCORE_LIMIT + 3000);
+        }
+        else if (best > beta)
+        {
+            return best;
+        }
+        else
+        {
+            best = -(SCORE_LIMIT + 3000);
+        }
+    }
+#endif
+
+    /* if best so far is better than alpha set alpha to best */
+    if (best > alpha)
+        alpha = best;
+
+    /********************** main loop ****************************/
+
+    /* look at each move until no more or beta cutoff */
+    for (pnt = pbst = TrPnt[ply];
+         (pnt < TrPnt[ply + 1]) && (best <= beta);
+         pnt++)
+    {
+        /* find the most interesting looking of the remaining moves */
+        if (ply > 1)
+            pick(pnt, TrPnt[ply + 1] - 1);
+
+#ifdef NULLMOVE
+        PVari = PVarisave && (pnt == TrPnt[ply]);  /* Is this the PV? */
+#endif
+
+        node = &Tree[pnt];
+
+        /* is this a forbidden move */
+        if (/* ply == 1 && */ node->score <= DONTUSE)
+            continue;
+
+        nxtline[ply + 1] = 0;
+
+        /* if at top level */
+#if !defined NOPOST
+        if (ply == 1)
+        {
+/* at the top update search status */
+            if (flag.post)
+            {
+#ifdef QUIETBACKGROUND
+                if (!background)
+#endif /* QUIETBACKGROUND */
+                    ShowCurrentMove(pnt, node->f, node->t);
+            }
+        }
+#endif
+
+        if (!(node->flags & exact))
+        {
+            /* make the move and go deeper */
+
+            MakeMove(side, node, &tempb, &tempc, &tempsf,
+                     &tempst, &INCscore);
+            CptrFlag[ply] = (node->flags & capture);
+            TesujiFlag[ply] = (node->flags & tesuji)
+                && (node->flags & dropmask);
+            Tscore[ply] = node->score;
+            PV = node->reply;
+
+            node->score = -search(xside, ply + 1,
+                                  (depth > 0) ? (depth - 1) : 0,
+                                  -beta, -alpha,
+                                  nxtline, &rcnt);
+
+            /*
+             * if(!flag.timeout)
+             *     node->score = score;
+             */
+
+            node->width = ((ply % 2) == 1)
+                ? (TrPnt[ply + 2] - TrPnt[ply + 1])
+                : 0;
+
+            if ((node->score > SCORE_LIMIT) || (node->score < -SCORE_LIMIT) )
+                node->flags |= exact;
+            else if (rcnt == 1)
+                node->score /= 2;
+
+            if (((rcnt >= 3)
+                 || ((node->score == (SCORE_LIMIT + 999) - ply)
+                     && !ChkFlag[ply])))
+            {
+                node->flags |= (draw | exact);
+                DRAW = CP[58];  /* Draw */
+                node->score = ((side == computer) ? contempt : -contempt);
+            }
+
+            node->reply = nxtline[ply + 1];
+
+            /* reset to try next move */
+            UnmakeMove(side, node, &tempb, &tempc, &tempsf, &tempst);
+        }
+
+        /* if best move so far */
+        /* CHECKME: flag.timeout isn't valid if no hard time limit */
+        if (!flag.timeout
+            && ((node->score > best)
+                || ((node->score == best) && (node->width > bestwidth))))
+        {
+            /*
+             * All things being equal pick the denser part of the
+             * tree.
+             */
+            bestwidth = node->width;
+
+            /*
+             * If not at goal depth and better than alpha and not
+             * an exact score increment by depth.
+             */
+
+            if ((depth > 0) && (node->score > alpha)
+                && !(node->flags & exact))
+            {
+                node->score += depth;
+            }
+
+            best = node->score;
+            pbst = pnt;
+
+            if (best > alpha)
+                alpha = best;
+
+            /* update best line */
+            for (j = ply + 1; nxtline[j] > 0; j++)
+                bstline[j] = nxtline[j];
+
+            bstline[j] = 0;
+            bstline[ply] = (node->f << 8) | node->t;
+
+            /* if at the top */
+            if (ply == 1)
+            {
+                /*
+                 * If it's better than the root score make it the root.
+                 */
+                if ((best > root->score)
+                    || ((best == root->score)
+                        && (bestwidth > root->width)))
+                {
+                    tmp = Tree[pnt];
+
+                    for (j = pnt - 1; j >= 0; j--)
+                        Tree[j + 1] = Tree[j];
+
+                    Tree[0] = tmp;
+                    pbst = 0;
+                }
+
+#ifdef QUIETBACKGROUND
+                if (!background)
+                {
+#endif /* QUIETBACKGROUND */
+                    if (Sdepth > 2)
+                    {
+                        if (best > beta)
+                        {
+                            ShowResults(best, bstline, '+');
+                        }
+                        else if (best < alpha)
+                        {
+                            ShowResults(best, bstline, '-');
+                        }
+                        else
+                        {
+                            ShowResults (best, bstline, '&');
+                        }
+                    }
+#ifdef QUIETBACKGROUND
+                }
+#endif
+            }
+        }
+
+        if (flag.timeout)
+            return Tscore[ply - 1];
+    }
+
+    /******************************************************/
+
+    node = &Tree[pbst];
+    mv = (node->f << 8) | node->t;
+
+#ifdef NULLMOVE
+    PVari = PVarisave;
+#endif
+
+    /*
+     * We have a move so put it in local table - if it's already there
+     * done else if not there or needs to be updated also put it in
+     * hashfile
+     */
+
+#if ttblsz
+    if (flag.hash && ply <= Sdepth && *rpt == 0 && best == alpha)
+    {
+#  ifdef HASHFILE /* MCV: warning: this confuses the formatter. */
+        if (use_ttable
+            && PutInTTable(side, best, depth, ply, alpha, beta, mv)
+            && hashfile
+            && (depth > HashDepth)
+            && (GameCnt < HashMoveLimit))
+#  else
+        if (use_ttable
+            && PutInTTable(side, best, depth, ply, alpha, beta, mv))
+#  endif
+        {
+            PutInFTable(side, best, depth, ply,
+                        alpha, beta, node->f, node->t);
+        }
+    }
+#endif /* ttblsz */
+
+    if (depth > 0)
+    {
+#if defined HISTORY
+        unsigned short h, x;
+        h = mv;
+
+        if (history[x = hindex(side, h)] < HISTORYLIM)
+            history[x] += (unsigned short) 1 << depth;
+#endif
+
+        if (node->t != (short)(GameList[GameCnt].gmove & 0xFF))
+        {
+            if (best <= beta)
+            {
+                killr3[ply] = mv;
+            }
+            else if (mv != killr1[ply])
+            {
+                killr2[ply] = killr1[ply];
+                killr1[ply] = mv;
+            }
+        }
+
+        killr0[ply] = ((best > SCORE_LIMIT) ? mv : 0);
+    }
+
+    return best;
+}
+
+
+
+/*
+ * Update the PieceList and Pindex arrays when a piece is captured or when a
+ * capture is unmade.
+ */
+
+void
+UpdatePieceList(short side, short sq, UpdatePieceList_mode iop)
+{
+    short i;
+
+    if (iop == REMOVE_PIECE)
+    {
+        PieceCnt[side]--;
+
+        for (i = Pindex[sq]; i <= PieceCnt[side]; i++)
+        {
+            PieceList[side][i] = PieceList[side][i + 1];
+            Pindex[PieceList[side][i]] = i;
+        }
+    }
+    else if (board[sq] == king)
+    {
+        /* king must have index 0 */
+        for (i = PieceCnt[side]; i >= 0; i--)
+        {
+            PieceList[side][i + 1] = PieceList[side][i];
+            Pindex[PieceList[side][i + 1]] = i + 1;
+        }
+
+        PieceCnt[side]++;
+        PieceList[side][0] = sq;
+        Pindex[sq] = 0;
+    }
+    else
+    {
+        PieceCnt[side]++;
+        PieceList[side][PieceCnt[side]] = sq;
+        Pindex[sq] = PieceCnt[side];
+    }
+}
+
+
+
+/* Make or Unmake drop move. */
+
+void
+drop(short side, short piece, short f, short t, short iop)
+{
+    if (iop == 1)
+    {
+        short n;
+        board[t] = piece;
+        color[t] = side;
+
+#if !defined SAVE_SVALUE
+        svalue[t] = 0;
+#endif
+
+        n = Captured[side][piece]--;
+
+        UpdateDropHashbd(side, piece, n);
+        UpdateHashbd(side, piece, -1, t);
+        UpdatePieceList(side, t, ADD_PIECE);
+
+        if (piece == pawn)
+        {
+            ++PawnCnt[side][column(t)];
+        }
+
+        Mvboard[t]++;
+        HasPiece[side][piece]++;
+    }
+    else
+    {
+        short n;
+        board[t] = no_piece;
+        color[t] = neutral;
+        n = ++Captured[side][piece];
+
+        UpdateDropHashbd(side, piece, n);
+        UpdateHashbd(side, piece, -1, t);
+        UpdatePieceList(side, t, REMOVE_PIECE);
+
+        if (piece == pawn)
+            --PawnCnt[side][column(t)];
+
+        Mvboard[t]--;
+        HasPiece[side][piece]--;
+    }
+}
+
+
+#ifdef HASHKEYTEST
+int
+CheckHashKey()
+{
+    unsigned long chashkey, chashbd;
+    short side, sq;
+    chashbd = chashkey = 0;
+
+    for (sq = 0; sq < NO_SQUARES; sq++)
+    {
+        if (color[sq] != neutral)
+        {
+            chashbd ^= (*hashcode)[color[sq]][board[sq]][sq].bd;
+            chashkey ^= (*hashcode)[color[sq]][board[sq]][sq].key;
+        }
+
+        /* hashcodes for initial board are 0 ! */
+        if (Stcolor[sq] != neutral)
+        {
+            chashbd ^= (*hashcode)[Stcolor[sq]][Stboard[sq]][sq].bd;
+            chashkey ^= (*hashcode)[Stcolor[sq]][Stboard[sq]][sq].key;
+        }
+    }
+
+    for (side = 0; side <= 1; side++)
+    {
+        short piece;
+
+        for (piece = 0; piece < NO_PIECES; piece++)
+        {
+            short n = Captured[side][piece];
+
+            if (n > 0)
+            {
+                short i;
+
+                for (i = 1; i <= n; i++)
+                {
+                    chashbd ^= (*drop_hashcode)[side][piece][i].bd;
+                    chashkey ^= (*drop_hashcode)[side][piece][i].key;
+                }
+            }
+        }
+    }
+
+    if (chashbd != hashbd)
+        printf("chashbd %lu != hashbd %lu\n", chashbd, hashbd);
+
+    if (chashkey != hashkey)
+        printf("chashkey %lu != hashkey %lu\n", chashkey, hashkey);
+
+    if (chashbd != hashbd || chashkey != hashkey)
+        return 1;
+
+    return 0;
+}
+#endif
+
+
+
+
+/*
+ * Update Arrays board[], color[], and Pindex[] to reflect the new board
+ * position obtained after making the move pointed to by node. Also update
+ * miscellaneous stuff that changes when a move is made.
+ */
+
+void
+MakeMove(short side,
+         struct leaf  *node,
+         short *tempb,  /* piece at to square */
+         short *tempc,  /* color of to square */
+         short *tempsf, /* static value of piece on from */
+         short *tempst, /* static value of piece on to */
+         short *INCscore)   /* score increment */
+{
+    short f, t, xside;
+    struct GameRec  *g;
+    short fromb, fromc;
+
+    xside = side ^ 1;
+    g = &GameList[++GameCnt];
+    g->hashkey = hashkey;
+    g->hashbd = hashbd;
+    FROMsquare = f = node->f;
+    TOsquare = t = (node->t & 0x7f);
+    *INCscore = (short)node->INCscore;
+    g->Game50 = Game50;
+    g->gmove = (f << 8) | node->t;
+    g->flags = node->flags;
+
+#ifdef HASHKEYTEST
+    if (CheckHashKey())
+    {
+        short i;
+        algbr(f, t, node->flags);
+        printf("error before MakeMove: %s\n", mvstr[0]);
+        UpdateDisplay(0, 0, 1, 0);
+
+        for (i = 1; i <= GameCnt; i++)
+        {
+            movealgbr(GameList[i].gmove, mvstr[0]);
+            printf("%d: %s\n", i, mvstr[0]);
+        }
+
+        exit(1);
+    }
+#endif
+
+    rpthash[side][hashkey & 0xFF]++, ISZERO++;
+
+    if (f > NO_SQUARES)
+    {
+        g->fpiece = (node->flags & pmask);
+        g->piece = *tempb = no_piece;
+        g->color = *tempc = neutral;
+
+#if !defined SAVE_SVALUE
+        *tempsf = 0;
+        *tempst = svalue[t];
+#endif
+
+        (void)drop(side, g->fpiece, f, t, 1);
+    }
+    else
+    {
+#if !defined SAVE_SVALUE
+        *tempsf = svalue[f];
+        *tempst = svalue[t];
+#endif
+
+        g->fpiece = board[f];
+        g->piece = *tempb = board[t];
+        g->color = *tempc = color[t];
+        fromb = board[f];
+        fromc = color[f];
+
+        if (*tempc != neutral)
+        {
+            /* Capture a piece */
+            UpdatePieceList(*tempc, t, REMOVE_PIECE);
+
+            /* if capture decrement pawn count */
+            if (*tempb == pawn)
+                --PawnCnt[*tempc][column(t)];
+
+            mtl[xside] -= (*value)[stage][*tempb];
+            HasPiece[xside][*tempb]--;
+
+            {
+                short n, upiece = unpromoted[*tempb];
+
+                /* add "upiece" captured by "side" */
+                n = ++Captured[side][upiece];
+
+                UpdateDropHashbd(side, upiece, n);
+                mtl[side] += (*value)[stage][upiece];
+            }
+
+            /* remove "*tempb" of "xside" from board[t] */
+            UpdateHashbd(xside, *tempb, -1, t);
+
+#if !defined SAVE_SVALUE
+            *INCscore += *tempst; /* add value of catched piece
+                                   * to own score */
+#endif
+
+            Mvboard[t]++;
+        }
+
+        color[t] = fromc;
+
+#if !defined SAVE_SVALUE
+        svalue[t] = svalue[f];
+        svalue[f] = 0;
+#endif
+
+        Pindex[t] = Pindex[f];
+        PieceList[side][Pindex[t]] = t;
+        color[f] = neutral;
+        board[f] = no_piece;
+
+        if (node->flags & promote)
+        {
+            short tob;
+
+            board[t] = tob = promoted[fromb];
+
+            /* remove unpromoted piece from board[f] */
+            UpdateHashbd(side, fromb, f, -1);
+
+            /* add promoted piece to board[t] */
+            UpdateHashbd(side, tob, -1, t);
+            mtl[side] += value[stage][tob] - value[stage][fromb];
+
+            if (fromb == pawn)
+                --PawnCnt[side][column(f)];
+
+            HasPiece[side][fromb]--;
+            HasPiece[side][tob]++;
+
+#if !defined SAVE_SVALUE
+            *INCscore -= *tempsf;
+#endif
+        }
+        else
+        {
+            board[t] = fromb;
+            /* remove piece from board[f] and add it to board[t] */
+            UpdateHashbd(side, fromb, f, t);
+        }
+
+        Mvboard[f]++;
+    }
+
+#ifdef HASHKEYTEST
+    algbr(f, t, node->flags);
+
+    if (CheckHashKey())
+    {
+        printf("error in MakeMove: %s\n", mvstr[0]);
+        exit(1);
+    }
+#endif
+}
+
+
+
+
+/*
+ * Take back a move.
+ */
+
+void
+UnmakeMove(short side,
+           struct leaf  *node,
+           short *tempb,
+           short *tempc,
+           short *tempsf,
+           short *tempst)
+{
+    short f, t, xside;
+
+    xside = side ^ 1;
+    f = node->f;
+    t = node->t & 0x7f;
+    Game50 = GameList[GameCnt].Game50;
+
+    if (node->flags & dropmask)
+    {
+        (void)drop(side, (node->flags & pmask), f, t, 2);
+
+#if !defined SAVE_SVALUE
+        svalue[t] = *tempst;
+#endif
+    }
+    else
+    {
+        short tob, fromb;
+
+        color[f] = color[t];
+        board[f] = tob = fromb = board[t];
+
+#if !defined SAVE_SVALUE
+        svalue[f] = *tempsf;
+#endif
+
+        Pindex[f] = Pindex[t];
+        PieceList[side][Pindex[f]] = f;
+        color[t] = *tempc;
+        board[t] = *tempb;
+
+#if !defined SAVE_SVALUE
+        svalue[t] = *tempst;
+#endif
+
+        /* Undo move */
+        if (node->flags & promote)
+        {
+            board[f] = fromb = unpromoted[tob];
+            mtl[side] += value[stage][fromb] - value[stage][tob];
+
+            if (fromb == pawn)
+                ++PawnCnt[side][column(f)];
+
+            HasPiece[side][fromb]++;
+            HasPiece[side][tob]--;
+
+            /* add unpromoted piece to board[f] */
+            UpdateHashbd(side, fromb, f, -1);
+
+            /* remove promoted piece from board[t] */
+            UpdateHashbd(side, tob, -1, t);
+        }
+        else
+        {
+            if (fromb == pawn)
+            {
+                --PawnCnt[side][column(t)];
+                ++PawnCnt[side][column(f)];
+            };
+
+            /* remove piece from board[t] and add it to board[f] */
+            UpdateHashbd(side, fromb, f, t);
+        }
+
+        /* Undo capture */
+        if (*tempc != neutral)
+        {
+            short n, upiece = unpromoted[*tempb];
+
+            UpdatePieceList(*tempc, t, ADD_PIECE);
+
+            if (*tempb == pawn)
+                ++PawnCnt[*tempc][column(t)];
+
+            mtl[xside] += (*value)[stage][*tempb];
+            HasPiece[xside][*tempb]++;
+            mtl[side] -= (*value)[stage][upiece];
+
+            /* remove "upiece" captured by "side" */
+            n = Captured[side][upiece]--;
+
+            UpdateDropHashbd(side, upiece, n);
+
+            /* replace captured piece on board[t] */
+            UpdateHashbd(xside, *tempb, -1, t);
+            Mvboard[t]--;
+        }
+
+        Mvboard[f]--;
+    }
+
+    GameCnt--;
+    rpthash[side][hashkey & 0xFF]--, ISZERO--;
+
+#ifdef HASHKEYTEST
+    algbr(f, t, node->flags);
+
+    if (CheckHashKey())
+    {
+        printf("error in UnmakeMove: %s\n", mvstr[0]);
+        exit(1);
+    }
+#endif
+}
+
+
+
+/*
+ * Scan thru the board seeing what's on each square. If a piece is found,
+ * update the variables PieceCnt, PawnCnt, Pindex and PieceList. Also
+ * determine the material for each side and set the hashkey and hashbd
+ * variables to represent the current board position. Array
+ * PieceList[side][indx] contains the location of all the pieces of either
+ * side. Array Pindex[sq] contains the indx into PieceList for a given
+ * square.
+ */
+
+void
+InitializeStats(void)
+{
+    short i, sq;
+
+    for (i = 0; i < NO_COLS; i++)
+        PawnCnt[black][i] = PawnCnt[white][i] = 0;
+
+    mtl[black] = mtl[white] = 0;
+    PieceCnt[black] = PieceCnt[white] = 0;
+    hashbd = hashkey = 0;
+
+    for (sq = 0; sq < NO_SQUARES; sq++)
+    {
+        if (color[sq] != neutral)
+        {
+            mtl[color[sq]] += (*value)[stage][board[sq]];
+
+            if (board[sq] == pawn)
+                ++PawnCnt[color[sq]][column(sq)];
+
+            Pindex[sq] = ((board[sq] == king) ? 0 : ++PieceCnt[color[sq]]);
+            PieceList[color[sq]][Pindex[sq]] = sq;
+            UpdateHashbd(color[sq], board[sq], sq, -1);
+        }
+
+        /* hashcodes for initial board are 0 ! */
+        if (Stcolor[sq] != neutral)
+            UpdateHashbd(Stcolor[sq], Stboard[sq], sq, -1);
+    }
+
+    {
+        short side;
+
+        for (side = 0; side <= 1; side++)
+        {
+            short piece;
+
+            for (piece = 0; piece < NO_PIECES; piece++)
+            {
+                short n = Captured[side][piece];
+
+                if (n > 0)
+                {
+                    Captured[side][piece] = 0;
+
+                    for (i = 1; i <= n; i++)
+                    {
+                        ++Captured[side][piece];
+                        UpdateDropHashbd(side, piece, i);
+                        mtl[side] += (*value)[stage][piece];
+                    }
+                }
+            }
+        }
+    }
+
+#ifdef HASHKEYTEST
+    if (CheckHashKey())
+    {
+        printf("error in InitializeStats\n");
+        exit(1);
+    }
+#endif
+}