/*
    CuckooChess - A java chess program.
    Copyright (C) 2011  Peter Österlund, peterosterlund2@gmail.com

    This program 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 3 of the License, or
    (at your option) any later version.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package chess;

import java.io.IOException;
import java.io.InputStream;

/** Position evaluation routines. */
public class Evaluate {
    static final int pV =   92 + Parameters.instance().getIntPar("pV");
    static final int nV =  385 + Parameters.instance().getIntPar("nV");
    static final int bV =  385 + Parameters.instance().getIntPar("bV");
    static final int rV =  593 + Parameters.instance().getIntPar("rV");
    static final int qV = 1244 + Parameters.instance().getIntPar("qV");
    static final int kV = 9900; // Used by SEE algorithm, but not included in board material sums

    static final int[] pieceValue;
    static {
        // Initialize material table
        pieceValue = new int[Piece.nPieceTypes];
        pieceValue[Piece.WKING  ] = kV;
        pieceValue[Piece.WQUEEN ] = qV;
        pieceValue[Piece.WROOK  ] = rV;
        pieceValue[Piece.WBISHOP] = bV;
        pieceValue[Piece.WKNIGHT] = nV;
        pieceValue[Piece.WPAWN  ] = pV;
        pieceValue[Piece.BKING  ] = kV;
        pieceValue[Piece.BQUEEN ] = qV;
        pieceValue[Piece.BROOK  ] = rV;
        pieceValue[Piece.BBISHOP] = bV;
        pieceValue[Piece.BKNIGHT] = nV;
        pieceValue[Piece.BPAWN  ] = pV;
        pieceValue[Piece.EMPTY  ] =  0;
    }

    /** Piece/square table for king during middle game. */
    private static final int[] kt1b = { -22,-35,-40,-40,-40,-40,-35,-22,
                                        -22,-35,-40,-40,-40,-40,-35,-22,
                                        -25,-35,-40,-45,-45,-40,-35,-25,
                                        -15,-30,-35,-40,-40,-35,-30,-15,
                                        -10,-15,-20,-25,-25,-20,-15,-10,
                                          4, -2, -5,-15,-15, -5, -2,  4,
                                         16, 14,  7, -3, -3,  7, 14, 16,
                                         24, 24,  9,  0,  0,  9, 24, 24 };

    /** Piece/square table for king during end game. */
    private static final int[] kt2b = {  0,  8, 16, 24, 24, 16,  8,  0,
                                         8, 16, 24, 32, 32, 24, 16,  8,
                                        16, 24, 32, 40, 40, 32, 24, 16,
                                        24, 32, 40, 48, 48, 40, 32, 24,
                                        24, 32, 40, 48, 48, 40, 32, 24,
                                        16, 24, 32, 40, 40, 32, 24, 16,
                                         8, 16, 24, 32, 32, 24, 16,  8,
                                         0,  8, 16, 24, 24, 16,  8,  0 };

    /** Piece/square table for pawns during middle game. */
    private static final int[] pt1b = {  0,  0,  0,  0,  0,  0,  0,  0,
                                         8, 16, 24, 32, 32, 24, 16,  8,
                                         3, 12, 20, 28, 28, 20, 12,  3,
                                        -5,  4, 10, 20, 20, 10,  4, -5,
                                        -6,  4,  5, 16, 16,  5,  4, -6,
                                        -6,  4,  2,  5,  5,  2,  4, -6,
                                        -6,  4,  4,-15,-15,  4,  4, -6,
                                         0,  0,  0,  0,  0,  0,  0,  0 };

    /** Piece/square table for pawns during end game. */
    private static final int[] pt2b = {   0,  0,  0,  0,  0,  0,  0,  0,
                                         25, 40, 45, 45, 45, 45, 40, 25,
                                         17, 32, 35, 35, 35, 35, 32, 17,
                                          5, 24, 24, 24, 24, 24, 24,  5,
                                         -9, 11, 11, 11, 11, 11, 11, -9,
                                        -17,  3,  3,  3,  3,  3,  3,-17,
                                        -20,  0,  0,  0,  0,  0,  0,-20,
                                          0,  0,  0,  0,  0,  0,  0,  0 };

    /** Piece/square table for knights during middle game. */
    private static final int[] nt1b = { -53,-42,-32,-21,-21,-32,-42,-53,
                                        -42,-32,-10,  0,  0,-10,-32,-42,
                                        -21,  5, 10, 16, 16, 10,  5,-21,
                                        -18,  0, 10, 21, 21, 10,  0,-18,
                                        -18,  0,  3, 21, 21,  3,  0,-18,
                                        -21,-10,  0,  0,  0,  0,-10,-21,
                                        -42,-32,-10,  0,  0,-10,-32,-42,
                                        -53,-42,-32,-21,-21,-32,-42,-53 };

    /** Piece/square table for knights during end game. */
    private static final int[] nt2b = { -56,-44,-34,-22,-22,-34,-44,-56,
                                        -44,-34,-10,  0,  0,-10,-34,-44,
                                        -22,  5, 10, 17, 17, 10,  5,-22,
                                        -19,  0, 10, 22, 22, 10,  0,-19,
                                        -19,  0,  3, 22, 22,  3,  0,-19,
                                        -22,-10,  0,  0,  0,  0,-10,-22,
                                        -44,-34,-10,  0,  0,-10,-34,-44,
                                        -56,-44,-34,-22,-22,-34,-44,-56 };

    /** Piece/square table for bishops during middle game. */
    private static final int[] bt1b = {  0,  0,  0,  0,  0,  0,  0,  0,
                                         0,  4,  2,  2,  2,  2,  4,  0,
                                         0,  2,  4,  4,  4,  4,  2,  0,
                                         0,  2,  4,  4,  4,  4,  2,  0,
                                         0,  2,  4,  4,  4,  4,  2,  0,
                                         0,  3,  4,  4,  4,  4,  3,  0,
                                         0,  4,  2,  2,  2,  2,  4,  0,
                                        -5, -5, -7, -5, -5, -7, -5, -5 };

    /** Piece/square table for bishops during middle game. */
    private static final int[] bt2b = {  0,  0,  0,  0,  0,  0,  0,  0,
                                         0,  2,  2,  2,  2,  2,  2,  0,
                                         0,  2,  4,  4,  4,  4,  2,  0,
                                         0,  2,  4,  4,  4,  4,  2,  0,
                                         0,  2,  4,  4,  4,  4,  2,  0,
                                         0,  2,  4,  4,  4,  4,  2,  0,
                                         0,  2,  2,  2,  2,  2,  2,  0,
                                         0,  0,  0,  0,  0,  0,  0,  0 };

    /** Piece/square table for queens during middle game. */
    private static final int[] qt1b = { -10, -5,  0,  0,  0,  0, -5,-10,
                                         -5,  0,  5,  5,  5,  5,  0, -5,
                                          0,  5,  5,  6,  6,  5,  5,  0,
                                          0,  5,  6,  6,  6,  6,  5,  0,
                                          0,  5,  6,  6,  6,  6,  5,  0,
                                          0,  5,  5,  6,  6,  5,  5,  0,
                                         -5,  0,  5,  5,  5,  5,  0, -5,
                                        -10, -5,  0,  0,  0,  0, -5,-10 };

