From: H.G.Muller Date: Fri, 21 Oct 2016 12:38:05 +0000 (+0200) Subject: Initial version of engine X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=c4793ed6e21e454ea3fee5830d51351ba642109d;p=crazywa.git Initial version of engine Crazyhouse sort of works here. --- c4793ed6e21e454ea3fee5830d51351ba642109d diff --git a/dropper.c b/dropper.c new file mode 100644 index 0000000..2c5fbae --- /dev/null +++ b/dropper.c @@ -0,0 +1,1559 @@ +#define VERSION "0.0" + +/********************************************************************************************/ +/* Simple XBoard-compatible engine for playing Chess variants with drops, by H.G. Muller. */ +/* Handles boards up to 11 x 11, with up to 16 droppable piece types, and 15 promoted types.*/ +/********************************************************************************************/ + +#include +#include +#include + +#define ON 1 +#define OFF 0 +#define INVALID 0 + +#define INF 8000 +#define MAXPLY 100 +#define MAXMOVES 500 + +#define NONE 0 +#define ANALYZE 1 +#define WHITE 32 +#define BLACK 64 +#define COLOR (WHITE | BLACK) + +#define CK_UNKNOWN 255 +#define CK_NONE 254 +#define CK_DOUBLE 253 + +#define C_DISTANT 0xFF00 +#define C_CONTACT 0x00FF + +int ply, nodeCount, forceMove, choice, rootMove, lastGameMove, rootScore, abortFlag, postThinking=1; // some frequently used data +int maxDepth=MAXPLY, timeControl=3000, mps=40, inc, timePerMove, timeLeft=1000; // TC parameters + +#define H_LOWER 1 +#define H_UPPER 2 + +int ReadClock (int start); +char *MoveToText (int move); +int TimeIsUp (int mode); + + +#define captCode (rawInts + 22*10 + 10) +#define deltaVec (rawChar + 22*10 + 10) +#define promoInc (rawChar + 21*22) +#define board (rawByte + 2*22 + 2) +#define pawnCount (board + 9*22 + 11) +#define dropType (rawByte + 15*22) +#define toDecode (rawByte + 26*22) +#define spoiler (rawByte + 37*22) +#define zoneTab (rawByte + 48*22) +#define sqr2file (rawByte + 59*22) +#define dist (rawByte + 70*22 + 22*10 + 10) +#define location (rawLocation + 23) +#define promoGain (rawGain + 1) + +int pvStack[MAXPLY*MAXPLY/2]; +int nrRanks, nrFiles, specials, pinCodes, maxDrop, moveSP, pawn, *pvPtr = pvStack, boardEnd, searchNr; +int rawInts[21*22], pieceValues[96], pieceCode[96]; +signed char rawChar[32*22], steps[512]; +unsigned char rawByte[91*22], firstDir[64], rawBulk[98], handSlot[97], promoCode[96], aVal[64], vVal[64], rawLocation[96+23]; +long long int handKey[96], pawnKey; +int handVal[96], rawGain[97]; +unsigned int moveStack[500*MAXPLY]; +int killers[MAXPLY][2]; +int path[MAXPLY], deprec[MAXPLY]; +int repKey[512+20]; + +/* + Drops: piece is the (negated) count, promo the piece to be dropped. + The from-square has to be cleared for moves, but incremented for drops. + piece = board[from]; + board[from] = (piece >> 7) & (piece + 1); // 0 if piece >= 0 + Promotion on moves indicated by to-square + promo = piece + promoTab[to]; + to = toTab[to]; + On drops, however, it is determined by the from-square + promo = dropTab[from]; + Combine: + promo = dropTab[from] + promoTab[to] + (piece & ~mask); + + piece = board[from]; // off-board is negative + mask = piece >> 7; // on board 0, off board -1 + board[from] = mask & (piece + 1); // + promo = dropTab[from] + promoTab[to]; + + + dropTab[] = { + 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, // off-board from-square contains piece codes + 0, 0, 0, 0, 0, 6, 7, 8, 9,10, + 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, + }; + promoTab[] = { + 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, + 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, + 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, + 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, + 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, + }; + + encoding: + 0 = empty + 1-16 = unpromoted pieces SP SC SO FC FG CM BD VS VW OC LH SW FF RR TR CE + 17-31 = promoted pieces GB FF CE RF SW VS VW RB BE PO HH GS TF TR K + promotion adds 16 + handTab[] = { 11 12 13 14 15 16 17 18 19 20 21 33 34 34 36 37 + + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 }; + P N B R Q + N~ B~ R~ Q~ K + promotion adds 17-20 + 32 = white + 64 = black + 128 = off board + negative piece types = hand counts + all of those point to same PST, which only has to exist for off-board squares + */ + +// Capture codes +// the needs of shogi and chess can be met with 8 contact and 4 distant bits, +// by splitting the Knight and Rook in f, b and s components +// wa needs additional f/bW2 (LH) and f/bW3 (CE) distant, and AvD (TF) instead of sN +// Chess fF, bF, fW, bW, sW, fN, bN, sN (6) R, B (2) +// wP x +// bP x +// N x x x +// B x x x +// R x x x x +// Q x x x x x x x +// K x x x x x +// Shogi fF, bF, fW, bW, sW, fN, bN (7) fR, bR, sR, B (5) +// wP x +// wL x +// wN x +// wS x x x +// wG x x x x +// B x x x +// R x x x x x x +// +B x x x x x x +// +R x x x x x x x x +// K x x x x x +// Tori fFbD, bFfD, fW, bW, sW, F, fAbD, bAfD (8) fRblB, fRbrB, bRflB, bRfrB, fA, bA (6) +// wP x +// wPh x +// wCr x x x +// wQl x x x +// wQr x x x +// wFa x x x +// +wP x +// +wFa x x x x x x x +// K x x x x +// Wa fF, bF, fW, bW, sW, vN, AvD (7) fR, bR, sR, B, fGfA, bGbA, fD, bD (8) +/* + The slider tracks skip the first square. + Test for slider alignment first. If hit, we do ray scan, and never try leaps. + This allows slides to mask jumps, so that a Quail can have bFfD + fRlbB. + */ + +// for each of the 16 bits in the capture codes there is a zero-terminated list of board steps to which it corresponds +// the first 8 are leaps, the next 4 unlimited-range sliders, than a pair of range 2 and a pair of range 3. +signed char +toriCodes[] = { 21,23,-44,0, -21,-23,44,0, 22,0,-22,0, 1,-1,0, 21,23,-21,-23,0, 42,46,-44,0, -42,-46,44,0, + 22,-23,0, 22,-21,0, -22,23,0, -22,21,0, 21,23,0, -21,-23,0, 0,0 }, +chessCodes[] = { 21,23,0, -21,-23,0, 22,0, -22,0, 1,-1,0, 43,45,0, -43,-45,0, 20,24,-20,-24,0, 22,0, -22,0, 1,-1,0, 21,23,-21,-23,0,0,0,0,0,0,0}, +shogiCodes[] = { 21,23,0, -21,-23,0, 22,0, -22,0, 1,-1,0, 43,45,0, -43,-45,0, 42,44,46,-42,-44,-46,0, 22,0, -22,0, 1,-1,0, 21,23,-21,-23,0, + 22,0, -22,0, 21,23,0, -21,-23,0 }; + +void +InitCaptureCodes(signed char *codes) +{ + int i, piece, dir=0; + for(i=-10-10*22; i<=10+10*22; i++) captCode[i] = 0; // clear capture codes and step vectors + // build variant-specific alignment map, marking each square with the capture sets to which it belongs + for(i=0; i<16; i++) { // for all 16 capture sets + int step, b = 1<= 12) range = i/2 - 4; // 2 or 3, for sets 12/13 and 14/15, respectively + while((step = codes[dir++])) { + if(i < 8) captCode[step] |= b; else { // first 8 capture sets are leaps + int d; // other are slides, so scan ray + for(d=2; d<=range; d++) captCode[d*step] |= b; + for(d=1; d<=10; d++) deltaVec[d*step] = step, dist[d*step] = d; // step for sliding to square + } + } + } + // collect codes of squares that each piece hits + for(piece=WHITE; piece> 3; + if(c) code |= c; // jump excludes slides to same square (which would mask it) + for(i=1; i<=range; i++) captCode[i*step] ^= -1; // flip reachable codes, clear bits + } + for(i=-10-10*22; i<=10+10*22; i++) { // scan over all possible moves + int c = captCode[i]; + if(c < 0) captCode[i] ^= -1; // restore reachable squares to normal + else code |= c; // set bits of sets that contain squares we cannot reach + } + pieceCode[piece] = code ^ 0xFFFF; +#if 0 + { + code = captCode[step] & C_CONTACT; // first step: unblockable codes + for(i=2; i<=range; i++) code |= captCode[i*step] & C_DISTANT; // later steps are blockable + } +#endif + } +} + +/* + Piece encoding + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + Wa: SP OC LH SC SO CM BD FC FG VS VW SW FF RR CE Tr + GB PO HH FF CE VS VW RF SW RB BE GS TF Tr CK + Shogi: P N B R L S R + +P +N H D +L +S K + Tori: S Ph Cr Ql Qr Fa + G Ea Px + Chess: P N B R A C Q + Q~ N~ B~ R~ A~ C~ K + */ + +unsigned char handSlot[97], rawBulk[98]; + +#define pawnBulk (rawBulk + 1) +#define maxBulk (rawBulk + 1 + 30) /* piece type 30 is not even used in Wa */ + +// Move tables: (step,range) pair for each direction a piece moves in, terminated by (0,0) sentinel +// divergent moves (for FIDE Pawn) are flagged in high nibble of range byte +// *** Note that this is re-packed at run time to have flags in low three bits! *** +#define MOVE_ONLY 32 +#define CAPT_ONLY (16+32) + +signed char steps[] = { + 21,10, 23,10, -21,10, -23,10, 20,1, 24,1, -20,1, -24,1, 43,1, 45,1, -43,1, -45,1, 0,0, // A=0, N=4, HH=8, bN=10 + 1,10, -1,10, 22,10, -22,10, 21,10, 23,10, -21,10, -23,10, 0,0, // Q=13 + 21,1, -21,1, 23,1, -23,1, 1,10, -1,10, 22,10, -22,10, 0,0, // +R=22, R=26 + 1,1, -1,1, 22,1, -22,1, 21,10, 23,10, -21,10, -23,10, 0,0, // +B=31, bFF=34, B=35 + 1,1, -1,1, 22,1, -22,1, 21,1, 23,1, -21,1, -23,1, 0,0, // K=40, FL=42, bS=43 + 22,2+MOVE_ONLY, 21,1+CAPT_ONLY, 23,1+CAPT_ONLY, 0,0, // wP=49 + -22,2+MOVE_ONLY, -21,1+CAPT_ONLY, -23,1+CAPT_ONLY, 0,0, // bP=53 + 1,1, -1,1, 22,1, 21,1, 23,1, -22,1, 0,0, // wG=57, wC=59, bSO=60, bP=62 + 1,1, -1,1, -22,1, -21,1, -23,1, 22,1, 0,0, // bG=64, bC=66, wSO=67, wP=69 + -21,1, -23,1, -22,1, 1,1, -1,1, 21,1, 23,1, 0,0, // bDE=71, wBD=73, wFC=74 + 21,1, 23,1, 22,1, 1,1, -1,1, -21,1, -23,1, 0,0, // wDE=79, bBD=81, bFC=82 + 22,1, 21,1, 23,1, -21,1, -23,1, 0,0, // wS=87 + 22,2, -22,10, 0,0, // bLH=93, bL=94 + -22,2, 22,10, 0,0, // wLH=96, wL=97 + 22,1, -22,1, 21,1, 23,1, -21,1, -23,1, -44,1, 46,1, 42,1, -46,1, -42,1, 44,1, 0,0, // TF=99, +wSw=108 + 46,1, 42,1, -44,1, 0,0, // +bSw=112 + 22,1, -22,1, 1,10, -1,10, 0,0, // SW=116 + 22,10, -22,1, 21,1, 23,1, -21,1, -23,1, 0,0, // wRR=121 + -22,10, 22,1, -21,1, -23,1, 21,1, 23,1, 0,0, // bRR=128 + 22,1, 21,10, 23,10, -21,10, -23,10, 0,0, // wFF=135 + 1,1, -1,1, 22,10, -22,10, 21,10, 23,10, -21,10, -23,10, 0,0, // TF=141 + 1,1, -1,1, 22,10, -22,10, 21,3, 23,3, -21,1, -23,1, 0,0, // wCE=150 + 1,1, -1,1, 22,10, -22,10, -21,3, -23,3, 21,1, 23,1, 0,0, // bCE=159 + 1,1, -1,1, 22,1, -22,10, 21,10, 23,10, -21,2, -23,2, 0,0, // +wFa=168 + 1,1, -1,1, 22,10, -22,1, -21,10, -23,10, 21,2, 23,2, 0,0, // +bFa=177 + 43,1, 45,1, 0,0, // wN=186 + 44,1, -21,1, -23,1 ,0,0, // wPh=189 + -44,1, 21,1, 23,1, 0,0, // bPh=193 + 22,10, -21,10, -23,1, 0,0, // wQl=197 + 22,10, -21,1, -23,10, 0,0, // wQr=201 + -22,10, 21,10, 23,1, 0,0, // bQl=205 + -22,10, 21,1, 23,10, 0,0, // bQr=209 + 22,10, 21,1, 23,1, 1,1, -1,1, -22,10, 0,0, // wRF=213 + -22,10, -21,1, -23,1, 1,1, -1,1, 22,10, 0,0, // bRF=220 +}; + +// first direction in 'steps' table for the various piece types +// organized in four sections, each terminated by 255: unprom white, prom white, unprom black, prom black +// first of unprom section is always king (which in reality is type 31, while unprom = 0-15 and prom = 16-31) + +int normalValue[] = {0, 10, 50, 130, 150, 60, 80, 90, 0, 110, 96, 155, 180, 94, 92, 90, // basic and promoted + 0, 20, 60, 150, 170, 75, 88, 90, 0, 20, 90, 165, 190, 95,100,110 }; // pre-promoted and in hand +int euroValue[] = {0, 10, 50, 130, 150, 80, 80, 90, 0, 110, 96, 155, 180, 94, 92, 90, // basic and promoted + 0, 20, 60, 150, 170, 80, 88, 90, 0, 20, 90, 165, 190, 95,100,110 }; // pre-promoted and in hand +int miniValue[] = {0, 20, 50, 81, 110, 60, 65, 79, 0, 99, 96, 125, 140, 94, 80, 79, // basic and promoted + 0, 20, 60, 120, 135, 70, 79, 79, 0, 40, 90, 105, 130, 95, 81, 84 }; // pre-promoted and in hand +int judkinValue[] = {0, 20, 50, 96, 115, 40, 65, 79, 0, 99, 96, 140, 155, 81, 80, 79, // basic and promoted + 0, 20, 60, 135, 150, 70, 79, 79, 0, 25, 90, 120, 135, 70, 81, 84 }; // pre-promoted and in hand +int toriValue[] = {0, 20, 0, 70, 70, 40, 90, 79, 0, 50, 0, 0, 0, 0,150, 79, // basic and promoted + 0, 20, 0, 70, 70, 40,140, 79, 0, 25, 0, 100, 100, 60,130,100 }; // pre-promoted and in hand +int +chessValues[] = { 100, 315, 300, 375, 600, -1, 700, 340, 325, 450, -1, 150, 385, 350, 400, 600, -1 }, +shogiValues[] = { 10, 80, 90, 130, 150, 50, 60, -1, 110, 92, 90, 155, 180, 96, 94, -1, 20, 100, 110, 165, 190, 90, 95, -1 }, +miniValues[] = { 20, 65, 79, 81, 110, -1, 99, 80, 79, 125, 140, -1, 40, 81, 84, 105, 130, -1 }, +judkinValues[] = { -1, -1, -1 }, +toriValues[] = { -1, -1, -1 }, +waValues[] = { -1, -1, -1 }; + +unsigned char +chessDirs[] = { 40, 49, 4, 35, 26, 13, 255, 13, 4, 35, 26, 255, 40, 53, 4, 35, 26, 13, 255, 13, 4, 35, 26, 255 }, // K,P,N,B,R,Q / Q~,N~,B~,R~ +shogiDirs[] = { 40, 69, 87, 57, 35, 26, 186, 97, 255, 57, 57, 57, 31, 22, 57, 57, 255, // K,P,S,G,B,R,N,L / +P,+S,-,DH,DK,+N,+L + 40, 62, 43, 64, 35, 26, 10, 94, 255, 64, 64, 64, 31, 22, 64, 64, 255 }, +toriDirs[] = { 40, 69, 189, 197, 201, 42, 79, 255, 108, 0, 0, 0, 0, 168, 255, // Ph, S, Pt, Ql, Qr, Cr, Fa / G - - - - Ea + 40, 62, 193, 205, 209, 42, 71, 255, 112, 0, 0, 0, 0, 177, 255 }, +waDirs[] = { 40, 69, 67, 67, 74, 59, 59, 73, 87, 57, 97, 96, 116, 121, 135, 99, 150, 255, // CK,SP,SC,SO,FC,CM,FG,BD,VS,VW,OC,LH,SW,RR,FF,Tr,CE + 57,135,150,213, 87,116, 57, 79, 40, 40, 8, 26, 99, 141, 255, // GB,FF,CE,RF,VS,SW,VW,RB,BE,PO,HH,GS,Tr,TF + 40, 62, 60, 60, 82, 66, 66, 81, 43, 64, 94, 93, 116, 128, 34, 99, 159, 255, + 64, 34,159,220, 43,116, 64, 71, 40, 40, 8, 26, 99, 141, 255 }, + +chessIDs[] = "PNBRQ", +shogiIDs[] = "PSGBRNL", +toriIDs[] = "SPLRCF", +waIDs[] = "PCOQMGDSVLHWRFXE", + +chessFEN[] = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -", +shogiFEN[] = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w", +toriFEN[] = "rpckcpl/3f3/sssssss/2s1S2/SSSSSSS/3F3/LPCKCPR w", +waFEN[] = "hmoqskvgcdl/1e3w3f1/ppprpppxppp/3p3p3/11/11/11/3P3P3/PPPXPPPRPPP/1F3W3E1/LDCGVKSQOMH w", +miniFEN[] = "rbsgk/4p/5/P4/KGSBR w", +judkinFEN[]= "rnbsgk/5p/6/6/P5/KGSBNR w", +euroFEN[] = "r w", + +// promotion codes for unpromoted pieces. Will be ANDed with Z_WHITE or Z_BLACK to fill promoCode[] table + +#define Z_FIDE 1 /* piece has promotion choice */ +#define Z_2ND 0x66 /* must promote on last 2 ranks */ +#define Z_MUST 0x78 /* must promote in entire zone */ +#define Z_WHITE 0x2B /* bits applying to white zone */ +#define Z_BLACK 0x55 /* bits applying to black zone */ +#define Z_LAST 0x80 /* ???? */ +#define Z_DOUBLE 0x80 /* where Pawns have double step */ + +chessProms[16] = { Z_FIDE|Z_MUST }, +shogiProms[16] = { Z_MUST, COLOR, 0, Z_MUST, Z_MUST, Z_2ND, Z_2ND }, +toriProms[16] = { Z_MUST, 0, 0, 0, 0, Z_MUST }, +waProms[16] = { Z_MUST, Z_MUST, Z_MUST, Z_MUST, COLOR, COLOR, Z_MUST, Z_MUST, Z_MUST, COLOR, COLOR, Z_MUST, Z_MUST, Z_MUST }; + +typedef struct { + int files, ranks, hand, zone; + char *name; + unsigned char *pieces, *fen, *dirs, *proms; + signed char *codes; + int *values; +} VariantDesc; + +VariantDesc variants[] = { + { 8, 8, 5, 1, "crazyhouse\n", chessIDs, chessFEN, chessDirs, chessProms, chessCodes, chessValues }, + { 5, 5, 5, 1, "minishogi\n", shogiIDs, miniFEN, shogiDirs, shogiProms, shogiCodes, miniValues }, + { 6, 6, 6, 2, "judkinshogi\n", shogiIDs, judkinFEN, shogiDirs, shogiProms, shogiCodes, judkinValues }, + { 7, 7, 6, 2, "torishogi\n", toriIDs, toriFEN, toriDirs, toriProms, toriCodes, toriValues }, + { 9, 9, 7, 3, "shogi\n", shogiIDs, shogiFEN, shogiDirs, shogiProms, shogiCodes, shogiValues }, + { 11, 11, 16, 3, "crazywa\n", waIDs, waFEN, waDirs, waProms, shogiCodes, waValues }, +}; + +// info per piece type. sometimes indexed by negated holdings count instead of piece +#define pieceKey (rawKey+1) +#define PST (rawPST+1) +signed char *rawPST[COLOR+1]; // PST[-1...95] indexed by 'mutation', which is -1 for drops +unsigned int rawKey[COLOR+1]; +unsigned int squareKey[22*11]; + +// piece-square tables. White and black tables interleave. The first two pairs are (0, center) and (hand1, ???) +#define center (pstData + 22*11) +#define hand1 (pstData + 22*11) /* beware: uses off-board part only */ +#define sparePST (pstData + 22*11) /* on-board part still available for something else */ +#define pawnPST (pstData + 22*11*2) /* from here interleaved (white, black) */ +#define kingPST (pstData + 22*11*3) + +signed char pstData[22*11*8]; // actual tables (for now 8 pairs) + +int +MyRandom () +{ + return (rand() >> 10) + rand() + (rand() << 10) + (rand() << 20); +} + +void +EngineInit () +{ + int i, r, f; + for(i=1; i> 4 | steps[i] << 3 & 127; + if(steps[i] & 7) steps[i] -= 8; + } + // set tables for on-the-fly Zobrist key creation as pieceKey[piece]*squareKey[sqr] + for(r=0; r<11; r++) for(f=0; f<11; f++) { + int sqr = 22*r + f; + squareKey[sqr] = MyRandom(); squareKey[sqr+11] = MyRandom() << 16; // low 16 bits 0 for holdings squares + } + for(r=WHITE; r0;) if(!strcmp(name, variants[v].name)) break; +printf("# variant %d: %s\n", v, variants[v].name); + nrFiles = variants[v].files; + nrRanks = variants[v].ranks; + zone = variants[v].zone; + maxDrop = variants[v].hand - 1; + pieces = variants[v].pieces; + moves = variants[v].dirs; + startPos= variants[v].fen; + codes = variants[v].proms; + + // board + ClearBoard(); + boardEnd = specials = 22*nrRanks; + + for(i=0; i= 0; i++) pieceValues[WHITE+i] = pieceValues[BLACK+i] = *ip++; // basic + for(i=16,ip++; *ip >= 0; i++) pieceValues[WHITE+i] = pieceValues[BLACK+i] = *ip++; // promoted + for(i=0, ip++; *ip >= 0; i++) handVal[WHITE+i] = handVal[BLACK+i] = *ip++; // in hand + pawn = 2*handVal[WHITE] << 21; // used for detection of material-loosing loops + for(i=0; i<16; i++) { + int demoted = dropType[handSlot[WHITE+i+16]]-1; // piece type after demotion (could be Pawn, in Chess) + handVal[WHITE+i+16] = handVal[BLACK+i+16] = pieceValues[WHITE+i+16] + handVal[demoted]; // gain by capturing promoted piece + } + for(i=0; i<16; i++) handVal[WHITE+i] = handVal[BLACK+i] += pieceValues[WHITE+i]; // gain by capturing base piece + for(i=0; i<16; i++) { + int demoted = dropType[handSlot[WHITE+i+16]]-1; // piece type after demotion (could be Pawn, in Chess) + promoGain[WHITE+i+16] = promoGain[BLACK+i+16] = pieceValues[WHITE+i+16] - pieceValues[demoted]; + } + for(i=WHITE; i>8 & 255, (i&15) == 15 ? "\n" : ""); + PrintValues("pieceValues:", pieceValues, 0); + PrintValues("handVal:", handVal, 0); + PrintValues("promoGain:", promoGain, 96); + PrintPieces("vVal:", vVal, 64); + PrintPieces("aVal:", aVal, 64); + PrintDBoard("hand PST:", PST[-1], " ", 11); + PrintDBoard("Pawn PST:", PST[WHITE], " ", 11); + PrintDBoard("board:", board, " ", 11); +} + +#define KEY(A, B) (pieceKey[A]*(Key) squareKey[B]) + +typedef long long int Key; + +typedef struct { // 12 bytes + unsigned int lock; + short int score; + unsigned short int move; + unsigned char depth; + unsigned char flags; + unsigned char checker; + char age; +} HashEntry; + +typedef struct { + Key hashKey, newKey; + unsigned char fromSqr, toSqr, captSqr, epSqr, rookSqr, rights; + signed char fromPiece, toPiece, victim, savePiece, rook, mutation; + int pstEval, newEval; + int move, depth; + int checker, checkDir, checkDist, xking; +} StackFrame; + +typedef struct { // move stack sectioning + int firstMove; // start of move list for current ply + int unsorted; // start of unsorted tail of move list + int nonCapts; // index of first non-capture in move list + int drops; // index of first quiet drop + int stage; // stage of move generation (0=hash/capt/prom, 1=killer/noncapt, 2=check-drops, 3=quiet drops) + int late; // start of late moves + int epSqr; + int checker; +} MoveStack; + +HashEntry *hashTable; +Key hashKey, pawnKey; +int hashMask; + +int +Evaluate (int stm) +{ + int k, score = 0; + k = location[31]; + score += ((board[k+22] == WHITE) + (board[k+22+1] == WHITE) + (board[k+22-1] == WHITE))*2; + score -= !board[k+22] + !board[k+22+1] + !board[k+22-1]; + score -= ((board[k+44] == BLACK) + (board[k+44+1] == BLACK) + (board[k+44-1] == BLACK))*5; + k = location[WHITE+31]; + score -= ((board[k-22] == BLACK) + (board[k-22+1] == BLACK) + (board[k-22-1] == BLACK))*2; + score += !board[k-22] + !board[k-22+1] + !board[k-22-1]; + score += ((board[k-44] == WHITE) + (board[k-44+1] == WHITE) + (board[k-44-1] == WHITE))*5; + score *= 20; + return stm == WHITE ? score : -score; +} + +int +PseudoLegal (int stm, int move) +{ // used for testing killers, so we can assume the move must be pseudo-legal for the stm in some position + int match, from = move >> 8 & 0xFF, to = move & 255; + signed char piece = board[from]; + to = toDecode[to]; + if(piece < 0) { // drop + if(piece == -1) return 0; // type not in hand + piece = dropType[from] - 1; + if((piece & ~COLOR) == 0 && pawnCount[sqr2file[to]] & maxBulk[stm]) return 0; // Pawn drop would over-crowd file + return (board[to] == 0); // otherwise drop is legal on empty square (assumes drop location always legal for piece type) + } + if(!(piece & stm)) return 0; // piece has wrong color + match = pieceCode[piece] & captCode[to - from]; + if(!match) return 0; // not aligned + if(match & C_DISTANT) { // distant alignment + int step = deltaVec[to - from]; + while(board[to -= step] == 0) {} // ray scan towards mover + return (from == to); // legal if it reaches mover + } + return 1; // must be leaper alignment, which guarantees hit +} + +void +Dump (char *s) +{int i; printf("%s\n",s); for(i=0; ifirstMove = m->nonCapts = m->late = moveSP; m->stage = 0; + for(r=0; repSqr) { // reaches e.p. square: must be through diagonal move, and e.p. square is always empty + moveStack[--m->firstMove] = to + 4*22+11 + 44*(stm == BLACK) | from << 8 | vVal[0] << 24; + break; + } + if(!victim == (range & 1)) break; // wrong type (lsb set = capture only, cleared = move only) + if(range > 7) { // can do double push + if(inZone & Z_DOUBLE && board[to+step] == 0) { // started on Pawn rank, and square in front of it is empty + moveStack[moveSP++] = from << 8 | to + step + 22*5; // generate now, as special move + } + range -= 8; // make sure it is not done again + } + } + if((victim & ~COLOR) == 31) return 1; // captures King; abort! + move = piece << 16; // store piece in move + promote = (inZone | zoneTab[to]) & promoCode[piece-WHITE]; + if((promote & (Z_2ND | Z_MUST )) == 0) { // not in place where deferral forbidden + int slot; + if(victim) { // capture + slot = --m->firstMove; + move += vVal[victim-WHITE] - aVal[piece-WHITE] << 24; // MVV/LVA sort code + } else { // non-capture + slot = moveSP++; + } + moveStack[slot] = move | from << 8 | to; + } + if(promote & stm) { // promotion is (also?) possible + moveStack[--m->firstMove] = (move | from << 8 | to + 11) + (vVal[victim-WHITE] + 20 << 24); // put it amongst captures + if(promoCode[piece-WHITE] & Z_FIDE) { // only for FIDE Pawns + moveStack[--m->firstMove] = move | from << 8 | to - 11 + (stm >> 6)*44; // under-promotion + // other under-promotions could go here (Capahouse?) + } + } + + + if((range -= 8) <= 0) break; // range exhausted + } while(!victim); + + } + } + } + m->unsorted = m->firstMove; m->drops = moveSP; + + return 0; +} + +void +CheckDrops (int stm, int king) +{ + int i; + stm ^= COLOR; + for(i=maxDrop; i>=0; i--) { + int piece = stm + i, from = handSlot[piece]; + if((signed char)board[from] < -1) { // piece type is in hand + int step, dir = 2*firstDir[piece-WHITE]; + while((step = steps[dir++])) { + int to = king, range = steps[dir++]; + if((range & 3) == 2) break; // non-capture direction + while(board[to-=step] == 0) { + moveStack[moveSP++] = to | from << 8; + if((range -= 8) <= 0) break; + } + } + } + } +} + +void +EvasionDrops (int stm, StackFrame *f) +{ + int i, x = f->checker, v = f->checkDir; + stm ^= COLOR; + while(board[x+=v] == 0) { // all squares on check ray + i = (x < 22 || x >= 7*22); // not on last rang. *** WRONG FOR SHOGI *** + for(i=0; i<=maxDrop; i++) { // all droppable types + int piece = stm + i, from = handSlot[piece]; + if((signed char)board[from] < -1) // piece type is in hand + moveStack[moveSP++] = from << 8 | x; + } + } +} + +void +AllDrops (int stm) +{ + int i, start = 22, end = boardEnd-22; + stm ^= COLOR; + for(i=0; i<=maxDrop; i++) { + int piece = stm + i, from = handSlot[piece]; + if((signed char)board[from] < -1) { // piece type is in hand + int r, f; + for(r=start; rfromPiece & ~COLOR) != 31) { // moves non-royal (or drops) + int d; + if(f->checker == CK_DOUBLE) return 1; // never helps against double check + if(f->toSqr == f->checker) return 0; // captures only checker: OK + d = dist[f->checker - f->toSqr]; + if(d && deltaVec[f->toSqr - f->checker] == f->checkDir && d < f->checkDist) return 0; // interposes: OK + return 1; + } + // king move + return 0; // for now, defer testing to daughter node +} + +int +MakeMove (StackFrame *f, int move) +{ + int to; + f->fromSqr = move >> 8 & 255; + to = move & 255; + f->toSqr = f->captSqr = toDecode[to]; // real to-square for to-encoded special moves + f->fromPiece = board[f->fromSqr]; // occupant or (for drops) complemented holdings count + if(f->checker != CK_NONE && NonEvade(f)) return 0; // abort if move did not evade existing check + f->mutation = (f->fromPiece >> 7) | f->fromPiece; // occupant or (for drops) -1 + f->toPiece = f->mutation + promoInc[to] + dropType[f->fromSqr]; // (possibly promoted) occupant or (for drops) piece to drop + f->savePiece = board[f->toSqr]; // replacement victim + f->newEval = f->pstEval; f->newKey = f->hashKey; // start building new key and eval + f->epSqr = 255; + f->rookSqr = sqr2file[f->toSqr] + (pawnCount - board); // normally (i.e. when not castling) use for pawnCount + f->rook = board[f->rookSqr]; // save and update Pawn occupancy + board[f->rookSqr] = f->rook + pawnBulk[f->toPiece] - pawnBulk[f->mutation] - pawnBulk[f->savePiece]; // assumes all on same file! +//printf("f=%02x t=%02x fp=%02x tp=%02x sp=%02x mut=%02x ep=%02x\n", f->fromSqr, f->toSqr, f->fromPiece, f->toPiece, f->savePiece, f->mutation, f->epSqr); + if(to >= specials) { // treat special moves for Chess + if(sqr2file[to] > 11) { // e.p. capture, shift capture square +//printf("# e.p. %02x\n", to); + f->captSqr = toDecode[to-11]; // use to-codes from double pushes, which happen to be what we need + f->victim = board[f->captSqr]; + board[f->captSqr] = 0; // e.p. is only case with toSqr != captSqr where we have to clear captSqr + } else if(sqr2file[to] < 8) { // double push + int xpawn = f->toPiece ^ COLOR; // enemy Pawn + if(board[f->toSqr + 1] == xpawn || // if land next to one + board[f->toSqr - 1] == xpawn ) { + f->epSqr = (f->fromSqr + f->toSqr) >> 1; // set e.p. rights + } + f->victim = 0; // for key and pst update + } else { // castling. at this point we are set up to 'promote' a King to Rook (so the check tests sees the Rook, and UnMake restores location[K]) + f->rookSqr = zoneTab[to]; // Rook from-square + f->rook = board[f->rookSqr]; // arrange Rook to be put back on UnMake (pawnCount is never modified in chess) + board[f->rookSqr] = 0; // and remove it + f->newEval -= PST[f->toPiece][f->rookSqr]; + f->newKey -= KEY(f->toPiece, f->rookSqr); + f->captSqr = dropType[to]; // this tabulates to-square of the King + f->victim = board[f->captSqr]; // should be 0, but who knows? + board[f->captSqr] = f->mutation; // place the King + f->newEval += PST[f->mutation][f->captSqr]; + f->newKey += KEY(f->mutation, f->captSqr); + location[f->mutation] = f->captSqr; // be sure King location stays known + } + } else f->victim = f->savePiece; // for normal moves replacement victim counts + board[f->fromSqr] = f->fromPiece - f->mutation; // 0 or (for drops) decremented count + board[f->toSqr] = f->toPiece; + board[handSlot[f->victim]]--; // put victim in holdings +//printf("# capt=%02x vic=%02x slot=%02x\n", f->captSqr, f->victim, handSlot[f->victim]); + + f->newEval += promoGain[f->toPiece] - promoGain[f->mutation] + handVal[f->victim] + + PST[f->toPiece][f->toSqr] - PST[f->mutation][f->fromSqr] + PST[f->victim][f->captSqr]; + f->newKey += KEY(f->toPiece, f->toSqr) - KEY(f->mutation, f->fromSqr) - KEY(f->victim, f->captSqr) + handKey[f->victim]; + location[f->toPiece] = f->toSqr; + + return 1; +} + +void +UnMake (StackFrame *f) +{ + board[f->rookSqr] = f->rook; // restore either pawnCount or (after castling) Rook from-square + board[f->captSqr] = f->victim; // differs from toSqr on e.p. (Pawn to-square) and castling, (King to-square) and should be cleared then + board[f->toSqr] = f->savePiece; // put back the regularly captured piece (for castling that captured by Rook) + board[f->fromSqr] = f->fromPiece; // and the mover + board[handSlot[f->victim]]++; + location[f->fromPiece] = f->fromSqr; +} + +#define PATH 0 +//ply==0 || path[0]==0x0017b1 && (ply==1 || (ply==2)) + +int +Search (int stm, int alpha, int beta, StackFrame *ff, int depth, int reduction, int maxDepth) +{ + MoveStack m; StackFrame f; HashEntry *entry; + int oldSP = moveSP, *pvStart = pvPtr; + int killer1 = killers[ply][0], killer2 = killers[ply][1], hashMove; + int bestNr, bestScore, startAlpha, startScore, resultDepth, iterDepth=0; + int hit, hashKeyH; + int curEval, score; + + // legality + int earlyGen = (ff->toPiece == stm+31 || ff->toSqr != ff->captSqr); // King was moved, or e.p. +if(ply > 90) Dump("maxply"); + f.xking = location[stm+31]; // opponent King, as stm not yet toggled + if(!earlyGen) { // if other piece was moved, abort with +INF score if it was pinned + if(ff->mutation > 0) { // exclude drops + int vec = f.xking - ff->fromSqr; + int match = captCode[vec] & pinCodes; + if(match) { + int x = f.xking, v = deltaVec[vec]; + while(board[x-=v] == 0) {} + if(!(board[x] & stm) && captCode[f.xking-x] & pieceCode[board[x]]) return INF; // guards & counters tests as own piece! + } + } + } + + // some housekeeping + stm ^= COLOR; + f.hashKey = ff->newKey; + f.pstEval = -ff->newEval; + f.rights = ff->rights | spoiler[ff->toSqr] | spoiler[ff->fromSqr]; + m.epSqr = ff->epSqr; // put in m, because MoveGen needs it +if(PATH)printf("%d:%d Hash Probe %016llx\n",ply,depth,f.hashKey); + // hash probe + hashKeyH = f.hashKey >> 32; + entry = hashTable + (f.hashKey + (stm + 9849)*(m.epSqr + 51451) & hashMask); + if(entry->lock == hashKeyH || (++entry)->lock == hashKeyH || (++entry)->lock == hashKeyH || (++entry)->lock == hashKeyH) { // 4 possible entries + int score = entry->score, d = entry->depth; + f.checker = entry->checker; f.checkDist = 0; + if(f.checker != CK_NONE) { // in check; restore info needed in evasion test + if(sqr2file[f.checker] != 12) f.checkDir = 0; else { // off-board represents on-board distant check + int vec = location[stm+31] - (f.checker -= 11); + f.checkDir = deltaVec[vec]; + f.checkDist = dist[vec]; + } + reduction = 0; // checks are not reduced + } +if(PATH)printf(" Hit, d=%d, checker = %x\n",entry->depth,f.checker); + if((entry->flags & H_LOWER || entry->score <= alpha) && (entry->flags & H_UPPER || entry->score >= beta)) { // compatible bound + d += (score >= beta)*reduction; // fail highs need to satisfy reduced depth only, so we fake higher depth than actually found + if(score > alpha && d >= depth || d >= maxDepth) { // sufficient depth + ff->depth = d + 1; return entry->score; // depth was sufficient, take hash cutoff + } + } + hashMove = entry->move; +if(hashMove && board[hashMove>>8&255] == 0) {char s[100];sprintf(s,"bad hash move %16llx: %s\n", f.hashKey, MoveToText(hashMove)); Dump(s); } + hit = 1; + } else hit = hashMove = 0, f.checker = CK_UNKNOWN; + + moveSP += 48; // create space for non-captures + if(earlyGen) { // last moved piece was King + if(MoveGen(stm, &m)) { moveSP = oldSP; return INF; } // make sure we detect if he moved into check + } + + if((++nodeCount & 0xFFF) == 0) abortFlag |= TimeIsUp(3); // check time limit every 4K nodes + curEval = f.pstEval + Evaluate(stm); + alpha -= (alpha < curEval); //pre-compensate delayed-loss bonus + beta -= (beta <= curEval); + killers[ply+1][0] = killers[ply+1][1] = 0; + if(-INF >= beta) { moveSP = oldSP; return -INF+1; } + + + // check test + if(f.checker == CK_UNKNOWN) { // hash did not supply it + int king = location[stm+31]; // own King + int vec = king - ff->toSqr; + int match = captCode[vec] & pieceCode[ff->toPiece]; + f.checker = CK_NONE; f.checkDist = 0; // assume not in check + if(match & C_DISTANT) { // moving piece is aligned + int x = ff->toSqr, v = deltaVec[vec]; + while(board[x+=v] == 0) {} // scan ray + if(x == king) f.checker = ff->toSqr, f.checkDir = v, f.checkDist = dist[vec]; // ray is clear, distant check + } else if(match & C_CONTACT) f.checker = ff->toSqr, f.checkDir = 0; // contact check + if(ff->mutation != -1) { // board move (no drop) + vec = king - ff->fromSqr; + match = captCode[vec] & pinCodes; + if(match) { // from-square is aligned + int x = king, v = deltaVec[vec]; + while(board[x-=v] == 0) {} // scan ray + if(f.checker != x && !(board[x] & stm) && captCode[king-x] & pieceCode[board[x]]) { // discovered check + if(f.checker != CK_NONE) f.checker = CK_DOUBLE; + else f.checker = x, f.checkDir = v, f.checkDist = dist[x-king]; + } + } + if(board[ff->captSqr] == 0) { // e.p. capture can discover check as well + vec = king - ff->captSqr; + match = captCode[vec] & pinCodes; + if(match) { // from-square is aligned + int x = king, v = deltaVec[vec]; + while(board[x-=v] == 0) {} // scan ray + if(f.checker != x && !(board[x] & stm) && captCode[king-x] & pieceCode[board[x]]) { // discovered check + if(f.checker != CK_NONE) f.checker = CK_DOUBLE; + else f.checker = x, f.checkDir = v, f.checkDist = dist[x-king]; + } + } + } + } + } + if(f.checker != CK_NONE) depth++, maxDepth++, reduction = 0; // extend check evasions + else if(depth > 3) { + if(depth - reduction < 3) reduction = depth - 3; // never reduce to below 3 ply + depth -= reduction; + } else reduction = 0; +if(PATH)printf("%d:%d {%d,%d} max=%d eval=%d check=%02x,%d,%d\n",ply,depth,alpha,beta,maxDepth,curEval,f.checker,f.checkDir,f.checkDist); + // stand pat or null move + startAlpha = alpha; startScore = -INF; + if(depth <= 0) { // QS + if(curEval > alpha) { + if(curEval >= beta) { ff->depth = 1; moveSP = oldSP; return curEval; } // stand-pat cutoff + alpha = startScore = curEval; maxDepth = 0; // we will not fail low, so no extra iterations + } + if(maxDepth <= 0 && board[toDecode[hashMove&255]] == 0) hashMove = 0; + } else if(curEval >= beta && f.checker == CK_NONE) { + int nullDepth = (depth > 3 ? depth - 3 : 0); + f.mutation = -1; // kludge to suppress testing for discovered check + f.newEval = f.pstEval; + f.newKey = f.hashKey; + deprec[ply] = maxDepth << 16 | depth << 8; path[ply++] = 0; + score = -Search(stm, -alpha-1, -alpha, &f, nullDepth, 0, nullDepth); + ply--; + if(score >= beta) { ff->depth = f.depth + 2; moveSP = oldSP; return beta + 1; } + } + + // move generation + if(!earlyGen) MoveGen(stm, &m); // generate moves if we had not done so yet + if(hashMove) moveStack[--m.firstMove] = hashMove; // put hash move in front of list (duplicat!) + + do { // IID loop + int curMove, highDepth; + iterDepth++; +if(PATH)printf("%d:%d:%d new iter moveStack[%d..%d]\n",ply,depth,iterDepth,m.firstMove,moveSP); + highDepth = (iterDepth > depth ? iterDepth : depth) - 1; // reply depth for high-failing moves + alpha = startAlpha; + pvPtr = pvStart; *pvPtr++ = 0; // empty PV + bestScore = startScore; bestNr = 0; // kludge: points to 0 entry in moveStack + resultDepth = MAXPLY; + m.stage &= 3; + for(curMove=m.firstMove; m.stage<4; curMove++) { + int score; + + // sort section + if(curMove >= m.unsorted) { + if(curMove < m.nonCapts) { // captures: extract best + unsigned int i, bestNr = curMove, bestCapt = moveStack[curMove]; + for(i=curMove+1; i bestCapt) bestCapt = moveStack[bestNr=i]; // find best + moveStack[bestNr] = moveStack[curMove]; moveStack[curMove] = bestCapt; // swap it to front + m.unsorted = curMove + 1; // sorted set now includes move + } else { + if(maxDepth <= 0) { resultDepth = 0; if(bestScore < curEval) bestScore = curEval; break; } // in QS we stop after captures + switch(m.stage) { // we reached non-captures + case 0: + if(PseudoLegal(stm, killer1)) moveStack[moveSP++] = moveStack[m.late], moveStack[m.late++] = killer1; // insert killers + if(PseudoLegal(stm, killer2)) moveStack[moveSP++] = moveStack[m.late], moveStack[m.late++] = killer2; // (original goes to end) + m.drops = moveSP; + // here we can sort based on history + m.stage = 1; if(moveSP > curMove) break; + case 1: + if(f.checker != CK_NONE) { + m.stage |= 4; // when in check we stop after evasion drops + if(f.checkDist == 0) continue; // but there cannot be any for contact/double checks + EvasionDrops(stm, &f); + if(moveSP <= curMove) continue; // no avail + m.stage = 3; break; + } + CheckDrops(stm, f.xking); + m.stage = 2; if(moveSP > curMove) break; + case 2: + if(iterDepth > 1) { // quiet drops only at depth >= 2 + AllDrops(stm); + m.stage = 3; if(moveSP > curMove) break; + } + case 3: + m.stage |= 4; continue; // this value of m.stage terminates the loop over moves + } + m.unsorted = moveSP; // set to return here when done with the current list + } + } + + // make move + if(MakeMove(&f, moveStack[curMove])) { // aborts if fails to evade existing check + + // repetition checking + int index = (unsigned int)f.newKey >> 24 ^ stm << 2; // uses high byte of low (= hands-free) key + while(repKey[index] && (repKey[index] ^ (int)f.newKey) & 0x1FFFFF) index += 2; + if(repKey[index]) { // key present in table: (quasi-)repetition + int gain = (f.newEval << 21) - (repKey[index] & 0xFFE00000); + if(gain == 0) { // true repeat + score = 0; // draw score *** HERE WE SHOULD TEST FOR PERPETUALS IN SHOGI *** + } else if(gain == pawn || gain > (450<<21)) score = INF; // quasi-repeat with extra piece in hand + else if(gain == -pawn || gain < (-450<<21)) score = -INF; // or with one piece less + else goto search;// traded one hand piece for another; could still lead somewhere + } else { // not a repeat: search it + int lmr; + search: + lmr = (curMove >= m.late) + (curMove >= m.drops); + repKey[index] = (int)f.newKey & 0x1FFFFF | f.newEval << 21; // remember position + // recursion + deprec[ply] = (f.checker != CK_NONE ? f.checker : 0)<<24 | maxDepth<<16 | depth<< 8 | iterDepth; path[ply++] = moveStack[curMove]; + score = -Search(stm, -beta, -alpha, &f, iterDepth-1, lmr, highDepth); + ply--; + + repKey[index] = 0; + } + + // unmake + UnMake(&f); + + } else score = -INF, f.depth = MAXPLY; +if(PATH){ +int m=moveStack[curMove]; +printf("%d:%d:%d %2d. %08x %c%d%c%d %6d %6d %6d\n",ply,depth,iterDepth,curMove,m,(m>>8&255)%22+'a',(m>>8&255)/22+1,toDecode[m&255]%22+'a',toDecode[m&255]/22+1,f.pstEval,score,bestScore); +} + + if(abortFlag) { moveSP = oldSP; return -INF; } + + // minimaxing + if(f.depth < resultDepth) resultDepth = f.depth; + + if(score > bestScore) { + bestScore = score; bestNr = curMove; + if(score > alpha) { + int *tail; + alpha = score; + if(score >= beta) { // beta cutoff + if(curMove >= m.nonCapts && moveStack[curMove] != killers[ply][1]) + killers[ply][0] = killers[ply][1], killers[ply][1] = moveStack[curMove]; + resultDepth = f.depth; + goto cutoff; // done with this node + } + tail = pvPtr; pvPtr = pvStart; *pvPtr++ = moveStack[curMove]; // alpha < score < beta: move starts new PV + while(*pvPtr++ = *tail++); // copy PV of daughter node behind it (including 0 sentinel) + if(ply == 0) { // in root we print this PV + printf("%d %d %d %d", iterDepth, score, ReadClock(0)/10, nodeCount); + for(tail=pvStart; *tail; tail++) printf(" %s", MoveToText(*tail)); + printf("\n"); fflush(stdout); + } + } + } + } // move loop + + // stalemate correction + + // self-deepening + if(resultDepth > iterDepth) iterDepth = resultDepth; // unexpectedly deep result (from hashed daughters?) + if(reduction && iterDepth == depth) depth += reduction, reduction = 0; // no fail high, start unreduced re-search on behalf of parent +if(PATH)printf("%d:%d:%d iter end, max=%d, alpha=%d start=%d\n", ply, depth,iterDepth, maxDepth, alpha, startAlpha); + if(iterDepth >= depth && alpha > startAlpha ) break; // move is PV; nominal depth suffices + alpha = startAlpha; // reset alpha for next iteration + + // put best in front + ff->move = moveStack[bestNr]; + if(bestNr > m.firstMove) { + int bestMove = moveStack[bestNr]; + if(bestNr == m.firstMove+1) moveStack[bestNr] = moveStack[m.firstMove]; else m.firstMove--; // swap first two, or prepend duplicat + moveStack[bestNr = m.firstMove] = bestMove; + } else m.late += (m.late == bestNr); // if best already in front (or non-existing), just make sure it is not reduced + + } while(iterDepth < maxDepth && (ply || !TimeIsUp(1))); // IID loop + + cutoff: + // delayed-loss bonus + bestScore += (bestScore < curEval); + resultDepth -= (f.checker != CK_NONE); // store unextended depth + + // hash store + if(!hit) { // replacement +// if(searchNr - entry[-3].age > 2) entry -= 3; else { // replace primary hit if stale + { + HashEntry *entry2 = entry - 3; + entry2 += (entry2[0].depth > entry2[1].depth); + entry -= (entry[0].depth > entry[-1].depth); + if(entry->depth > entry2->depth) entry = entry2; + } + } + entry->lock = hashKeyH; + entry->move = moveStack[bestNr]; // if no move was found, bestNr = 0, and moveStack[0] contains INVALID + entry->score = bestScore; + entry->depth = resultDepth; + entry->flags = (bestScore > alpha)*H_LOWER + (bestScore < beta)*H_UPPER; + entry->checker = f.checker + 11*(f.checkDist != 0); // encode distant check as off-board checker +if(PATH || f.hashKey == 0x1348708590)printf("%d:%d Hash store %16llx, d=%d, checker=%x move=%s\n",ply,depth,f.hashKey,resultDepth,entry->checker,MoveToText(entry->move)); + + // return results + moveSP = oldSP; pvPtr = pvStart; + ff->depth = resultDepth + 1 + reduction; // report valid depth as seen from parent + return bestScore; +} + +int moveNr; // part of game state; incremented by MakeMove +int gameMove[MAXMOVES]; // holds the game history +int stm = WHITE; + +// Some routines your engine should have to do the various essential things +void PonderUntilInput(int stm); // Search current position for stm, deepening forever until there is input. + +int +TimeIsUp (int mode) +{ // determine if we should stop, depending on time already used, TC mode, time left on clock and from where it is called ('mode') + int t = ReadClock(0), targetTime, panicTime; + if(timePerMove >= 0) { // fixed time per move + targetTime = panicTime = 10*timeLeft - 30; + } else if(mps) { // classical TC + int movesLeft = -(moveNr >> 1); + while(movesLeft <= 0) movesLeft += mps; + targetTime = 10*(timeLeft - 30) / (movesLeft + 2); + panicTime = 50*(timeLeft - 30) / (movesLeft + 4); + } else { // incremental TC + targetTime = 10*timeLeft / 40 + inc; + panicTime = 5*targetTime; + } + switch(mode) { + case 1: return (t > 0.6*targetTime); // for starting new root iteration + case 2: return (t > targetTime); // for searching new move in root + case 3: return (t > panicTime); // during search + } + return 0; // unreachable; added to silence warning +} + +StackFrame undoInfo; + +int +Setup (char *fen) +{ // very flaky FEN parser + static char castle[] = "QqKk-", startFEN[200]; + int pstEval, rights, stm = WHITE, i, p, sqr = 22*(nrRanks-1); // upper-left corner + ClearBoard(); + if(!fen) fen = startFEN; else strcpy(startFEN, fen); // remember start position, or use remembered one if not given + rights = 15; pstEval = 0; // no castling rights, balance score + hashKey = pawnKey = 0; // clear hash keys + while(*fen) { // parse board-field of FEN + if(*fen == ' ' || *fen == '[') break; + if(*fen == '/') sqr = 22*(sqr/22) - 22; else // skip to (start of) next rank + if(*fen <= '9') { + int n = atoi(fen); sqr += n; fen += (n > 9); // skip given number of squares (and second digit of 10 or 11) + } else { + int color, prom, n; + fen += prom = (*fen == '+'); // remember promotion prefix + p = *fen & ~32; color = *fen - p + WHITE; // convert to upper case and determine color + prom |= n = (fen[1] == '~'); fen += n; // remember promotion suffix + i = 0; while(pieces[i] && pieces[i] != p) i++; // identify piece type + if(p == 'K') i = 31; // K is not in list, and (royal) piece 31 in any variant + if(p == 'Q' && *fen == '~') i = 0; // Q~ is +P, not +Q + i |= color + 16*prom; // adjust type for color and promotion + board[sqr] = i; location[i] = sqr; // place piece + hashKey += KEY(i, sqr); // update hash key + pstEval += (color & WHITE ? 1 : -1)*(PST[i][sqr]+pieceValues[i]);// update PST eval (white POV) + pawnCount[sqr2file[sqr]] += pawnBulk[i]; // Pawn occupancy per file + sqr++; + } + fen++; + } + while(*fen == ' ') fen++; // skip white + if(*fen == '[') { // holdings + if(*++fen == '-') fen++; else + while(*fen && (p = *fen) != ']') { + int color; + p &= ~32; color = *fen++ - p + WHITE; + i = 0; while(pieces[i] && pieces[i] != p) i++; // identify piece type + i |= color; // adjust type for color + sqr = handSlot[i ^ COLOR]; // determine counter location + board[sqr]--; // count piece in hand + hashKey += handKey[i ^ COLOR]; // update hash key + pstEval += (color & WHITE ? 1 : -1)*(handVal[i] - pieceValues[i]); // update PST eval (white POV) + } + fen++; // skip closing bracket + } + if(*++fen == 'b') stm = BLACK, pstEval *= -1; // black to move; flip eval + while(*++fen == ' '); + while(*fen) { + i=0; while(castle[i] && castle[i] != *fen) i++; + if(i > 3) break; + rights &= ~(1<> 8 & 0xFF, to = move & 0xFF; + int inc = promoInc[to]; + to = toDecode[to]; + if(inc > 0) promo = pieceID[inc - 16]; // move is promotion + else if(inc < 0) to = 2*to - from; // move is castling, and 'to' indicates Rook; calculate King to-square + if(promo == '+' && promoCode[WHITE] & Z_FIDE) promo = 'q'; + if(from%22 > 10) sprintf(buf, "%c@%c%d", pieces[dropType[from]-1&~COLOR], 'a'+(to%22), 1+(to/22)); // move is drop + else sprintf(buf, "%c%d%c%d%c", 'a'+(from%22), 1+(from/22), 'a'+(to%22), 1+(to/22), promo); + return buf; +} + +int +ParseMove(int stm, char *s) +{ + char prom = 0, f, f2, piece; + int m, r, r2, i, from; + if(sscanf(s, "%c@%c%d", &piece, &f, &r) == 3) { // drop + f -= 'a', r--; + m = 22*r + f; + for(i=0; pieces[i]; i++) if(pieces[i] == piece) break; + m += handSlot[COLOR - stm + i] << 8; + } else { + sscanf(s, "%c%d%c%d%c", &f2, &r2, &f, &r, &prom); + f -= 'a'; f2 -= 'a'; r--, r2--; + m = 22*r + f + ((from = 22*r2 + f2) << 8); + if(prom == '\n') prom = 0; + if(prom) { // promotion suffix + int in = (stm == WHITE ? -22 : 22); // inward board step + m += 11; // move to-square off-board on same rank (good for '+' and 'q') + switch(prom) { // under-promotions must encode choice by rank (they always occur on last rank) + case 'n': m += in; break; + case 'b': m += 2*in; break; + case 'r': m += 3*in; break; + } + } else if(board[from] == stm + 31 && (f - f2 > 1 || f2 - f > 1)) { // castling + m = 22*8+10 + (f > f2 ? 0 : 11) + 22*(r2 == 7) + (from << 8); + } else if(board[from] == stm) { // Pawn moves + if(!(r - r2 & 1)) m += 5*22; // steps even nr of ranks, so must be double push + else if(f - f2 && !board[22*r+f]) m += 22*((r^1) - r + 5) + 11; // diagonal to empty: e.p. + } + } +printf("# move = %08x\n", m); + return m; +} + +// Some global variables that control your engine's behavior +int ponder; +int randomize; +int resign; // engine-defined option +int contemptFactor; // likewise + +#ifdef WIN32 +# include +# define CPUtime 1000.*clock +#else +# include +# include +# include + int GetTickCount() // with thanks to Tord + { struct timeval t; + gettimeofday(&t, NULL); + return t.tv_sec*1000 + t.tv_usec/1000; + } +#endif + +int +ReadClock (int start) +{ + static int startTime; + int t = GetTickCount(); + if(start) startTime = t; + return t - startTime; // msec +} + +int +SetMemorySize (int n) +{ + static int oldSize; + if(n == oldSize) return 0; // nothing to do + oldSize = n; // remember current size + if(hashTable) free(hashTable); // throw away old table + for(hashMask = (1<<24)-1; hashMask*sizeof(HashEntry) > n*1024*1024; hashMask >>= 1); // round down nr of buckets to power of 2 + hashTable = (HashEntry*) calloc(hashMask+4, sizeof(HashEntry)); +printf("# memory allocated\n"); + return !hashTable; // return TRUE if alocation failed +} + +int +RootMakeMove(int move) +{ + int index; + // irreversibly adopt incrementally updated values from last move as new starting point + undoInfo.pstEval = -undoInfo.newEval; // (like we initialize new Stackframe in daughter node) + undoInfo.hashKey = undoInfo.newKey; + undoInfo.rights |= spoiler[undoInfo.fromSqr] | spoiler[undoInfo.toSqr]; + undoInfo.checker = CK_NONE; + MakeMove(&undoInfo, move); + // store in game history hash table + index = (unsigned int)undoInfo.newKey >> 24 ^ stm << 2; // uses high byte of low (= hands-free) key + while(repKey[index] && (repKey[index] ^ (int)undoInfo.newKey) & 0x1FFFFF) index += 2; // find empty slot + repKey[index] = (int)undoInfo.newKey & 0x1FFFFF | undoInfo.newEval << 21; // remember position + stm ^= COLOR; + return 1; +} + +void +TakeBack (int n) +{ // reset the game and then replay it to the desired point + int last; + stm = Setup(NULL); // uses FEN saved during previous Setup + last = moveNr - n; if(last < 0) last = 0; + for(moveNr=0; moveNr 0 && stm == WHITE || score < 0 && stm == BLACK) printf("1-0\n"); + else printf("0-1\n"); +} + +int engineSide=NONE; // side played by engine +int ponderMove; +char inBuf[80]; + +void +ReadLine () +{ + int i, c; + if(inBuf[0]) return; // buffer already holds a backlogged command; + for(i = 0; (c = getchar()) != EOF && (inBuf[i++] = c) != '\n'; ); + inBuf[i] = 0; +} + +int +DoCommand (int searching) +{ + char command[80]; + + while(1) { // usually we break out of this loop after treating one command + + ReadLine(); // read one line into inBuf (or retrieve backlogged) +printf("# command: %s\n", inBuf); + if(!*inBuf) exit(0); // EOF, terminate + sscanf(inBuf, "%s", command); // extract the first word + *inBuf = 0; // and already mark the buffer as empty + + // recognize and execute 'easy' commands, i.e those that can be executed during search + if(!strcmp(command, "quit")) { exit(0); } // exit immediately + if(!strcmp(command, "otim")) { continue; } // move will follow immediately, wait for it + if(!strcmp(command, "time")) { sscanf(inBuf+4, "%d", &timeLeft); continue; } + if(!strcmp(command, "easy")) { ponder = OFF; return 0; } + if(!strcmp(command, "hard")) { ponder = ON; return 0; } + if(!strcmp(command, "post")) { postThinking = ON; return 0; } + if(!strcmp(command, "nopost")) { postThinking = OFF;return 0; } + if(!strcmp(command, "random")) { randomize = ON; return 0; } + if(!strcmp(command, ".")) { return 0; } // periodic update request; ignore for now + if(!strcmp(command, "option")) { // setting of engine-define option; find out which + if(sscanf(inBuf+7, "Resign=%d", &resign) == 1) return 0; + if(sscanf(inBuf+7, "Contempt=%d", &contemptFactor) == 1) return 0; + return 1; + } + + if(searching) { + if(!strcmp(command, "usermove")) { return 1; } // TODO during search we just test for ponder hit + *inBuf = *command; // backlog command (by repairing inBuf) + return 1; // and request search abort + } + + // the following commands can (or need) only be done when not searching + if(!strcmp(command, "force")) { engineSide = NONE; return 1; } + if(!strcmp(command, "analyze")) { engineSide = ANALYZE; return 1; } + if(!strcmp(command, "exit")) { engineSide = NONE; return 1; } + if(!strcmp(command, "level")) { + int min, sec=0; + sscanf(inBuf, "level %d %d %d", &mps, &min, &inc) == 3 || // if this does not work, it must be min:sec format + sscanf(inBuf, "level %d %d:%d %d", &mps, &min, &sec, &inc); + timeControl = 60*min + sec; timePerMove = -1; + return 1; + } + if(!strcmp(command, "protover")){ + printf("feature ping=1 setboard=1 colors=0 usermove=1 memory=1 debug=1 reuse=0 sigint=0 sigterm=0 myname=\"CrazyWa " VERSION "\"\n"); + printf("feature variants=\"crazyhouse,shogi,minishogi,judkinshogi,torishogi,euroshogi,crazywa\"\n"); + printf("feature option=\"Resign -check 0\"\n"); // example of an engine-defined option + printf("feature option=\"Contempt -spin 0 -200 200\"\n"); // and another one + printf("feature done=1\n"); + return 1; + } + if(!strcmp(command, "sd")) { sscanf(inBuf+2, "%d", &maxDepth); return 1; } + if(!strcmp(command, "st")) { sscanf(inBuf+2, "%d", &timePerMove); return 1; } + if(!strcmp(command, "memory")) { if(SetMemorySize(atoi(inBuf+7))) printf("tellusererror Not enough memory\n"), exit(-1); return 1; } + if(!strcmp(command, "ping")) { printf("pong%s", inBuf+4); return 1; } +// if(!strcmp(command, "")) { sscanf(inBuf, " %d", &); return 1; } + if(!strcmp(command, "new")) { engineSide = BLACK; stm = WHITE; maxDepth = MAXPLY; randomize = OFF; return 1; } + if(!strcmp(command, "variant")) { GameInit(inBuf + 8); Setup(startPos); return 1; } + if(!strcmp(command, "setboard")){ engineSide = NONE; stm = Setup(inBuf+9); return 1; } + if(!strcmp(command, "undo")) { TakeBack(1); return 1; } + if(!strcmp(command, "remove")) { TakeBack(2); return 1; } + if(!strcmp(command, "go")) { engineSide = stm; return 1; } + if(!strcmp(command, "hint")) { if(ponderMove != INVALID) printf("Hint: %s\n", MoveToText(ponderMove)); return 1; } + if(!strcmp(command, "book")) { return 1; } + // completely ignored commands: + if(!strcmp(command, "xboard")) { return 1; } + if(!strcmp(command, "computer")){ return 1; } + if(!strcmp(command, "name")) { return 1; } + if(!strcmp(command, "ics")) { return 1; } + if(!strcmp(command, "accepted")){ return 1; } + if(!strcmp(command, "rejected")){ return 1; } + if(!strcmp(command, "?")) { return 1; } // 'move now' + if(!strcmp(command, "p")) { Debug(); return 1; } + + if(!strcmp(command, "b")) { PrintDBoard("board:", board, " ", 11); return 1; } + if(!strcmp(command, "")) { return 1; } + if(!strcmp(command, "usermove")){ + int move = ParseMove(stm, inBuf+9); + if(move == INVALID) printf("Illegal move\n"); + else if(!RootMakeMove(move)) printf("Illegal move\n"); + else { + ponderMove = INVALID; + } + return 1; + } + printf("Error: unknown command\n"); + } + return 0; +} + +int +main () +{ + int score; + + EngineInit(); SetMemorySize(1); // reserve minimal hash to prevent crash if GUI sends no 'memory' command + GameInit("zh"); Setup(startPos); // to facilitate debugging from command line + + while(1) { // infinite loop + + fflush(stdout); // make sure everything is printed before we do something that might take time + + if(stm == engineSide) { // if it is the engine's turn to move, set it thinking, and let it move + + nodeCount = forceMove = undoInfo.move = abortFlag = 0; ReadClock(1); + score = Search(stm^COLOR, -INF, INF, &undoInfo, maxDepth, 0, maxDepth); + + if(!undoInfo.move) { // no move, game apparently ended + engineSide = NONE; // so stop playing + PrintResult(stm, score); + } else { + RootMakeMove(undoInfo.move); // perform chosen move (stores it in lastGameMove and changes stm) + printf("move %s\n", MoveToText(undoInfo.move)); // and output it + } + } + + fflush(stdout); // make sure everything is printed before we do something that might take time +#if 0 + // now it is not our turn (anymore) + if(engineSide == ANALYZE) { // in analysis, we always ponder the position + PonderUntilInput(stm); + } else + if(engineSide != NONE && ponder == ON && moveNr != 0) { // ponder while waiting for input + if(ponderMove == INVALID) { // if we have no move to ponder on, ponder the position + PonderUntilInput(stm); + } else { + int newStm = MakeMove(stm, ponderMove); + PonderUntilInput(newStm); + UnMake(ponderMove); + } + } +#endif + DoCommand(0); + } + return 0; +}