    /** Piece/square table for rooks during middle game. */
    private static final int[] rt1b = {  8, 11, 13, 13, 13, 13, 11,  8,
                                22, 27, 27, 27, 27, 27, 27, 22,
                                 0,  0,  0,  0,  0,  0,  0,  0,
                                 0,  0,  0,  0,  0,  0,  0,  0,
                                -2,  0,  0,  0,  0,  0,  0, -2,
                                -2,  0,  0,  2,  2,  0,  0, -2,
                                -3,  2,  5,  5,  5,  5,  2, -3,
                                 0,  3,  5,  5,  5,  5,  3,  0 };

    private static final int[] kt1w, qt1w, rt1w, bt1w, nt1w, pt1w, kt2w, bt2w, nt2w, pt2w;
    static {
        kt1w = new int[64];
        qt1w = new int[64];
        rt1w = new int[64];
        bt1w = new int[64];
        nt1w = new int[64];
        pt1w = new int[64];
        kt2w = new int[64];
        bt2w = new int[64];
        nt2w = new int[64];
        pt2w = new int[64];
        for (int i = 0; i < 64; i++) {
            kt1w[i] = kt1b[63-i];
            qt1w[i] = qt1b[63-i];
            rt1w[i] = rt1b[63-i];
            bt1w[i] = bt1b[63-i];
            nt1w[i] = nt1b[63-i];
            pt1w[i] = pt1b[63-i];
            kt2w[i] = kt2b[63-i];
            bt2w[i] = bt2b[63-i];
            nt2w[i] = nt2b[63-i];
            pt2w[i] = pt2b[63-i];
        }
    }

    private static final int[] empty = { 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
                                         0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
                                         0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
                                         0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0};
    static final int[][] psTab1 = { empty, kt1w, qt1w, rt1w, bt1w, nt1w, pt1w,
                                           kt1b, qt1b, rt1b, bt1b, nt1b, pt1b };
    static final int[][] psTab2 = { empty, kt2w, qt1w, rt1w, bt2w, nt2w, pt2w,
                                           kt2b, qt1b, rt1b, bt2b, nt2b, pt2b };

    static final int[][] distToH1A8 = { { 0, 1, 2, 3, 4, 5, 6, 7 },
                                        { 1, 2, 3, 4, 5, 6, 7, 6 },
                                        { 2, 3, 4, 5, 6, 7, 6, 5 },
                                        { 3, 4, 5, 6, 7, 6, 5, 4 },
                                        { 4, 5, 6, 7, 6, 5, 4, 3 },
                                        { 5, 6, 7, 6, 5, 4, 3, 2 },
                                        { 6, 7, 6, 5, 4, 3, 2, 1 },
                                        { 7, 6, 5, 4, 3, 2, 1, 0 } };

    private static final int[] rookMobScore = {-10,-7,-4,-1,2,5,7,9,11,12,13,14,14,14,14};
    private static final int[] bishMobScore = {-15,-10,-6,-2,2,6,10,13,16,18,20,22,23,24};
    private static final int[] queenMobScore = {-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,9,10,10,10,10,10,10,10,10,10,10,10,10};

    private static final class PawnHashData {
        long key;
        int score;         // Positive score means good for white
        short passedBonusW;
        short passedBonusB;
        long passedPawnsW;     // The most advanced passed pawns for each file
        long passedPawnsB;
    }
    private static final PawnHashData[] pawnHash;
    static {
        final int numEntries = 1<<16;
        pawnHash = new PawnHashData[numEntries];
        for (int i = 0; i < numEntries; i++) {
            PawnHashData phd = new PawnHashData();
            phd.key = -1; // Non-zero to avoid collision for positions with no pawns
            phd.score = 0;
            pawnHash[i] = phd;
        }
    }

    private static byte[] kpkTable = null;
    private static byte[] krkpTable = null;

    // King safety variables
    private long wKingZone, bKingZone;       // Squares close to king that are worth attacking
    private int wKingAttacks, bKingAttacks; // Number of attacks close to white/black king
    private long wAttacksBB, bAttacksBB;
    private long wPawnAttacks, bPawnAttacks; // Squares attacked by white/black pawns

    /** Constructor. */
    public Evaluate() {
        if (kpkTable == null)
            kpkTable = readTable("/kpk.bitbase", 2*32*64*48/8);
        if (krkpTable == null)
            krkpTable = readTable("/krkp.winmasks", 2*32*48*8);
    }

    private byte[] readTable(String resource, int length) {
        byte[] table = new byte[2*32*64*48/8];
        try (InputStream inStream = getClass().getResourceAsStream(resource)) {
            int off = 0;
            while (off < table.length) {
                int len = inStream.read(table, off, table.length - off);
                if (len < 0)
                    throw new RuntimeException();
                off += len;
            }
            return table;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Static evaluation of a position.
     * @param pos The position to evaluate.
     * @return The evaluation score, measured in centipawns.
     *         Positive values are good for the side to make the next move.
     */
    final public int evalPos(Position pos) {
        int score = pos.wMtrl - pos.bMtrl;

        wKingAttacks = bKingAttacks = 0;
        wKingZone = BitBoard.kingAttacks[pos.getKingSq(true)]; wKingZone |= wKingZone << 8;
        bKingZone = BitBoard.kingAttacks[pos.getKingSq(false)]; bKingZone |= bKingZone >>> 8;
        wAttacksBB = bAttacksBB = 0L;

        long pawns = pos.pieceTypeBB[Piece.WPAWN];
        wPawnAttacks = ((pawns & BitBoard.maskBToHFiles) << 7) |
                       ((pawns & BitBoard.maskAToGFiles) << 9);
        pawns = pos.pieceTypeBB[Piece.BPAWN];
        bPawnAttacks = ((pawns & BitBoard.maskBToHFiles) >>> 9) |
                       ((pawns & BitBoard.maskAToGFiles) >>> 7);

        score += pieceSquareEval(pos);
        score += pawnBonus(pos);
        score += tradeBonus(pos);
        score += castleBonus(pos);

        score += rookBonus(pos);
        score += bishopEval(pos, score);
        score += threatBonus(pos);
        score += kingSafety(pos);
        score = endGameEval(pos, score);

        if (!pos.whiteMove)
            score = -score;
        return score;
    }

    /** Compute white_material - black_material. */
    static int material(Position pos) {
        return pos.wMtrl - pos.bMtrl;
    }
    
    /** Compute score based on piece square tables. Positive values are good for white. */
    private int pieceSquareEval(Position pos) {
        int score = 0;
        final int wMtrl = pos.wMtrl;
        final int bMtrl = pos.bMtrl;
        final int wMtrlPawns = pos.wMtrlPawns;
        final int bMtrlPawns = pos.bMtrlPawns;
        
        // Kings
        {
            final int t1 = qV + 2 * rV + 2 * bV;
            final int t2 = rV;
            {
                final int k1 = pos.psScore1[Piece.WKING];
                final int k2 = pos.psScore2[Piece.WKING];
                final int t = bMtrl - bMtrlPawns;
                score += interpolate(t, t2, k2, t1, k1);
            }
            {
                final int k1 = pos.psScore1[Piece.BKING];
                final int k2 = pos.psScore2[Piece.BKING];
                final int t = wMtrl - wMtrlPawns;
                score -= interpolate(t, t2, k2, t1, k1);
            }
        }

        // Pawns
        {
            final int t1 = qV + 2 * rV + 2 * bV;
            final int t2 = rV;
            int wp1 = pos.psScore1[Piece.WPAWN];
            int wp2 = pos.psScore2[Piece.WPAWN];
            if ((wp1 != 0) || (wp2 != 0)) {
                final int tw = bMtrl - bMtrlPawns;
                score += interpolate(tw, t2, wp2, t1, wp1);
            }
            int bp1 = pos.psScore1[Piece.BPAWN];
            int bp2 = pos.psScore2[Piece.BPAWN];
            if ((bp1 != 0) || (bp2 != 0)) {
                final int tb = wMtrl - wMtrlPawns;
                score -= interpolate(tb, t2, bp2, t1, bp1);
            }
        }

        // Knights
        {
            final int t1 = qV + 2 * rV + bV + nV + 6 * pV;
            final int t2 = nV + 8 * pV;
            int n1 = pos.psScore1[Piece.WKNIGHT];
            int n2 = pos.psScore2[Piece.WKNIGHT];
            if ((n1 != 0) || (n2 != 0)) {
                score += interpolate(bMtrl, t2, n2, t1, n1);
            }
            n1 = pos.psScore1[Piece.BKNIGHT];
            n2 = pos.psScore2[Piece.BKNIGHT];
            if ((n1 != 0) || (n2 != 0)) {
                score -= interpolate(wMtrl, t2, n2, t1, n1);
            }
        }

        // Bishops
        {
            score += pos.psScore1[Piece.WBISHOP];
            score -= pos.psScore1[Piece.BBISHOP];
        }

        // Queens
        {
            final long occupied = pos.whiteBB | pos.blackBB;
            score += pos.psScore1[Piece.WQUEEN];
            long m = pos.pieceTypeBB[Piece.WQUEEN];
            while (m != 0) {
                int sq = BitBoard.numberOfTrailingZeros(m);
                long atk = BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied);
                wAttacksBB |= atk;
                score += queenMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))];
                bKingAttacks += Long.bitCount(atk & bKingZone) * 2;
                m &= m-1;
            }
            score -= pos.psScore1[Piece.BQUEEN];
            m = pos.pieceTypeBB[Piece.BQUEEN];
            while (m != 0) {
                int sq = BitBoard.numberOfTrailingZeros(m);
                long atk = BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied);
                bAttacksBB |= atk;
                score -= queenMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))];
                wKingAttacks += Long.bitCount(atk & wKingZone) * 2;
                m &= m-1;
            }
        }

        // Rooks
        {
            int r1 = pos.psScore1[Piece.WROOK];
            if (r1 != 0) {
                final int nP = bMtrlPawns / pV;
                final int s = r1 * Math.min(nP, 6) / 6;
                score += s;
            }
            r1 = pos.psScore1[Piece.BROOK];
            if (r1 != 0) {
                final int nP = wMtrlPawns / pV;
                final int s = r1 * Math.min(nP, 6) / 6;
                score -= s;
            }
        }

        return score;
    }

    /** Implement the "when ahead trade pieces, when behind trade pawns" rule. */
    private int tradeBonus(Position pos) {
        final int wM = pos.wMtrl;
        final int bM = pos.bMtrl;
        final int wPawn = pos.wMtrlPawns;
        final int bPawn = pos.bMtrlPawns;
        final int deltaScore = wM - bM;

        int pBonus = 0;
        pBonus += interpolate((deltaScore > 0) ? wPawn : bPawn, 0, -30 * deltaScore / 100, 6 * pV, 0);
        pBonus += interpolate((deltaScore > 0) ? bM : wM, 0, 30 * deltaScore / 100, qV + 2 * rV + 2 * bV + 2 * nV, 0);

        return pBonus;
    }

    private static final int[] castleFactor;
    static {
        castleFactor = new int[256];
        for (int i = 0; i < 256; i++) {
            int h1Dist = 100;
            boolean h1Castle = (i & (1<<7)) != 0;
            if (h1Castle)
                h1Dist = 2 + Long.bitCount(i & 0x0000000000000060L); // f1,g1
            int a1Dist = 100;
            boolean a1Castle = (i & 1) != 0;
            if (a1Castle)
                a1Dist = 2 + Long.bitCount(i & 0x000000000000000EL); // b1,c1,d1
            castleFactor[i] = 1024 / Math.min(a1Dist, h1Dist);
        }
    }

    /** Score castling ability. */
    private int castleBonus(Position pos) {
        if (pos.getCastleMask() == 0) return 0;

        final int k1 = kt1b[7*8+6] - kt1b[7*8+4];
        final int k2 = kt2b[7*8+6] - kt2b[7*8+4];
        final int t1 = qV + 2 * rV + 2 * bV;
        final int t2 = rV;
        final int t = pos.bMtrl - pos.bMtrlPawns;
        final int ks = interpolate(t, t2, k2, t1, k1);

        final int castleValue = ks + rt1b[7*8+5] - rt1b[7*8+7];
        if (castleValue <= 0)
            return 0;

        long occupied = pos.whiteBB | pos.blackBB;
        int tmp = (int) (occupied & 0x6E);
        if (pos.a1Castle()) tmp |= 1;
        if (pos.h1Castle()) tmp |= (1 << 7);
        final int wBonus = (castleValue * castleFactor[tmp]) >> 10;

        tmp = (int) ((occupied >>> 56) & 0x6E);
        if (pos.a8Castle()) tmp |= 1;
        if (pos.h8Castle()) tmp |= (1 << 7);
        final int bBonus = (castleValue * castleFactor[tmp]) >> 10;

        return wBonus - bBonus;
    }

    private int pawnBonus(Position pos) {
        long key = pos.pawnZobristHash();
        PawnHashData phd = pawnHash[(int)key & (pawnHash.length - 1)];
        if (phd.key != key)
            computePawnHashData(pos, phd);
        int score = phd.score;

        final int hiMtrl = qV + rV;
        score += interpolate(pos.bMtrl - pos.bMtrlPawns, 0, 2 * phd.passedBonusW, hiMtrl, phd.passedBonusW);
        score -= interpolate(pos.wMtrl - pos.wMtrlPawns, 0, 2 * phd.passedBonusB, hiMtrl, phd.passedBonusB);

        // Passed pawns are more dangerous if enemy king is far away
        int bestWPawnDist = 8;
        int bestWPromSq = -1;
        long m = phd.passedPawnsW;
        if (m != 0) {
            int mtrlNoPawns = pos.bMtrl - pos.bMtrlPawns;
            if (mtrlNoPawns < hiMtrl) {
                int kingPos = pos.getKingSq(false);
                while (m != 0) {
                    int sq = BitBoard.numberOfTrailingZeros(m);
                    int x = Position.getX(sq);
                    int y = Position.getY(sq);
                    int pawnDist = Math.min(5, 7 - y);
                    int kingDist = BitBoard.getDistance(kingPos, Position.getSquare(x, 7));
                    int kScore = kingDist * 4;
                    if (kingDist > pawnDist) kScore += (kingDist - pawnDist) * (kingDist - pawnDist);
                    score += interpolate(mtrlNoPawns, 0, kScore, hiMtrl, 0);
                    if (!pos.whiteMove)
                        kingDist--;
                    if ((pawnDist < kingDist) && (mtrlNoPawns == 0)) {
                        if ((BitBoard.northFill(1L<<sq) & (1L << pos.getKingSq(true))) != 0)
                            pawnDist++; // Own king blocking pawn
                        if (pawnDist < bestWPawnDist) {
                            bestWPawnDist = pawnDist;
                            bestWPromSq = Position.getSquare(x, 7);
                        }
                    }
                    m &= m-1;
                }
            }
        }
        int bestBPawnDist = 8;
        int bestBPromSq = -1;
        m = phd.passedPawnsB;
        if (m != 0) {
            int mtrlNoPawns = pos.wMtrl - pos.wMtrlPawns;
            if (mtrlNoPawns < hiMtrl) {
                int kingPos = pos.getKingSq(true);
                while (m != 0) {
                    int sq = BitBoard.numberOfTrailingZeros(m);
                    int x = Position.getX(sq);
                    int y = Position.getY(sq);
                    int pawnDist = Math.min(5, y);
                    int kingDist = BitBoard.getDistance(kingPos, Position.getSquare(x, 0));
                    int kScore = kingDist * 4;
                    if (kingDist > pawnDist) kScore += (kingDist - pawnDist) * (kingDist - pawnDist);
                    score -= interpolate(mtrlNoPawns, 0, kScore, hiMtrl, 0);
                    if (pos.whiteMove)
                        kingDist--;
                    if ((pawnDist < kingDist) && (mtrlNoPawns == 0)) {
                        if ((BitBoard.southFill(1L<<sq) & (1L << pos.getKingSq(false))) != 0)
                            pawnDist++; // Own king blocking pawn
                        if (pawnDist < bestBPawnDist) {
                            bestBPawnDist = pawnDist;
                            bestBPromSq = Position.getSquare(x, 0);
                        }
                    }
                    m &= m-1;
                }
            }
        }

        // Evaluate pawn races in pawn end games
        if (bestWPromSq >= 0) {
            if (bestBPromSq >= 0) {
                int wPly = bestWPawnDist * 2; if (pos.whiteMove) wPly--;
                int bPly = bestBPawnDist * 2; if (!pos.whiteMove) bPly--;
                if (wPly < bPly - 1) {
                    score += 500;
                } else if (wPly == bPly - 1) {
                    if (BitBoard.getDirection(bestWPromSq, pos.getKingSq(false)) != 0)
                        score += 500;
                } else if (wPly == bPly + 1) {
                    if (BitBoard.getDirection(bestBPromSq, pos.getKingSq(true)) != 0)
                        score -= 500;
                } else {
                    score -= 500;
                }
            } else
                score += 500;
        } else if (bestBPromSq >= 0)
            score -= 500;

        return score;
    }

    /** Compute pawn hash data for pos. */
    private void computePawnHashData(Position pos, PawnHashData ph) {
        int score = 0;

        // Evaluate double pawns and pawn islands
        long wPawns = pos.pieceTypeBB[Piece.WPAWN];
        long wPawnFiles = BitBoard.southFill(wPawns) & 0xff;
        int wDouble = Long.bitCount(wPawns) - Long.bitCount(wPawnFiles);
        int wIslands = Long.bitCount(((~wPawnFiles) >>> 1) & wPawnFiles);
        int wIsolated = Long.bitCount(~(wPawnFiles<<1) & wPawnFiles & ~(wPawnFiles>>>1));

        
        long bPawns = pos.pieceTypeBB[Piece.BPAWN];
        long bPawnFiles = BitBoard.southFill(bPawns) & 0xff;
        int bDouble = Long.bitCount(bPawns) - Long.bitCount(bPawnFiles);
        int bIslands = Long.bitCount(((~bPawnFiles) >>> 1) & bPawnFiles);
        int bIsolated = Long.bitCount(~(bPawnFiles<<1) & bPawnFiles & ~(bPawnFiles>>>1));

        score -= (wDouble - bDouble) * 25;
        score -= (wIslands - bIslands) * 15;
        score -= (wIsolated - bIsolated) * 15;

        // Evaluate backward pawns, defined as a pawn that guards a friendly pawn,
        // can't be guarded by friendly pawns, can advance, but can't advance without 
        // being captured by an enemy pawn.
        long wPawnAttacks = (((wPawns & BitBoard.maskBToHFiles) << 7) |
                             ((wPawns & BitBoard.maskAToGFiles) << 9));
        long bPawnAttacks = (((bPawns & BitBoard.maskBToHFiles) >>> 9) |
                             ((bPawns & BitBoard.maskAToGFiles) >>> 7));
        long wBackward = wPawns & ~((wPawns | bPawns) >>> 8) & (bPawnAttacks >>> 8) &
                         ~BitBoard.northFill(wPawnAttacks);
        wBackward &= (((wPawns & BitBoard.maskBToHFiles) >>> 9) |
                      ((wPawns & BitBoard.maskAToGFiles) >>> 7));
        wBackward &= ~BitBoard.northFill(bPawnFiles);
        long bBackward = bPawns & ~((wPawns | bPawns) << 8) & (wPawnAttacks << 8) &
                         ~BitBoard.southFill(bPawnAttacks);
        bBackward &= (((bPawns & BitBoard.maskBToHFiles) << 7) |
                      ((bPawns & BitBoard.maskAToGFiles) << 9));
        bBackward &= ~BitBoard.northFill(wPawnFiles);
        score -= (Long.bitCount(wBackward) - Long.bitCount(bBackward)) * 15;

        // Evaluate passed pawn bonus, white
        long passedPawnsW = wPawns & ~BitBoard.southFill(bPawns | bPawnAttacks | (wPawns >>> 8));
        final int[] ppBonus = {-1,24,26,30,36,55,100,-1};
        int passedBonusW = 0;
        if (passedPawnsW != 0) {
            long guardedPassedW = passedPawnsW & (((wPawns & BitBoard.maskBToHFiles) << 7) |
                                                  ((wPawns & BitBoard.maskAToGFiles) << 9));
            passedBonusW += 15 * Long.bitCount(guardedPassedW);
            long m = passedPawnsW;
            while (m != 0) {
                int sq = Long .numberOfTrailingZeros(m);
                int y = Position.getY(sq);
                passedBonusW += ppBonus[y];
                m &= m-1;
            }
        }

        // Evaluate passed pawn bonus, black
        long passedPawnsB = bPawns & ~BitBoard.northFill(wPawns | wPawnAttacks | (bPawns << 8));
        int passedBonusB = 0;
        if (passedPawnsB != 0) {
            long guardedPassedB = passedPawnsB & (((bPawns & BitBoard.maskBToHFiles) >>> 9) |
                                                  ((bPawns & BitBoard.maskAToGFiles) >>> 7));
            passedBonusB += 15 * Long.bitCount(guardedPassedB);
            long m = passedPawnsB;
            while (m != 0) {
                int sq = Long .numberOfTrailingZeros(m);
                int y = Position.getY(sq);
                passedBonusB += ppBonus[7-y];
                m &= m-1;
            }
        }

        // Connected passed pawn bonus. Seems logical but scored -8 elo in tests
//        if (passedPawnsW != 0) {
//            long mask = passedPawnsW;
//            mask = (((mask >> 7) | (mask << 1) | (mask << 9)) & BitBoard.maskBToHFiles) |
//                    (((mask >> 9) | (mask >> 1) | (mask << 7)) & BitBoard.maskAToGFiles);
//            passedBonusW += 13 * Long.bitCount(passedPawnsW & mask);
//        }
//        if (passedPawnsB != 0) {
//            long mask = passedPawnsB;
//            mask = (((mask >> 7) | (mask << 1) | (mask << 9)) & BitBoard.maskBToHFiles) |
//                    (((mask >> 9) | (mask >> 1) | (mask << 7)) & BitBoard.maskAToGFiles);
//            passedBonusB += 13 * Long.bitCount(passedPawnsB & mask);
//        }

        ph.key = pos.pawnZobristHash();
        ph.score = score;
        ph.passedBonusW = (short)passedBonusW;
        ph.passedBonusB = (short)passedBonusB;
        ph.passedPawnsW = passedPawnsW;
        ph.passedPawnsB = passedPawnsB;
    }

    /** Compute rook bonus. Rook on open/half-open file. */
    private int rookBonus(Position pos) {
        int score = 0;
        final long wPawns = pos.pieceTypeBB[Piece.WPAWN];
        final long bPawns = pos.pieceTypeBB[Piece.BPAWN];
        final long occupied = pos.whiteBB | pos.blackBB;
        long m = pos.pieceTypeBB[Piece.WROOK];
        while (m != 0) {
            int sq = BitBoard.numberOfTrailingZeros(m);
            final int x = Position.getX(sq);
            if ((wPawns & BitBoard.maskFile[x]) == 0) { // At least half-open file
                score += (bPawns & BitBoard.maskFile[x]) == 0 ? 25 : 12;
            }
            long atk = BitBoard.rookAttacks(sq, occupied);
            wAttacksBB |= atk;
            score += rookMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))];
            if ((atk & bKingZone) != 0)
                bKingAttacks += Long.bitCount(atk & bKingZone);
            m &= m-1;
        }
        long r7 = (pos.pieceTypeBB[Piece.WROOK] >>> 48) & 0x00ffL;
        if (((r7 & (r7 - 1)) != 0) &&
            ((pos.pieceTypeBB[Piece.BKING] & 0xff00000000000000L) != 0))
            score += 30; // Two rooks on 7:th row
        m = pos.pieceTypeBB[Piece.BROOK];
        while (m != 0) {
            int sq = BitBoard.numberOfTrailingZeros(m);
            final int x = Position.getX(sq);
            if ((bPawns & BitBoard.maskFile[x]) == 0) {
                score -= (wPawns & BitBoard.maskFile[x]) == 0 ? 25 : 12;
            }
            long atk = BitBoard.rookAttacks(sq, occupied);
            bAttacksBB |= atk;
            score -= rookMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))];
            if ((atk & wKingZone) != 0)
                wKingAttacks += Long.bitCount(atk & wKingZone);
            m &= m-1;
        }
        r7 = pos.pieceTypeBB[Piece.BROOK] & 0xff00L;
        if (((r7 & (r7 - 1)) != 0) &&
            ((pos.pieceTypeBB[Piece.WKING] & 0xffL) != 0))
          score -= 30; // Two rooks on 2:nd row
        return score;
    }

    /** Compute bishop evaluation. */
    private int bishopEval(Position pos, int oldScore) {
        int score = 0;
        final long occupied = pos.whiteBB | pos.blackBB;
        long wBishops = pos.pieceTypeBB[Piece.WBISHOP];
        long bBishops = pos.pieceTypeBB[Piece.BBISHOP];
        if ((wBishops | bBishops) == 0)
            return 0;
        long m = wBishops;
        while (m != 0) {
            int sq = BitBoard.numberOfTrailingZeros(m);
            long atk = BitBoard.bishopAttacks(sq, occupied);
            wAttacksBB |= atk;
            score += bishMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))];
            if ((atk & bKingZone) != 0)
                bKingAttacks += Long.bitCount(atk & bKingZone);
            m &= m-1;
        }
        m = bBishops;
        while (m != 0) {
            int sq = BitBoard.numberOfTrailingZeros(m);
            long atk = BitBoard.bishopAttacks(sq, occupied);
            bAttacksBB |= atk;
            score -= bishMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))];
            if ((atk & wKingZone) != 0)
                wKingAttacks += Long.bitCount(atk & wKingZone);
            m &= m-1;
        }

        boolean whiteDark  = (pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq ) != 0;
        boolean whiteLight = (pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskLightSq) != 0;
        boolean blackDark  = (pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq ) != 0;
        boolean blackLight = (pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskLightSq) != 0;
        int numWhite = (whiteDark ? 1 : 0) + (whiteLight ? 1 : 0);
        int numBlack = (blackDark ? 1 : 0) + (blackLight ? 1 : 0);

        // Bishop pair bonus
        if (numWhite == 2) {
            final int numPawns = pos.wMtrlPawns / pV;
            score += 28 + (8 - numPawns) * 3;
        }
        if (numBlack == 2) {
            final int numPawns = pos.bMtrlPawns / pV;
            score -= 28 + (8 - numPawns) * 3;
        }
    
        if ((numWhite == 1) && (numBlack == 1) && (whiteDark != blackDark) &&
            (pos.wMtrl - pos.wMtrlPawns == pos.bMtrl - pos.bMtrlPawns)) {
            final int penalty = (oldScore + score) / 2;
            final int loMtrl = 2 * bV;
            final int hiMtrl = 2 * (qV + rV + bV);
            int mtrl = pos.wMtrl + pos.bMtrl - pos.wMtrlPawns - pos.bMtrlPawns;
            score -= interpolate(mtrl, loMtrl, penalty, hiMtrl, 0);
        }

        // Penalty for bishop trapped behind pawn at a2/h2/a7/h7
        if (((wBishops | bBishops) & 0x0081000000008100L) != 0) {
            if ((pos.squares[48] == Piece.WBISHOP) && // a7
                (pos.squares[41] == Piece.BPAWN) &&
                (pos.squares[50] == Piece.BPAWN))
                score -= pV * 3 / 2;
            if ((pos.squares[55] == Piece.WBISHOP) && // h7
                (pos.squares[46] == Piece.BPAWN) &&
                (pos.squares[53] == Piece.BPAWN))
                score -= (pos.pieceTypeBB[Piece.WQUEEN] != 0) ? pV : pV * 3 / 2;
            if ((pos.squares[8] == Piece.BBISHOP) &&  // a2
                (pos.squares[17] == Piece.WPAWN) &&
                (pos.squares[10] == Piece.WPAWN))
                score += pV * 3 / 2;
            if ((pos.squares[15] == Piece.BBISHOP) && // h2
                (pos.squares[22] == Piece.WPAWN) &&
                (pos.squares[13] == Piece.WPAWN))
                score += (pos.pieceTypeBB[Piece.BQUEEN] != 0) ? pV : pV * 3 / 2;
        }

        return score;
    }

    private int threatBonus(Position pos) {
        int score = 0;

        // Sum values for all black pieces under attack
        long m = pos.pieceTypeBB[Piece.WKNIGHT];
        while (m != 0) {
            int sq = BitBoard.numberOfTrailingZeros(m);
            wAttacksBB |= BitBoard.knightAttacks[sq];
            m &= m-1;
        }
        wAttacksBB &= (pos.pieceTypeBB[Piece.BKNIGHT] |
                       pos.pieceTypeBB[Piece.BBISHOP] |
                       pos.pieceTypeBB[Piece.BROOK] |
                       pos.pieceTypeBB[Piece.BQUEEN]);
        wAttacksBB |= wPawnAttacks;
        m = wAttacksBB & pos.blackBB & ~pos.pieceTypeBB[Piece.BKING];
        int tmp = 0;
        while (m != 0) {
            int sq = BitBoard.numberOfTrailingZeros(m);
            tmp += pieceValue[pos.squares[sq]];
            m &= m-1;
        }
        score += tmp + tmp * tmp / qV;

        // Sum values for all white pieces under attack
        m = pos.pieceTypeBB[Piece.BKNIGHT];
        while (m != 0) {
            int sq = BitBoard.numberOfTrailingZeros(m);
            bAttacksBB |= BitBoard.knightAttacks[sq];
            m &= m-1;
        }
        bAttacksBB &= (pos.pieceTypeBB[Piece.WKNIGHT] |
                       pos.pieceTypeBB[Piece.WBISHOP] |
                       pos.pieceTypeBB[Piece.WROOK] |
                       pos.pieceTypeBB[Piece.WQUEEN]);
        bAttacksBB |= bPawnAttacks;
        m = bAttacksBB & pos.whiteBB & ~pos.pieceTypeBB[Piece.WKING];
        tmp = 0;
        while (m != 0) {
            int sq = BitBoard.numberOfTrailingZeros(m);
            tmp += pieceValue[pos.squares[sq]];
            m &= m-1;
        }
        score -= tmp + tmp * tmp / qV;
        return score / 64;
    }

    /** Compute king safety for both kings. */
    private int kingSafety(Position pos) {
        final int minM = rV + bV;
        final int m = (pos.wMtrl - pos.wMtrlPawns + pos.bMtrl - pos.bMtrlPawns) / 2;
        if (m <= minM)
            return 0;
        final int maxM = qV + 2 * rV + 2 * bV + 2 * nV;
        int score = kingSafetyKPPart(pos);
        if (Position.getY(pos.wKingSq) == 0) {
            if (((pos.pieceTypeBB[Piece.WKING] & 0x60L) != 0) && // King on f1 or g1
                ((pos.pieceTypeBB[Piece.WROOK] & 0xC0L) != 0) && // Rook on g1 or h1
                ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[6]) != 0) &&
                ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[7]) != 0)) {
                score -= 6 * 15;
            } else
            if (((pos.pieceTypeBB[Piece.WKING] & 0x6L) != 0) && // King on b1 or c1
                ((pos.pieceTypeBB[Piece.WROOK] & 0x3L) != 0) && // Rook on a1 or b1
                ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[0]) != 0) &&
                ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[1]) != 0)) {
                score -= 6 * 15;
            }
        }
        if (Position.getY(pos.bKingSq) == 7) {
            if (((pos.pieceTypeBB[Piece.BKING] & 0x6000000000000000L) != 0) && // King on f8 or g8
                ((pos.pieceTypeBB[Piece.BROOK] & 0xC000000000000000L) != 0) && // Rook on g8 or h8
                ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[6]) != 0) &&
                ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[7]) != 0)) {
                score += 6 * 15;
            } else
            if (((pos.pieceTypeBB[Piece.BKING] & 0x600000000000000L) != 0) && // King on b8 or c8
                ((pos.pieceTypeBB[Piece.BROOK] & 0x300000000000000L) != 0) && // Rook on a8 or b8
                ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[0]) != 0) &&
                ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[1]) != 0)) {
                score += 6 * 15;
            }
        }
        score += (bKingAttacks - wKingAttacks) * 4;
        final int kSafety = interpolate(m, minM, 0, maxM, score);
        return kSafety;
    }

    private static final class KingSafetyHashData {
        long key;
        int score;
    }
    private static final KingSafetyHashData[] kingSafetyHash;
    static {
        final int numEntries = 1 << 15;
        kingSafetyHash = new KingSafetyHashData[numEntries];
        for (int i = 0; i < numEntries; i++) {
            KingSafetyHashData ksh = new KingSafetyHashData();
            ksh.key = -1;
            ksh.score = 0;
            kingSafetyHash[i] = ksh;
        }
    }

    private int kingSafetyKPPart(Position pos) {
        final long key = pos.pawnZobristHash() ^ pos.kingZobristHash();
        KingSafetyHashData ksh = kingSafetyHash[(int)key & (kingSafetyHash.length - 1)];
        if (ksh.key != key) {
            int score = 0;
            long wPawns = pos.pieceTypeBB[Piece.WPAWN];
            long bPawns = pos.pieceTypeBB[Piece.BPAWN];
            {
                int safety = 0;
                int halfOpenFiles = 0;
                if (Position.getY(pos.wKingSq) < 2) {
                    long shelter = 1L << Position.getX(pos.wKingSq);
                    shelter |= ((shelter & BitBoard.maskBToHFiles) >>> 1) |
                               ((shelter & BitBoard.maskAToGFiles) << 1);
                    shelter <<= 8;
                    safety += 3 * Long.bitCount(wPawns & shelter);
                    safety -= 2 * Long.bitCount(bPawns & (shelter | (shelter << 8)));
                    shelter <<= 8;
                    safety += 2 * Long.bitCount(wPawns & shelter);
                    shelter <<= 8;
                    safety -= Long.bitCount(bPawns & shelter);
                    
                    long wOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(wPawns)) & 0xff;
                    if (wOpen != 0) {
                        halfOpenFiles += 25 * Long.bitCount(wOpen & 0xe7);
                        halfOpenFiles += 10 * Long.bitCount(wOpen & 0x18);
                    }
                    long bOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(bPawns)) & 0xff;
                    if (bOpen != 0) {
                        halfOpenFiles += 25 * Long.bitCount(bOpen & 0xe7);
                        halfOpenFiles += 10 * Long.bitCount(bOpen & 0x18);
                    }
                    safety = Math.min(safety, 8);
                }
                final int kSafety = (safety - 9) * 15 - halfOpenFiles;
                score += kSafety;
            }
            {
                int safety = 0;
                int halfOpenFiles = 0;
                if (Position.getY(pos.bKingSq) >= 6) {
                    long shelter = 1L << (56 + Position.getX(pos.bKingSq));
                    shelter |= ((shelter & BitBoard.maskBToHFiles) >>> 1) |
                               ((shelter & BitBoard.maskAToGFiles) << 1);
                    shelter >>>= 8;
                    safety += 3 * Long.bitCount(bPawns & shelter);
                    safety -= 2 * Long.bitCount(wPawns & (shelter | (shelter >>> 8)));
                    shelter >>>= 8;
                    safety += 2 * Long.bitCount(bPawns & shelter);
                    shelter >>>= 8;
                    safety -= Long.bitCount(wPawns & shelter);

                    long wOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(wPawns)) & 0xff;
                    if (wOpen != 0) {
                        halfOpenFiles += 25 * Long.bitCount(wOpen & 0xe7);
                        halfOpenFiles += 10 * Long.bitCount(wOpen & 0x18);
                    }
                    long bOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(bPawns)) & 0xff;
                    if (bOpen != 0) {
                        halfOpenFiles += 25 * Long.bitCount(bOpen & 0xe7);
                        halfOpenFiles += 10 * Long.bitCount(bOpen & 0x18);
                    }
                    safety = Math.min(safety, 8);
                }
                final int kSafety = (safety - 9) * 15 - halfOpenFiles;
                score -= kSafety;
            }
            ksh.key = key;
            ksh.score = score;
        }
        return ksh.score;
    }

    /** Implements special knowledge for some endgame situations. */
    private int endGameEval(Position pos, int oldScore) {
        int score = oldScore;
        if (pos.wMtrl + pos.bMtrl > 6 * rV)
            return score;
        final int wMtrlPawns = pos.wMtrlPawns;
        final int bMtrlPawns = pos.bMtrlPawns;
        final int wMtrlNoPawns = pos.wMtrl - wMtrlPawns;
        final int bMtrlNoPawns = pos.bMtrl - bMtrlPawns;

        boolean handled = false;
        if ((wMtrlPawns + bMtrlPawns == 0) && (wMtrlNoPawns < rV) && (bMtrlNoPawns < rV)) {
            // King + minor piece vs king + minor piece is a draw
            return 0;
        }
        if (!handled && (pos.wMtrl == qV) && (pos.bMtrl == pV) && (pos.pieceTypeBB[Piece.WQUEEN] != 0)) {
            int wk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WKING]);
            int wq = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WQUEEN]);
            int bk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BKING]);
            int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]);
            score = evalKQKP(wk, wq, bk, bp, pos.whiteMove);
            handled = true;
        }
        if (!handled && (pos.wMtrl == rV) && (pos.pieceTypeBB[Piece.WROOK] != 0)) {
            if (pos.bMtrl == pV) {
                int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]);
                score = krkpEval(pos.getKingSq(true), pos.getKingSq(false),
                        bp, pos.whiteMove);
                handled = true;
            } else if ((pos.bMtrl == bV) && (pos.pieceTypeBB[Piece.BBISHOP] != 0)) {
                score /= 8;
                final int kSq = pos.getKingSq(false);
                final int x = Position.getX(kSq);
                final int y = Position.getY(kSq);
                if ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) != 0) {
                    score += (7 - distToH1A8[7-y][7-x]) * 7;
                } else {
                    score += (7 - distToH1A8[7-y][x]) * 7;
                }
                handled = true;
            }
        }
        if (!handled && (pos.bMtrl == qV) && (pos.wMtrl == pV) && (pos.pieceTypeBB[Piece.BQUEEN] != 0)) {
            int bk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BKING]);
            int bq = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BQUEEN]);
            int wk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WKING]);
            int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]);
            score = -evalKQKP(63-bk, 63-bq, 63-wk, 63-wp, !pos.whiteMove);
            handled = true;
        }
        if (!handled && (pos.bMtrl == rV) && (pos.pieceTypeBB[Piece.BROOK] != 0)) {
            if (pos.wMtrl == pV) {
                int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]);
                score = -krkpEval(63-pos.getKingSq(false), 63-pos.getKingSq(true),
                        63-wp, !pos.whiteMove);
                handled = true;
            } else if ((pos.wMtrl == bV) && (pos.pieceTypeBB[Piece.WBISHOP] != 0)) {
                score /= 8;
                final int kSq = pos.getKingSq(true);
                final int x = Position.getX(kSq);
                final int y = Position.getY(kSq);
                if ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) != 0) {
                    score -= (7 - distToH1A8[7-y][7-x]) * 7;
                } else {
                    score -= (7 - distToH1A8[7-y][x]) * 7;
                }
                handled = true;
            }
        }
        if (!handled && (score > 0)) {
            if ((wMtrlPawns == 0) && (wMtrlNoPawns <= bMtrlNoPawns + bV)) {
                if (wMtrlNoPawns < rV) {
                    return -pos.bMtrl / 50;
                } else {
                    score /= 8;         // Too little excess material, probably draw
                    handled = true;
                }
            } else if ((pos.pieceTypeBB[Piece.WROOK] | pos.pieceTypeBB[Piece.WKNIGHT] |
                        pos.pieceTypeBB[Piece.WQUEEN]) == 0) {
                // Check for rook pawn + wrong color bishop
                if (((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskBToHFiles) == 0) &&
                    ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskLightSq) == 0) &&
                    ((pos.pieceTypeBB[Piece.BKING] & 0x0303000000000000L) != 0)) {
                    return 0;
                } else
                if (((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskAToGFiles) == 0) &&
                    ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) == 0) &&
                    ((pos.pieceTypeBB[Piece.BKING] & 0xC0C0000000000000L) != 0)) {
                    return 0;
                }
            }
        }
        if (!handled) {
            if (bMtrlPawns == 0) {
                if (wMtrlNoPawns - bMtrlNoPawns > bV) {
                    int wKnights = Long.bitCount(pos.pieceTypeBB[Piece.WKNIGHT]);
                    int wBishops = Long.bitCount(pos.pieceTypeBB[Piece.WBISHOP]);
                    if ((wKnights == 2) && (pos.wMtrl == 2 * nV) && (bMtrlNoPawns == 0)) {
                        score /= 50;    // KNNK is a draw
                    } else if ((wKnights == 1) && (wBishops == 1) && (wMtrlNoPawns == nV + bV) && (bMtrlNoPawns == 0)) {
                        score /= 10;
                        score += nV + bV + 300;
                        final int kSq = pos.getKingSq(false);
                        final int x = Position.getX(kSq);
                        final int y = Position.getY(kSq);
                        if ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) != 0) {
                            score += (7 - distToH1A8[7-y][7-x]) * 10;
                        } else {
                            score += (7 - distToH1A8[7-y][x]) * 10;
                        }
                    } else {
                        score += 300;       // Enough excess material, should win
                    }
                    handled = true;
                } else if ((wMtrlNoPawns + bMtrlNoPawns == 0) && (wMtrlPawns == pV)) { // KPK
                    int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]);
                    score = kpkEval(pos.getKingSq(true), pos.getKingSq(false),
                                    wp, pos.whiteMove);
                    handled = true;
                }
            }
        }
        if (!handled && (score < 0)) {
            if ((bMtrlPawns == 0) && (bMtrlNoPawns <= wMtrlNoPawns + bV)) {
                if (bMtrlNoPawns < rV) {
                    return pos.wMtrl / 50;
                } else {
                    score /= 8;         // Too little excess material, probably draw
                    handled = true;
                }
            } else if ((pos.pieceTypeBB[Piece.BROOK] | pos.pieceTypeBB[Piece.BKNIGHT] |
                        pos.pieceTypeBB[Piece.BQUEEN]) == 0) {
                // Check for rook pawn + wrong color bishop
                if (((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskBToHFiles) == 0) &&
                    ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) == 0) &&
                    ((pos.pieceTypeBB[Piece.WKING] & 0x0303L) != 0)) {
                    return 0;
                } else
                if (((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskAToGFiles) == 0) &&
                    ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskLightSq) == 0) &&
                    ((pos.pieceTypeBB[Piece.WKING] & 0xC0C0L) != 0)) {
                    return 0;
                }
            }
        }
        if (!handled) {
            if (wMtrlPawns == 0) {
                if (bMtrlNoPawns - wMtrlNoPawns > bV) {
                    int bKnights = Long.bitCount(pos.pieceTypeBB[Piece.BKNIGHT]);
                    int bBishops = Long.bitCount(pos.pieceTypeBB[Piece.BBISHOP]);
                    if ((bKnights == 2) && (pos.bMtrl == 2 * nV) && (wMtrlNoPawns == 0)) {
                        score /= 50;    // KNNK is a draw
                    } else if ((bKnights == 1) && (bBishops == 1) && (bMtrlNoPawns == nV + bV) && (wMtrlNoPawns == 0)) {
                        score /= 10;
                        score -= nV + bV + 300;
                        final int kSq = pos.getKingSq(true);
                        final int x = Position.getX(kSq);
                        final int y = Position.getY(kSq);
                        if ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) != 0) {
                            score -= (7 - distToH1A8[7-y][7-x]) * 10;
                        } else {
                            score -= (7 - distToH1A8[7-y][x]) * 10;
                        }
                    } else {
                        score -= 300;       // Enough excess material, should win
                    }
                    handled = true;
                } else if ((wMtrlNoPawns + bMtrlNoPawns == 0) && (bMtrlPawns == pV)) { // KPK
                    int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]);
                    score = -kpkEval(63-pos.getKingSq(false), 63-pos.getKingSq(true),
                                     63-bp, !pos.whiteMove);
                    handled = true;
                }
            }
        }
        return score;
    }

    private static int evalKQKP(int wKing, int wQueen, int bKing, int bPawn, boolean whiteMove) {
        boolean canWin = false;
        if (((1L << bKing) & 0xFFFF) == 0) {
            canWin = true; // King doesn't support pawn
        } else if (Math.abs(Position.getX(bPawn) - Position.getX(bKing)) > 2) {
            canWin = true; // King doesn't support pawn
        } else {
            switch (bPawn) {
            case 8:  // a2
                canWin = ((1L << wKing) & 0x0F1F1F1F1FL) != 0;
                if (canWin && (bKing == 0) && (Position.getX(wQueen) == 1) && !whiteMove)
                    canWin = false; // Stale-mate
                break;
            case 10: // c2
                canWin = ((1L << wKing) & 0x071F1F1FL) != 0;
                break;
            case 13: // f2
                canWin = ((1L << wKing) & 0xE0F8F8F8L) != 0;
                break;
            case 15: // h2
                canWin = ((1L << wKing) & 0xF0F8F8F8F8L) != 0;
                if (canWin && (bKing == 7) && (Position.getX(wQueen) == 6) && !whiteMove)
                    canWin = false; // Stale-mate
                break;
            default:
                canWin = true;
                break;
            }
        }

        final int dist = BitBoard.getDistance(wKing, bPawn);
        int score = qV - pV - 20 * dist;
        if (!canWin)
            score /= 50;
        return score;
    }

    private static int kpkEval(int wKing, int bKing, int wPawn, boolean whiteMove) {
        if (Position.getX(wKing) >= 4) { // Mirror X
            wKing ^= 7;
            bKing ^= 7;
            wPawn ^= 7;
        }
        int index = whiteMove ? 0 : 1;
        index = index * 32 + Position.getY(wKing)*4+Position.getX(wKing);
        index = index * 64 + bKing;
        index = index * 48 + wPawn - 8;

        int bytePos = index / 8;
        int bitPos = index % 8;
        boolean draw = (((int)kpkTable[bytePos]) & (1 << bitPos)) == 0;
        if (draw)
            return 0;
        return qV - pV / 4 * (7-Position.getY(wPawn));
    }

    private static int krkpEval(int wKing, int bKing, int bPawn, boolean whiteMove) {
        if (Position.getX(bKing) >= 4) { // Mirror X
            wKing ^= 7;
            bKing ^= 7;
            bPawn ^= 7;
        }
        int index = whiteMove ? 0 : 1;
        index = index * 32 + Position.getY(bKing)*4+Position.getX(bKing);
        index = index * 48 + bPawn - 8;
        index = index * 8 + Position.getY(wKing);
        byte mask = krkpTable[index];
        boolean canWin = (mask & (1 << Position.getX(wKing))) != 0;

        int score = rV - pV + Position.getY(bPawn) * pV / 4;
        if (canWin)
            score += 150;
        else
            score /= 50;
        return score;
    }

    /**
     * Interpolate between (x1,y1) and (x2,y2).
     * If x < x1, return y1, if x > x2 return y2. Otherwise, use linear interpolation.
     */
    static int interpolate(int x, int x1, int y1, int x2, int y2) {
        if (x > x2) {
            return y2;
        } else if (x < x1) {
            return y1;
        } else {
            return (x - x1) * (y2 - y1) / (x2 - x1) + y1;
        }
    }
}
