From: H.G. Muller Date: Tue, 3 Jul 2012 13:00:23 +0000 (+0200) Subject: More developed version for WinBoard Alien Edition X-Git-Tag: 0.18~103 X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=bf370f90ab8ae2a240a4afb0b961c272b8da406b;p=hachu.git More developed version for WinBoard Alien Edition This commit contains the code as it was further developed on Windows, and tested under WB-Alien. Highlighting was added, and a 3-ply fixed-depth search is invoked when it is the engines turn to move. Piece-square tables have been added to draw pieces to the center, and the move generator respects the special Lion-capture and promotion rules of Chu Shogi. Most of the time things work, but there must be a bad bug, as sometimes it suddenly moves with an opponent piece! --- diff --git a/hachu.c b/hachu.c index e1b156a..2133ce9 100644 --- a/hachu.c +++ b/hachu.c @@ -1,1626 +1,1776 @@ -/***********************************************************************/ -/* HaChu */ -/* A WinBoard engine for large (dropless) Shogi variants by H.G.Muller */ -/* The engine is based on incremental updating of an attack map and */ -/* mobility scores, since the effort in this only grows proportional */ -/* to board edge length, rather than board area. */ -/***********************************************************************/ - -// TODO: -// in GenCapts we do not generate jumps of more than two squares yet -// Chu rules for Lion capture -// promotions by pieces with Lion power stepping in & out the zone in same turn -// promotion on capture - -#define VERSION 0.0 - -#include - -#define BW 24 -#define BH 12 -#define BSIZE BW*BH*2 -#define ZONE 4 - -#define BLACK 0 -#define WHITE 1 -#define EMPTY 0 -#define EDGE (1<<11) -#define TYPE (WHITE|BLACK|EDGE) -#define ABSENT 4095 -#define INF 8000 -#define NPIECES 2000 /* length of piece list */ - -#define SQLEN 11 /* bits in square number */ -#define SQUARE ((1<1 = (limited) range -// Attack table: -// A table in board format, containing pairs of consecutive integers for each square (indexed as 2*sqr and 2*sqr+1) -// The first integer contains info on black attacks to the square, the second similarly for white attacks -// Each integer contains eight 3-bit fields, which count the number of attacks on it with moves in a particular direction -// (If there are attacks by range-jumpers, the 3-bit count is increased by 2 over the actual value) -// Board: -// The board has twice as many files as actually used, in 0x88 fashion -// The used squares hold the piece numbers (for use as index in the piece list) -// Unused squares are set to the invalid piece number EDGE -// There are also 3 guard ranks of EDGE on each side -// Moves: -// Moves are encoded as 11-bit from-square and to-square numbers packed in the low bits of an int -// Special moves (like Lion double moves) are encoded by off-board to-squares above a certain value -// Promotions are indicated by bit 22 -// Hash table: -// Entries of 16 bytes, holding a 32-bit signature, 16-bit lower- and upper-bound scores, -// 8-bit draft of each of those scores, an age counter that stores the search number of the last access. -// The hash key is derived as the XOR of the products pieceKey[piece]*squareKey[square]. -// Promotion zones -// the promoBoard contains one byte with flags for each square, to indicate for each side whether the square -// is in the promotion zone (twice), on the last two ranks, or on the last rank -// the promoFlag field in the piece list can select which bits of this are tested, to see if it -// (1) can promote (2) can defer when the to-square is on last rank, last two ranks, or anywhere. -// Pawns normally can't defer anywhere, but if the user defers with them, their promoFlag is set to promote on last rank only - -typedef struct { - int pos; - int pieceKey; - int promo; - int value; - int pst; - signed char range[8]; - char promoFlag; - char qval; - char mobility; - char mobWeight; -} PieceInfo; // piece-list entry - -int last[2], royal[2]; -PieceInfo p[NPIECES]; // piece list - -typedef struct { - int lock; - Move move; - short int upper; - short int lower; - char depthU; - char depthL; - char flags; - char age; -} HashEntry; // hash-table entry - -int squareKey[BSIZE]; - -int rawBoard[BSIZE + 11*BW + 6]; -int attacks[2*BSIZE]; // attack map -char distance[2*BSIZE]; // distance table -char promoBoard[BSIZE]; -signed char PST[2*BSIZE]; - -#define board (rawBoard + 6*BW + 3) -#define dist (distance + BSIZE) - -int LookUp(char *name, PieceDesc *list) -{ // find piece of given name in list of descriptors - int i=0; - while(list->name && strcmp(name, list->name)) i++, list++; - return (list->name == NULL ? -1 : i); -} - -void -SqueezeOut (int n) -{ // remove piece number n from the mentioned side's piece list (and adapt the reference to the displaced pieces!) - int i; - for(i=stm+2; i n) p[i].promo -= 2; - for(i=n; i= 0 && b >= 0) return a < b; - if(a >= 0) return 1; // a (limited) range can never do the same as a special move - switch(a) { - case J: return b < J; // any special move is better than a plain jump - case D: return b > 2 || b < D; - case T: return b > 3 || b < T; - case L: return b > 2 || b < D; - case F: return b > 3 || b < F || b == T; - case S: return b == H || b == T; - case H: return b < 0; - default: return 1; // a >= 0, so b must be < 0 and can always do something a ranging move cannot do - } - return 0; -} - -int -IsUpwardCompatible (signed char *r, signed char *s) -{ - int i; - for(i=0; i<8; i++) { - if(Worse(r[i], s[i])) return 0; - } - return 1; -} - -int -Forward (signed char *r) -{ - int i; - for(i=2; i<7; i++) if(r[i]) return 0; - return 1; -} - -void -Compactify (int stm) -{ // remove pieces that are permanently gone (captured or promoted) from one side's piece list - int i, j, k; - for(i=stm+2; i= 0 && p[i].pos == ABSENT) { // unpromoted piece no longer there - p[k].promo = -2; // orphan promoted version - SqueezeOut(i); - } - } - for(i=stm+2; ii; j-= 2) p[j] = p[j-2]; - p[i].value = list[n].value; - for(j=0; j<8; j++) p[i].range[j] = list[n].range[j^4*(WHITE-stm)]; - p[i].promoFlag = 0; - for(j=stm+2; j<= last[stm]; j+=2) { - if(p[j].promo >= i) p[j].promo += 2; - } - if(royal[stm] >= i) royal[stm] += 2; - if(p[i].value == 28) royal[stm] = i; - return i; -} - -void -SetUp(char *array, PieceDesc *list) -{ - int i, j, k, k2, n, m, nr, color; - char c, *q, name[3]; - last[WHITE] = 1; last[BLACK] = 0; - for(i=0; ; i++) { -printf("next rank: %s\n", array); - for(j = BW*i; ; j++) { - c = name[0] = *array++; - if(!c) goto eos; - if(c == '.') continue; - if(c == '/') break; - name[1] = name[2] = 0; - if(c == ':') name[0] = *array++, name[1] = *array++; - if(name[0] >= 'a') { - color = BLACK; - name[0] += 'A' - 'a'; - if(name[1]) name[1] += 'A' - 'a'; - } else color = WHITE; - k = LookUp(name, list); - n = AddPiece(color, k, list); - p[n].pos = j; - if(list[k].promoted[0]) { - k2 = LookUp(list[k].promoted, list); - m = AddPiece(color, k2, list); - if(m <= n) n += 2; - p[n].promo = m; - p[n].promoFlag = IsUpwardCompatible(list[k2].range, list[k].range) * DONT_DEFER + CAN_PROMOTE; - if(Forward(list[k].range)) p[n].promoFlag |= LAST_RANK; // Pieces that only move forward can't defer on last rank - if(!strcmp(list[k].name, "N")) p[n].promoFlag |= CANT_DEFER; // Knights can't defer on last 2 ranks - p[n].promoFlag &= n&1 ? P_WHITE : P_BLACK; - p[m].promo = -1; - p[m].pos = ABSENT; - } else p[n].promo = -1; // unpromotable piece -printf("piece = %c%-2s %d(%d) %d/%d\n", color ? 'w' : 'b', name, n, m, last[color], last[!color]); - } - } - eos: - for(i=0; i>10 ^ rand() << 10 ^ rand() << 20; -} - -void -Init() -{ - int i, j; - - for(i= -1; i<9; i++) { // board steps in linear coordinates - kStep[i] = STEP(direction[i&7].x, direction[i&7].y); // King - nStep[i] = STEP(direction[(i&7)+8].x, direction[(i&7)+8].y); // Knight - } - - for(i=0; i<8; i++) { // Lion double-move decoding tables - for(j=0; j<8; j++) { - epList[8*i+j] = kStep[i]; - toList[8*i+j] = kStep[j] + kStep[i]; - } - // Lion-Dog triple moves - toList[64+i] = 3*kStep[i]; epList[64+i] = kStep[i]; - toList[72+i] = 3*kStep[i]; epList[72+i] = 2*kStep[i]; - toList[80+i] = 3*kStep[i]; epList[80+i] = kStep[i]; ep2List[80+i] = 2*kStep[i]; - toList[88+i] = kStep[i]; epList[88+i] = 2*kStep[i]; - } - - // fill distance table - for(i=0; i= BH-ZONE) v |= (CAN_PROMOTE | DONT_DEFER) & P_WHITE; - if(i >= BH-2) v |= CANT_DEFER & P_WHITE; - if(i == BH-1) v |= LAST_RANK & P_WHITE; - promoBoard[BW*i + j] = v; - } -} - -#if 0 -inline int -AddMove (int i, int x, int y) -{ - if(board[y] == EDGE) return 1; - if(board[y] == EMPTY) { // non-capt - if((flagBoard[x] | flagBoard[y]) & p[i].flags) { // promotion possible - moveStack[msp++] = moveStack[nonCapts]; - moveStack[nonCapts++] |= PROMOTE | p[i].flags << 24; - if(!(p[i].flags & ALWAYS_PROMOTE)) - moveStack[msp++] = x << SQLEN | y; // push deferral as well - else moveStack[msp++] = x << SQLEN | y; // normal move - } - } else { // capture - if(((board[y] ^ i) & 1) return 1; // own - moveStack[msp++] = moveStack[nonCapts]; - moveStack[nonCapts++] = moveStack[promotions]; - moveStack[promotions++] = x << SQLEN | y; - if((flagBoard[x] | flagBoard[y]) & p[i].flags) { // promotion possible - int p = promotions; - if(!(p[i].flags & ALWAYS_PROMOTE)) { - moveStack[msp++] = moveStack[nonCapts]; - moveStack[nonCapts++] = moveStack[promotions]; - moveStack[promotions++] = moveStack[p-1]; - } - moveStack[p-1] += PROMOTE | p[i].flags<<24; - } - return 1; // capture ends ray scan - } - return 0; -} -#endif - -inline int -NewNonCapture (int x, int y, int promoFlags) -{ - if(board[y] != EMPTY) return 1; // edge, capture or own piece - if( (promoBoard[x] | promoBoard[y]) & promoFlags) { // piece can promote with this move - moveStack[msp++] = moveStack[nonCapts]; // create space for promotion - moveStack[nonCapts++] = x<= -3) { // in any case, do a jump of 2 - if(board[x + 2*v] == EMPTY) - ADDMOVE(x, x+2*v); - if(r < -1) { // Lion power, also single step - if(board[x + v] == EMPTY) - ADDMOVE(x, x+v); - if(r == -3) { // true Lion, also Knight jump - v = nStep[j]; - if(board[x + v] == EMPTY) - ADDMOVE(x, x+v); - } - } - } - continue; - } - y = x; - while(r-- > 0) { - if(board[y+=v] == GUARD) break; // off board - if((board[y] + i & 1) == 0) break; // same color - } - } -} -#endif - -void -GenNonCapts (int promoSuppress) -{ - int i, j; - for(i=stm+2; i<=last[stm]; i+=2) { - int x = p[i].pos, pFlag = p[i].promoFlag; - if(x == ABSENT) continue; - if(x == promoSuppress) pFlag = 0; - for(j=0; j<8; j++) { - int y, v = kStep[j], r = p[i].range[j]; - if(r < 0) { // jumping piece, special treatment - if(r >= S) { // in any case, do a jump of 2 - NewNonCapture(x, x + 2*v, pFlag); - if(r < N) { // Lion power, also single step - NewNonCapture(x, x + v, pFlag); - if(r == L) { // true Lion, also Knight jump - v = nStep[j]; - NewNonCapture(x, x + v, pFlag); - } - } - } - continue; - } - y = x; - while(r-- > 0) - if(NewNonCapture(x, y+=v, pFlag)) break; - } - } -} - -void -report (int x, int y, int i) -{ -} - -void -MapOneColor (int start, int last, int *map) -{ - int i, j, k; - for(i=start+2; i<=last; i+=2) { - if(p[i].pos == ABSENT) continue; - for(j=0; j<8; j++) { - int x = p[i].pos, v = kStep[j], r = p[i].range[j]; - if(r < 0) { // jumping piece, special treatment - if(r >= S) { // in any case, do a jump of 2 - if(board[x + 2*v] != EMPTY && board[x + 2*v] != EDGE) - map[2*(x + 2*v) + start] += one[j]; - if(r < J) { // Lion power, also single step - if(board[x + v] != EMPTY && board[x + v] != EDGE) - map[2*(x + v) + start] += one[j]; - if(r == T) { // Lion Dog, also jump of 3 - if(board[x + 3*v] != EMPTY && board[x + 3*v] != EDGE) - map[2*(x + 3*v) + start] += one[j]; - } else - if(r <= L) { // true Lion, also Knight jump - if(r < L) { // Lion plus (limited) range - int y = x, n = 0; - r = (r == S ? 36 : 3); - while(n++ <= r) { - if(board[y+=v] == EDGE) break; - if(board[y] != EMPTY) { - if(n > 2) map[2*y + start] += one[j]; // outside Lion range - break; - } - } - } - v = nStep[j]; - if(board[x + v] != EMPTY && board[x + v] != EDGE) - map[2*(x + v) + start] += one[8]; - } - } - } - continue; - } - while(r-- > 0) { - if(board[x+=v] != EMPTY) { - if(board[x] != EDGE) map[2*x + start] += one[j]; - break; - } - } - } - } -} - -void -MapFromScratch (int *map) -{ - int i; - for(i=0; i<2*BSIZE; i++) map[i] = 0; - MapOneColor(0, last[BLACK], map); - MapOneColor(1, last[WHITE], map); -} - -void -Connect (int sqr, int piece, int dir) -{ - int x, step = kStep[dir], r1 = p[piece].range[dir], r2 = p[piece].range[dir+1], piece1, piece2; - int d1, d2, r, y, c; - - if((attacks[2*sqr] + attacks[2*sqr+1]) & attackMask[dir]) { // there are incoming attack(s) from 'behind' - x = sqr; - while(board[x-=step] == EMPTY); // in any case, scan to attacker, to see where / what it is - d1 = dist[x-sqr]; piece1 = board[x]; - attacks[2*x + stm] -= -(d1 <= r2) & one[dir+4]; // remove our attack on it if in-range - if((attacks[2*sqr] + attacks[2*sqr+1]) & attackMask[dir+4]) { // there are also incoming attack(s) from 'ahead' - - y = sqr; - while(board[y+=step] == EMPTY); // also always scan to that one to see what it is - d2 = dist[y-sqr]; piece2 = board[y]; - attacks[2*y+stm] -= -(d2 <= r1) & one[dir]; // remove our attack on it if in-range - // we have two pieces now shooting at each other. See how far they get. - if(d1 + d2 <= (r1 = p[piece1].range[dir])) { // 1 hits 2 - attacks[2*y + (piece1 & WHITE)] += one[dir]; // count attack - UPDATE_MOBILITY(piece1, d2); - } else UPDATE_MOBILITY(piece1, r1 - d1); // does not connect, but could still gain mobility - if(d1 + d2 <= (r2 = p[piece2].range[dir])) { // 2 hits 1 - attacks[2*x + (piece2 & WHITE)] += one[dir+4]; // count attack - UPDATE_MOBILITY(piece2, d1); - } else UPDATE_MOBILITY(piece2, r2 - d2); // does not connect, but could still gain mobility - - } else { // we were only attacked from behind - - r = (r2 = p[piece1].range[dir]) - d1; - if(r < 0 || c > one[dir+1]) { // Oops! This was not our attacker, or not the only one. There must be a jump attack from even further behind! - // for now, forget jumpers - } - y = sqr; - while(r--) - if(board[y+=step] != EMPTY) { - d2 = dist[y-sqr]; piece2 = board[y]; - if(piece2 != EDGE) { // extended move hits a piece - attacks[2*y + (piece1 & WHITE)] += one[dir]; // count attack - attacks[2*y + stm] -= -(d2 <= r1) & one[dir]; // remove our own attack on it, if in-range - } - UPDATE_MOBILITY(piece1, d2); // count extra mobility even if we hit edge - return; - } - // we hit nothing with the extended move of the attacker behind us. - UPDATE_MOBILITY(piece1, r2 - d1); - r = r1 - r2; // extra squares covered by mover - while(r-- > 0) - if(board[y+=step] != EMPTY) { - d2 = dist[y-sqr]; piece2 = board[y]; - if(piece2 != EDGE) { // extended move hits a piece - attacks[2*y + stm] -= one[dir]; // count attack - } - return; - } - } - - } else // no incoming attack from behind - if(c = (attacks[2*sqr] + attacks[2*sqr+1]) & attackMask[dir+4]) { // but incoming attack(s) from 'ahead' - - y = sqr; while(board[y+=step]); // locate attacker - d2 = dist[y-sqr]; piece2 = board[y]; - attacks[2*y + stm] -= -(d2 <= r1) & one[dir]; // remove our attack on it if in-range - r = (r1 = p[piece1].range[dir]) - d2; - if(r < 0 || c > one[dir]) { // Oops! This was not our attacker, or not the only one. There must be a jump attack from even further behind! - // for now, forget jumpers - } - x = sqr; - while(r--) - if(board[x-=step] != EMPTY) { - d1 = dist[x-sqr]; piece1 = board[x]; - if(piece1 != EDGE) { // extended move hits a piece - attacks[2*x + (piece2 & WHITE)] += one[dir+4]; // count attack - attacks[2*x + stm] -= -(d1 <= r2) & one[dir+4]; // remove our own attack on it, if in-range - } - UPDATE_MOBILITY(piece2, d1); // count extra mobility even if we hit edge - return; - } - // we hit nothing with the extended move of the attacker behind us. - UPDATE_MOBILITY(piece2, r2 - d1); - r = r2 - r1; // extra squares covered by mover - while(r-- > 0) - if(board[x-=step] != EMPTY) { - d1 = dist[x-sqr]; piece1 = board[x]; - if(piece1 != EDGE) { // extended move hits a piece - attacks[2*x + stm] -= one[dir+4]; // count attack - } - return; - } - - } else { // no incoming attacks from either side. Only delete attacks of mover on others - - x = sqr; - while(r1--) - if(board[x+=step] != EMPTY) { // piece found that we attacked - attacks[2*x + stm] -= one[dir]; // decrement attacks along that direction - break; - } - - x = sqr; - while(r2--) - if(board[x-=step] != EMPTY) { // piece found that we attacked - attacks[2*x + stm] -= one[dir+4]; // decrement attacks along opposite direction - break; - } - - } -} - -void -Disconnect (int sqr, int dir) -{ - int x = sqr, step = kStep[dir], piece1, piece2, y; - while( board[x+=step] == EMPTY ); - if(board[x] != EDGE) { // x has hit a piece - piece1 = board[x]; - y = sqr; while( board[y-=step] == EMPTY ); - if(board[y] != EDGE) { // both ends of the ray hit a piece - piece2 = board[y]; - - return; - } - } else { - x = sqr; while( board[x-=step] == EMPTY ); - if(board[x] == EDGE) return; // ray empty on both sides - } - // we only get here if one side hits a - -} - -void -Occupy (int sqr) -{ // determines attacks on square and blocking when a piece lands on an empty square - int i; - for(i=0; i<4; i++) { - Disconnect(sqr, i); - } -} - -void -Evacuate (int sqr, int piece) -{ // determines change in attacks on neighbors due to unblocking and mover when the mentioned piece vacates the given square - int i; - for(i=0; i<4; i++) Connect(sqr, piece, i); -} - -int -MakeMove(Move m, UndoInfo *u) -{ - int deferred = ABSENT; - // first execute move on board - u->from = m>>SQLEN & SQUARE; - u->to = m & SQUARE; - u->piece = board[u->from]; - board[u->from] = EMPTY; - - if(m & (PROMOTE | DEFER)) { - if(m & DEFER) { - deferred = u->to; - u->new = u->piece; - } else { - p[u->piece].pos = ABSENT; - u->new = p[u->piece].promo; - } - } else u->new = u->piece; - - u->booty = PST[p[u->new].pst + u->to] - PST[p[u->piece].pst + u->from]; - - if(u->to > SPECIAL) { // two-step Lion move - // take care of first e.p. victim - u->epSquare = u->from + epList[u->to - SPECIAL]; // decode - u->epVictim = board[u->epSquare]; // remember for takeback - board[u->epSquare] = EMPTY; // and remove - p[u->epVictim].pos = ABSENT; - // now take care of (optional) second e.p. victim - u->ep2Square = u->from + ep2List[u->to - SPECIAL]; // This is the (already evacuated) from-square when there is none! - u->ep2Victim = board[u->ep2Square]; // remember - board[u->ep2Square] = EMPTY; // and remove - p[u->ep2Victim].pos = ABSENT; - // decode the true to-square, and correct difEval and hash key for the e.p. captures - u->to = u->from + toList[u->to - SPECIAL]; - u->booty += PST[p[u->epVictim].pst + u->epSquare] + PST[p[u->ep2Victim].pst + u->ep2Square]; - hashKeyL ^= p[u->epVictim].pieceKey * squareKey[u->epSquare]; - hashKeyH ^= p[u->epVictim].pieceKey * squareKey[u->epSquare+BW]; - hashKeyL ^= p[u->ep2Victim].pieceKey * squareKey[u->ep2Square]; - hashKeyH ^= p[u->ep2Victim].pieceKey * squareKey[u->ep2Square+BW]; - } else u->epVictim = EMPTY; - - u->victim = board[u->to]; - p[u->victim].pos = ABSENT; - u->booty += PST[p[u->victim].pst + u->to]; - - p[u->new].pos = u->to; - board[u->to] = u->new; - - hashKeyL ^= p[u->new].pieceKey * squareKey[u->to] - ^ p[u->piece].pieceKey * squareKey[u->from] - ^ p[u->victim].pieceKey * squareKey[u->to]; - hashKeyH ^= p[u->new].pieceKey * squareKey[u->to+BW] - ^ p[u->piece].pieceKey * squareKey[u->from+BW] - ^ p[u->victim].pieceKey * squareKey[u->to+BW]; - return deferred; -} - -void -UnMake(UndoInfo *u) -{ - if(u->epVictim) { // put Lion victim of first leg back - p[u->ep2Victim].pos = u->ep2Square; - board[u->ep2Square] = u->ep2Victim; - p[u->epVictim].pos = u->epSquare; - board[u->epSquare] = u->epVictim; - } - - p[u->victim].pos = u->to; - board[u->to] = u->victim; // can be EMPTY - - p[u->new].pos = ABSENT; - p[u->piece].pos = u->from; // this can be the same as above - board[u->from] = u->piece; -} - -void -GenCapts(int sqr, int victimValue) -{ // generate all moves that capture the piece on the given square - int i, range, att = attacks[2*sqr + stm]; -printf("GenCapts(%d,%d)\n",sqr,victimValue); - for(i=0; i<8; i++) { // try all rays - int x, v, jumper; - if(att & attackMask[i]) { // attacked by move in this direction - v = -kStep[i]; x = sqr; - while( board[x+=v] == EMPTY ); // scan towards source until we encounter a 'stop' -printf("stop @ %c%d (dir %d)\n",x%BW+'a',x/BW,i); - if((board[x] & TYPE) == stm) { // stop is ours - int attacker = board[x], d = dist[x-sqr]; -printf("attacker %d, range %d, dist %d\n", attacker, p[attacker].range[i], d); - if(p[attacker].range[i] >= d) { // it has a plain move in our direction that hits us - NewCapture(x, sqr + victimValue - SORTKEY(attacker), p[attacker].promoFlag); - } else - if(p[attacker].range[i] < 0) { // stop has non-standard moves - switch(p[attacker].range[i]) { // figure out what he can do (including multi-captures, which causes the complexity) - case L: // Lion - if(d > 2) break; - case F: // Lion power + 3-step (as in FF) - if(d > 3) break; - case S: // Lion power + ranging (as in BS) - NewCapture(x, sqr + victimValue - SORTKEY(attacker), p[attacker].promoFlag); - // now the multi-captures of designated victim together with lower-valued piece - if(d == 2) { // primary victim on second ring; look for victims to take in passing - if((board[sqr+v] & TYPE) == xstm && board[sqr+v] > board[sqr]) - NewCapture(x, SPECIAL + 9*i + victimValue - SORTKEY(attacker), p[attacker].promoFlag); - if((i&1) == 0) { // orthogonal: two extra bent paths - v = kStep[i-1]; - if((board[x+v] & TYPE) == xstm && board[x+v] > board[sqr]) - NewCapture(x, SPECIAL + 8*(i-1&7) + (i+1&7) + victimValue - SORTKEY(attacker), p[attacker].promoFlag); - v = kStep[i+1]; - if((board[x+v] & TYPE) == xstm && board[x+v] > board[sqr]) - NewCapture(x, SPECIAL + 8*(i+1&7) + (i-1&7) + victimValue - SORTKEY(attacker), p[attacker].promoFlag); - } - } else { // primary victim on first ring - int j; - for(j=0; j<8; j++) { // we can go on in 8 directions after we captured it in passing - int v = kStep[j]; - if(sqr + v == x || board[sqr+v] == EMPTY) // hit & run; make sure we include igui (attacker is still at x!) - NewCapture(x, SPECIAL + 8*i + j + victimValue, p[attacker].promoFlag); - else if((board[sqr+v] & TYPE) == xstm && board[sqr+v] > board[sqr]) { // double capture - NewCapture(x, SPECIAL + 8*i + j + victimValue, p[attacker].promoFlag); // other victim after primary - if(dist[sqr+v-x] == 1) // other victim also on first ring; reverse order is possible - NewCapture(x, SPECIAL + 8*j + i + victimValue, p[attacker].promoFlag); - } - } - } - break; - case D: // linear Lion move (as in HF, SE) - if(d > 2) break; - NewCapture(x, sqr + victimValue - SORTKEY(attacker), p[attacker].promoFlag); - if(d == 2) { // heck if we can take intermediate with it - if((board[x+v] & TYPE) == xstm && board[x+v] > board[sqr]) - NewCapture(x, SPECIAL + 9*i + victimValue - SORTKEY(attacker), p[attacker].promoFlag); // e.p. - } else { // d=1; can move on to second, or move back for igui - NewCapture(x, SPECIAL + 8*i + (i^4) + victimValue, p[attacker].promoFlag); // igui - if(board[sqr+v] == EMPTY || (board[sqr+v] & TYPE) == xstm && board[sqr+v] > board[sqr]) - NewCapture(x, SPECIAL + 9*i + victimValue - SORTKEY(attacker), p[attacker].promoFlag); // hit and run - } - break; - case J: // plain jump (as in KY, PH) - if(d != 2) break; - NewCapture(x, sqr + victimValue - SORTKEY(attacker), p[attacker].promoFlag); - } - } - att -= one[i]; - if((att & attackMask[i]) == 0) continue; // no other attackers, so done with this direction - } - // now we get to the hairy part: there are other attackers than the stop, apparently jumping over it - jumper = board[x+v]; // immediately behind stop - if((jumper & TYPE) == xstm && p[jumper].range[i] < 0) { // the piece behind the stop has a jump on us - NewCapture(x+v, sqr + victimValue - SORTKEY(jumper), p[jumper].promoFlag); - // for Chu/Dai, this can be all. With range-jumpers and Lion Dogs there could be attacks left from further upstream! - } - } - } - // off-ray attacks - if(att & attackMask[8]) { // Knight attack - for(i=0; i<8; i++) { // scan all knight jumps to locate source - int x = sqr - nStep[i], attacker = board[x]; - if((attacker & TYPE) != stm) continue; - if(p[attacker].range[i] <= N && p[attacker].range[i] >= S) { // has Knight jump in our direction - NewCapture(x, sqr + victimValue, p[attacker].promoFlag); // plain jump (as in N) - if(p[attacker].range[i] < N) { // Lion power; generate double captures over two possible intermediates - int v = kStep[i]; // leftish path - if((board[x+v] & TYPE) == xstm && board[x+v] > board[sqr]) - NewCapture(x, SPECIAL + 8*i + (i+1&7) + victimValue, p[attacker].promoFlag); - v = kStep[i+1]; // rightish path - if((board[x+v] & TYPE) == xstm && board[x+v] > board[sqr]) - NewCapture(x, SPECIAL + 8*(i+1&7) + i + victimValue, p[attacker].promoFlag); - } - } - } - } -} - -int -Evaluate () -{ - return 0; -} - -#if 1 -int Search (int alpha, int beta, int difEval, int depth, int oldPromo, int promoSuppress) -{ - int i, j, k, firstMove, oldMSP = msp, curMove, sorted, bad, phase, king, iterDep, nextVictim, to, defer, dubious, bestMoveNr; - int savHashL = hashKeyL, savHashH = hashKeyH; - int score, bestScore, curEval; - Move move; - UndoInfo tb; -printf("search\n");fflush(stdout); - xstm = stm ^ WHITE; - MapFromScratch(attacks); // for as long as incremental update does not work. -printf("map made\n");fflush(stdout); - // KING CAPTURE - k = p[king=royal[xstm]].pos; - if( k != ABSENT) { - if(attacks[2*k + stm]) { - if( p[king + 2].pos == ABSENT ) return INF; // we have an attack on his only King - } - } else { // he has no king! Test for attacks on Crown Prince - k = p[king + 2].pos; - if(attacks[2*k + stm]) return INF; // we have attack on Crown Prince - } -printf("King safe\n");fflush(stdout); - // EVALUATION & WINDOW SHIFT - curEval = difEval + Evaluate(); - alpha -= (alpha < curEval); - beta -= (beta <= curEval); - - - firstMove = curMove = sorted = nonCapts = msp += 20; // leave 20 empty slots in front of move list - phase = 0; - for(iterDep=1; iterDep<= depth; iterDep++) { -printf("iter %d\n", iterDep);fflush(stdout); - bestScore = -INF; - for(curMove = firstMove; ; curMove++) { // loop over moves -printf("phase=%d: first/curr/last = %d / %d / %d\n", phase, firstMove, curMove, msp);fflush(stdout); - // MOVE SOURCE - if(curMove >= msp) { // we ran out of moves; generate some new - switch(phase) { - case 0: // null move -#if 0 - if(depth <= 0) { - bestScore = curEval; - - } - if(curEval >= beta) { - stm ^= WHITE; - score = -Search(-beta, -alpha, difEval, depth-3, ABSENT, ABSENT); - stm ^= WHITE; - if(score >= beta) { msp = oldMSP; return score + (score < curEval); } - } -#endif - phase = 1; - case 1: // hash move - phase = 2; - case 2: // capture-gen init - nextVictim = xstm; - phase = 3; - case 3: // generate captures - while(nextVictim < last[xstm]) { // more victims exist - int group, to = p[nextVictim += 2].pos; // take next - if(to == ABSENT) continue; // ignore if absent - if(!attacks[2*to + stm]) continue; // skip if not attacked - group = p[nextVictim].qval; // remember value of this found victime - GenCapts(to, group<<28); - while(nextVictim < last[xstm] && p[nextVictim+2].qval == group) { // more victims of same value exist - to = p[nextVictim += 2].pos; // take next - if(to == ABSENT) continue; // ignore if absent - if(!attacks[2*to + stm]) continue; // skip if not attacked - GenCapts(to, group<<28); - } -printf("captures on %d generated, msp=%d\n", nextVictim, msp); - goto extractMove; - } - phase = 4; // out of victims: all captures generated - case 4: // dubious captures -#if 0 - while( dubious < framePtr + 250 ) // add dubious captures back to move stack - moveStack[msp++] = moveStack[dubious++]; - if(curMove != msp) break; -#endif - phase = 5; - case 5: // killers - phase = 6; - case 6: // non-captures - GenNonCapts(oldPromo); - phase = 7; - sorted = msp; // do not sort noncapts - break; - case 7: // bad captures - case 8: - case 9: - goto cutoff; - } - } - - // MOVE EXTRACTION - extractMove: - if(curMove < sorted) { - move = moveStack[sorted=j=curMove]; - for(i=curMove+1; i move) move = moveStack[j=i]; // search move with highest priority - moveStack[j] = moveStack[curMove]; moveStack[curMove] = move; // swap highest-priority move to front of remaining - if(move == 0) { msp = curMove--; continue; } // all remaining moves must be 0; clip off move list - } else { - move = moveStack[curMove]; - if(move == 0) continue; // skip invalidated move - } -#if 0 - // RECURSION - stm ^= WHITE; - defer = MakeMove(moveStack[curMove], &tb); - score = -Search(-beta, -alpha, -difEval - tb.booty, depth-1, promoSuppress, defer); - UnMake(&tb); - hashKeyL = savHashL; - hashKeyH = savHashH; - stm ^= WHITE; -#else -score = 0; -#endif -#if 0 - // ALPHA-BETA STUFF - if(score > bestScore) { - bestScore = alpha; bestMoveNr = curMove; - if(score > alpha) { - alpha = score; - if(curMove < firstMove + 5) { // if not too much work, sort move to front - int i; - for(i=curMove; i>firstMove; i--) { - moveStack[i] = moveStack[i-1]; - } - moveStack[firstMove] = move; - } else { // late move appended in front of list, and leaves hole in list - moveStack[--firstMove] = move; - moveStack[curMove] = 0; - } - bestMoveNr = firstMove; - if(score >= beta) { // beta cutoff - // update killer - goto cutoff; - } - } - - } -#endif - } // next move - cutoff: - ; - } // next depth - retMSP = msp; - msp = oldMSP; - return bestScore + (bestScore < curEval); -} -#endif - -void -pplist() -{ - int i, j; - for(i=0; i<182; i++) { - printf("%3d. %3d %3d %4d %02x ", i, p[i].value, p[i].promo, p[i].pos, p[i].promoFlag&255); - for(j=0; j<8; j++) printf(" %2d", p[i].range[j]); - printf("\n"); - } - printf("last: %d / %d\nroyal %d / %d\n", last[WHITE], last[BLACK], royal[WHITE], royal[BLACK]); -} - -void -pboard (int *b) -{ - int i, j; - for(i=BH+2; i>-4; i--) { - for(j=-3; j=0; i--) { - for(j=0; j=0; i--) { - for(j=0; j>SQLEN & SQUARE; - t = m & SQUARE; - printf("%3d. %08x %3d-%3d %c%d%c%d%s\n", i, m, f, t, f%BW+'a', f/BW+1, t%BW+'a', t/BW+1, m & PROMOTE ? "+" : ""); - } -} - - /********************************************************/ - /* Example of a WinBoard-protocol driver, by H.G.Muller */ - /********************************************************/ - - #include - - // four different constants, with values for WHITE and BLACK that suit your engine - #define NONE 3 - #define ANALYZE 4 - - // some value that cannot occur as a valid move - #define INVALID 0 - - // some parameter of your engine - #define MAXMOVES 500 /* maximum game length */ - #define MAXPLY 60 /* maximum search depth */ - - #define OFF 0 - #define ON 1 - -#define ONE 0 -#define DEFAULT_FEN "" - -typedef Move MOVE; - - int moveNr; // part of game state; incremented by MakeMove - MOVE gameMove[MAXMOVES]; // holds the game history - - // Some routines your engine should have to do the various essential things - int MakeMove2(int stm, MOVE move); // performs move, and returns new side to move - void UnMake2(MOVE move); // unmakes the move; - int Setup2(char *fen); // sets up the position from the given FEN, and returns the new side to move - void SetMemorySize(int n); // if n is different from last time, resize all tables to make memory usage below n MB - char *MoveToText(MOVE move); // converts the move from your internal format to text like e2e2, e1g1, a7a8q. - MOVE ParseMove(char *moveText); // converts a long-algebraic text move to your internal move format - int SearchBestMove(int stm, int timeLeft, int mps, int timeControl, int inc, int timePerMove, MOVE *move, MOVE *ponderMove); - void PonderUntilInput(int stm); // Search current position for stm, deepening forever until there is input. - -UndoInfo undoInfo; -int sup0, sup1, sup2; // promo suppression squares - -int -MakeMove2 (int stm, MOVE move) -{ - sup0 = sup1; sup1 = sup2; - sup2 = MakeMove(move, &undoInfo); - return stm ^ WHITE; -} - -void -UnMake2 (MOVE move) -{ - UnMake(&undoInfo); - sup2 = sup1; sup1 = sup0; -} - -int -Setup2 (char *fen) -{ - SetUp(chuArray, chuPieces); - return WHITE; -} - -void -SetMemorySize (int n) -{ -} - -char * -MoveToText (MOVE move) -{ - static char buf[50]; - int f = move>>SQLEN & SQUARE, g = f, t = move & SQUARE; - if(t >= SPECIAL) { // kludgy! Print as side effect non-standard WB command to remove victims from double-capture (breaks hint command!) - int e = f + epList[t - SPECIAL]; -// printf("take %c%d\n", e%BW+'a', e/BW+ONE); - printf("move %c%d%c%d,\n", f%BW+'a', f/BW+ONE, e%BW+'a', e/BW+ONE); f = e; - if(ep2List[t - SPECIAL]) { - e = g + ep2List[t - SPECIAL]; -// printf("take %c%d\n", e%BW+'a', e/BW+ONE); - printf("move %c%d%c%d,\n", f%BW+'a', f/BW+ONE, e%BW+'a', e/BW+ONE); f = e; - } - t = g + toList[t - SPECIAL]; - } - sprintf(buf, "%c%d%c%d%s", f%BW+'a', f/BW+ONE, t%BW+'a', t/BW+ONE, move & PROMOTE ? "+" : ""); - return buf; -} - -int -ReadSquare (char *p, int *sqr) -{ - int i=0, f, r; - f = p[0] - 'a'; - r = atoi(p + 1) - ONE; - *sqr = r*BW + f; - return 2 + (r > 9); -} - -MOVE -ParseMove (char *moveText) -{ - int i, j, f, t, t2, e, ret, deferred=0; - moveText += ReadSquare(moveText, &f); - moveText += ReadSquare(moveText, &t); t2 = t; - if(*moveText == ',') { - moveText++; - moveText += ReadSquare(moveText, &e); - if(e != t) return INVALID; // must continue with same piece - moveText += ReadSquare(moveText, &e); - for(i=0; i<8; i++) if(f + kStep[i] == e) break; - if(i >= 8) return INVALID; // this rejects Lion Dog 2+1 and 2-1 moves! - for(j=0; j<8; j++) if(e + kStep[j] == t) break; - if(j >= 8) return INVALID; // this rejects Lion Dog 1+2 moves! - t2 = SPECIAL + 8*i + j; - } - ret = f<=retMSP) { // no exact match - if(deferred) { // but maybe non-sensical deferral - int flags = p[board[f]].promoFlag; - i = deferred; // in any case we take that move - if(!(flags & promoBoard[t] & (CANT_DEFER | LAST_RANK))) { // but change it into a deferral if that is allowed - moveStack[i] &= ~PROMOTE; - if(p[board[f]].value == 4) p[board[f]].promoFlag &= LAST_RANK; else - if(!(flags & promoBoard[t])) moveStack[i] |= DEFER; // came from outside zone, so essential deferral - } - } - if(i >= retMSP) - for(i=20; i= retMSP ? INVALID : moveStack[i]); -} - -int -SearchBestMove (int stm, int timeLeft, int mps, int timeControl, int inc, int timePerMove, MOVE *move, MOVE *ponderMove) -{ -} - -void -PonderUntilInput (int stm) -{ -} - - // Some global variables that control your engine's behavior - int ponder; - int randomize; - int postThinking; - int resign; // engine-defined option - int contemptFactor; // likewise - - int TakeBack(int n) - { // reset the game and then replay it to the desired point - int last, stm; - stm = Setup2(NULL); - 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"); - } - - main() - { - int engineSide=NONE; // side played by engine - int timeLeft; // timeleft on engine's clock - int mps, timeControl, inc, timePerMove; // time-control parameters, to be used by Search - int maxDepth; // used by search - MOVE move, ponderMove; - int i, score; - char inBuf[80], command[80]; - - Init(); - SetUp(chuArray, chuPieces); -// pplist(); -// pboard(board); -// pbytes(promoBoard); -// Search(-INF, INF, 0, 1, ABSENT, ABSENT); -// pmoves(20, retMSP); -//MapFromScratch(attacks); -// MapOneColor(1, last[WHITE], attacks); - - 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 - - score = SearchBestMove(stm, timeLeft, mps, timeControl, inc, timePerMove, &move, &ponderMove); - - if(move == INVALID) { // game apparently ended - engineSide = NONE; // so stop playing - PrintResult(stm, score); - } else { - stm = MakeMove2(stm, move); // assumes MakeMove returns new side to move - gameMove[moveNr++] = move; // remember game - printf("move %s\n", MoveToText(move)); - } - } - - fflush(stdout); // make sure everything is printed before we do something that might take time - - // 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 = MakeMove2(stm, ponderMove); - PonderUntilInput(newStm); - UnMake2(ponderMove); - } - } - - noPonder: - // wait for input, and read it until we have collected a complete line - for(i = 0; (inBuf[i] = getchar()) != '\n'; i++); - inBuf[i+1] = 0; - - // extract the first word - sscanf(inBuf, "%s", command); - - // recognize the command,and execute it - if(!strcmp(command, "quit")) { break; } // breaks out of infinite loop - if(!strcmp(command, "force")) { engineSide = NONE; continue; } - if(!strcmp(command, "analyze")) { engineSide = ANALYZE; continue; } - if(!strcmp(command, "exit")) { engineSide = NONE; continue; } - if(!strcmp(command, "otim")) { goto noPonder; } // do not start pondering after receiving time commands, as move will follow immediately - if(!strcmp(command, "time")) { sscanf(inBuf, "time %d", &timeLeft); goto noPonder; } - 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; - continue; - } - if(!strcmp(command, "protover")){ - printf("feature ping=1 setboard=1 colors=0 usermove=1 memory=1 debug=1\n"); - printf("feature variants=\"chu,12x12+0_fairy\"\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"); - continue; - } - if(!strcmp(command, "option")) { // setting of engine-define option; find out which - if(sscanf(inBuf+7, "Resign=%d", &resign) == 1) continue; - if(sscanf(inBuf+7, "Contempt=%d", &contemptFactor) == 1) continue; - continue; - } - if(!strcmp(command, "sd")) { sscanf(inBuf, "sd %d", &maxDepth); continue; } - if(!strcmp(command, "st")) { sscanf(inBuf, "st %d", &timePerMove); continue; } - if(!strcmp(command, "memory")) { SetMemorySize(atoi(inBuf+7)); continue; } - if(!strcmp(command, "ping")) { printf("pong%s", inBuf+4); continue; } - // if(!strcmp(command, "")) { sscanf(inBuf, " %d", &); continue; } - if(!strcmp(command, "new")) { engineSide = BLACK; stm = Setup2(DEFAULT_FEN); maxDepth = MAXPLY; randomize = OFF; continue; } - if(!strcmp(command, "setboard")){ engineSide = NONE; stm = Setup2(inBuf+9); continue; } - if(!strcmp(command, "easy")) { ponder = OFF; continue; } - if(!strcmp(command, "hard")) { ponder = ON; continue; } - if(!strcmp(command, "undo")) { stm = TakeBack(1); continue; } - if(!strcmp(command, "remove")) { stm = TakeBack(2); continue; } - if(!strcmp(command, "go")) { engineSide = stm; continue; } - if(!strcmp(command, "post")) { postThinking = ON; continue; } - if(!strcmp(command, "nopost")) { postThinking = OFF;continue; } - if(!strcmp(command, "random")) { randomize = ON; continue; } - if(!strcmp(command, "hint")) { if(ponderMove != INVALID) printf("Hint: %s\n", MoveToText(ponderMove)); continue; } - if(!strcmp(command, "book")) { continue; } - if(!strcmp(command, "p")) { pboard(board); continue; } - if(!strcmp(command, "w")) { pmap(attacks, WHITE); continue; } - if(!strcmp(command, "b")) { pmap(attacks, BLACK); continue; } - // ignored commands: - if(!strcmp(command, "xboard")) { continue; } - if(!strcmp(command, "computer")){ continue; } - if(!strcmp(command, "name")) { continue; } - if(!strcmp(command, "ics")) { continue; } - if(!strcmp(command, "accepted")){ continue; } - if(!strcmp(command, "rejected")){ continue; } - if(!strcmp(command, "variant")) { continue; } - if(!strcmp(command, "")) { continue; } - if(!strcmp(command, "usermove")){ - int move = ParseMove(inBuf+9); - if(move == INVALID) printf("Illegal move\n"); - else { - stm = MakeMove2(stm, move); - ponderMove = INVALID; - gameMove[moveNr++] = move; // remember game - } - continue; - } - printf("Error: unknown command\n"); - } - } - +/***********************************************************************/ +/* HaChu */ +/* A WinBoard engine for large (dropless) Shogi variants by H.G.Muller */ +/* The engine is based on incremental updating of an attack map and */ +/* mobility scores, since the effort in this only grows proportional */ +/* to board edge length, rather than board area. */ +/***********************************************************************/ + +// TODO: +// in GenCapts we do not generate jumps of more than two squares yet +// Chu rules for Lion capture +// promotions by pieces with Lion power stepping in & out the zone in same turn +// promotion on capture + +#define VERSION 0.0 + +#define PATH level==0 || level==1 && path[0] == 0x55893 + +#include + +#define BW 24 +#define BH 12 +#define BSIZE BW*BH*2 +#define ZONE 4 + +#define ONE 0 + +#define BLACK 0 +#define WHITE 1 +#define EMPTY 0 +#define EDGE (1<<11) +#define TYPE (WHITE|BLACK|EDGE) +#define ABSENT 4095 +#define INF 8000 +#define NPIECES 2000 /* length of piece list */ + +#define SQLEN 11 /* bits in square number */ +#define SQUARE ((1<1 = (limited) range +// Attack table: +// A table in board format, containing pairs of consecutive integers for each square (indexed as 2*sqr and 2*sqr+1) +// The first integer contains info on black attacks to the square, the second similarly for white attacks +// Each integer contains eight 3-bit fields, which count the number of attacks on it with moves in a particular direction +// (If there are attacks by range-jumpers, the 3-bit count is increased by 2 over the actual value) +// Board: +// The board has twice as many files as actually used, in 0x88 fashion +// The used squares hold the piece numbers (for use as index in the piece list) +// Unused squares are set to the invalid piece number EDGE +// There are also 3 guard ranks of EDGE on each side +// Moves: +// Moves are encoded as 11-bit from-square and to-square numbers packed in the low bits of an int +// Special moves (like Lion double moves) are encoded by off-board to-squares above a certain value +// Promotions are indicated by bit 22 +// Hash table: +// Entries of 16 bytes, holding a 32-bit signature, 16-bit lower- and upper-bound scores, +// 8-bit draft of each of those scores, an age counter that stores the search number of the last access. +// The hash key is derived as the XOR of the products pieceKey[piece]*squareKey[square]. +// Promotion zones +// the promoBoard contains one byte with flags for each square, to indicate for each side whether the square +// is in the promotion zone (twice), on the last two ranks, or on the last rank +// the promoFlag field in the piece list can select which bits of this are tested, to see if it +// (1) can promote (2) can defer when the to-square is on last rank, last two ranks, or anywhere. +// Pawns normally can't defer anywhere, but if the user defers with them, their promoFlag is set to promote on last rank only + +typedef struct { + int pos; + int pieceKey; + int promo; + int value; + int pst; + signed char range[8]; + char promoFlag; + char qval; + char mobility; + char mobWeight; +} PieceInfo; // piece-list entry + +int last[2], royal[2]; +PieceInfo p[NPIECES]; // piece list + +typedef struct { + int lock; + Move move; + short int upper; + short int lower; + char depthU; + char depthL; + char flags; + char age; +} HashEntry; // hash-table entry + +int squareKey[BSIZE]; + +int rawBoard[BSIZE + 11*BW + 6]; +//int attacks[2*BSIZE]; // attack map +int attackMaps[200*BSIZE], *attacks = attackMaps; +char distance[2*BSIZE]; // distance table +char promoBoard[BSIZE]; +signed char PST[2*BSIZE]; + +#define board (rawBoard + 6*BW + 3) +#define dist (distance + BSIZE) + +int LookUp(char *name, PieceDesc *list) +{ // find piece of given name in list of descriptors + int i=0; + while(list->name && strcmp(name, list->name)) i++, list++; + return (list->name == NULL ? -1 : i); +} + +void +SqueezeOut (int n) +{ // remove piece number n from the mentioned side's piece list (and adapt the reference to the displaced pieces!) + int i; + for(i=stm+2; i n) p[i].promo -= 2; + for(i=n; i= 0 && b >= 0) return a < b; + if(a >= 0) return 1; // a (limited) range can never do the same as a special move + switch(a) { + case J: return b < J; // any special move is better than a plain jump + case D: return b > 2 || b < D; + case T: return b > 3 || b < T; + case L: return b > 2 || b < D; + case F: return b > 3 || b < F || b == T; + case S: return b == H || b == T; + case H: return b < 0; + default: return 1; // a >= 0, so b must be < 0 and can always do something a ranging move cannot do + } + return 0; +} + +int +IsUpwardCompatible (signed char *r, signed char *s) +{ + int i; + for(i=0; i<8; i++) { + if(Worse(r[i], s[i])) return 0; + } + return 1; +} + +int +Forward (signed char *r) +{ + int i; + for(i=2; i<7; i++) if(r[i]) return 0; + return 1; +} + +int +Range (signed char *r) +{ + int i, m=0; + for(i=0; i<8; i++) { + int d = r[i]; + if(r[i] < 0) d == r[i] >= L ? 2 : 36; + if(d > m) m = d; + } + return m; +} + +void +Compactify (int stm) +{ // remove pieces that are permanently gone (captured or promoted) from one side's piece list + int i, j, k; + for(i=stm+2; i= 0 && p[i].pos == ABSENT) { // unpromoted piece no longer there + p[k].promo = -2; // orphan promoted version + SqueezeOut(i); + } + } + for(i=stm+2; ii; j-= 2) p[j] = p[j-2]; + p[i].value = 10*list[n].value; + for(j=0; j<8; j++) p[i].range[j] = list[n].range[j^4*(WHITE-stm)]; + switch(Range(p[i].range)) { + case 1: p[i].pst = BH; break; + case 2: p[i].pst = BSIZE; break; + default: p[i].pst = BSIZE + BH; break; + } + p[i].promoFlag = 0; + for(j=stm+2; j<= last[stm]; j+=2) { + if(p[j].promo >= i) p[j].promo += 2; + } + if(royal[stm] >= i) royal[stm] += 2; + if(p[i].value == 280) royal[stm] = i; + return i; +} + +void +SetUp(char *array, PieceDesc *list) +{ + int i, j, k, k2, n, m, nr, color; + char c, *q, name[3]; + last[WHITE] = 1; last[BLACK] = 0; + for(i=0; ; i++) { +//printf("next rank: %s\n", array); + for(j = BW*i; ; j++) { + c = name[0] = *array++; + if(!c) goto eos; + if(c == '.') continue; + if(c == '/') break; + name[1] = name[2] = 0; + if(c == ':') name[0] = *array++, name[1] = *array++; + if(name[0] >= 'a') { + color = BLACK; + name[0] += 'A' - 'a'; + if(name[1]) name[1] += 'A' - 'a'; + } else color = WHITE; + k = LookUp(name, list); + n = AddPiece(color, k, list); + p[n].pos = j; + if(list[k].promoted[0]) { + k2 = LookUp(list[k].promoted, list); + m = AddPiece(color, k2, list); + if(m <= n) n += 2; + p[n].promo = m; + p[n].promoFlag = IsUpwardCompatible(list[k2].range, list[k].range) * DONT_DEFER + CAN_PROMOTE; + if(Forward(list[k].range)) p[n].promoFlag |= LAST_RANK; // Pieces that only move forward can't defer on last rank + if(!strcmp(list[k].name, "N")) p[n].promoFlag |= CANT_DEFER; // Knights can't defer on last 2 ranks + p[n].promoFlag &= n&1 ? P_WHITE : P_BLACK; + p[m].promo = -1; + p[m].pos = ABSENT; + } else p[n].promo = -1; // unpromotable piece +//printf("piece = %c%-2s %d(%d) %d/%d\n", color ? 'w' : 'b', name, n, m, last[color], last[!color]); + } + } + eos: + for(i=0; i>10 ^ rand() << 10 ^ rand() << 20; +} + +void +Init() +{ + int i, j, k; + + for(i= -1; i<9; i++) { // board steps in linear coordinates + kStep[i] = STEP(direction[i&7].x, direction[i&7].y); // King + nStep[i] = STEP(direction[(i&7)+8].x, direction[(i&7)+8].y); // Knight + } + + for(i=0; i<8; i++) { // Lion double-move decoding tables + for(j=0; j<8; j++) { + epList[8*i+j] = kStep[i]; + toList[8*i+j] = kStep[j] + kStep[i]; + for(k=0; k<8*i+j; k++) + if(epList[k] == toList[8*i+j] && toList[k] == epList[8*i+j]) + reverse[k] = 8*i+j, reverse[8*i+j] = k; + } + // Lion-Dog triple moves + toList[64+i] = 3*kStep[i]; epList[64+i] = kStep[i]; + toList[72+i] = 3*kStep[i]; epList[72+i] = 2*kStep[i]; + toList[80+i] = 3*kStep[i]; epList[80+i] = kStep[i]; ep2List[80+i] = 2*kStep[i]; + toList[88+i] = kStep[i]; epList[88+i] = 2*kStep[i]; + } + + // fill distance table + for(i=0; i= BH-ZONE) v |= (CAN_PROMOTE | DONT_DEFER) & P_WHITE; + if(i >= BH-2) v |= CANT_DEFER & P_WHITE; + if(i == BH-1) v |= LAST_RANK & P_WHITE; + promoBoard[BW*i + j] = v; + } + + // piece-square tables + for(i=0; i= -3) { // in any case, do a jump of 2 + if(board[x + 2*v] == EMPTY) + ADDMOVE(x, x+2*v); + if(r < -1) { // Lion power, also single step + if(board[x + v] == EMPTY) + ADDMOVE(x, x+v); + if(r == -3) { // true Lion, also Knight jump + v = nStep[j]; + if(board[x + v] == EMPTY) + ADDMOVE(x, x+v); + } + } + } + continue; + } + y = x; + while(r-- > 0) { + if(board[y+=v] == GUARD) break; // off board + if((board[y] + i & 1) == 0) break; // same color + } + } +} +#endif + +int +GenNonCapts (int promoSuppress) +{ + int i, j, nullMove = ABSENT; + for(i=stm+2; i<=last[stm]; i+=2) { + int x = p[i].pos, pFlag = p[i].promoFlag; + if(x == ABSENT) continue; + if(x == promoSuppress) pFlag = 0; + for(j=0; j<8; j++) { + int y, v = kStep[j], r = p[i].range[j]; + if(r < 0) { // jumping piece, special treatment + if(r >= S) { // in any case, do a jump of 2 + NewNonCapture(x, x + 2*v, pFlag); + if(r < N) { // Lion power, also single step + if(!NewNonCapture(x, x + v, pFlag)) nullMove = x; + if(r == L) { // true Lion, also Knight jump + v = nStep[j]; + NewNonCapture(x, x + v, pFlag); + } + } + } + continue; + } + y = x; + while(r-- > 0) + if(NewNonCapture(x, y+=v, pFlag)) break; + } + } + return nullMove; +} + +void +report (int x, int y, int i) +{ +} + +void +MapOneColor (int start, int last, int *map) +{ + int i, j, k; + for(i=start+2; i<=last; i+=2) { + if(p[i].pos == ABSENT) continue; + for(j=0; j<8; j++) { + int x = p[i].pos, v = kStep[j], r = p[i].range[j]; + if(r < 0) { // jumping piece, special treatment + if(r >= S) { // in any case, do a jump of 2 + if(board[x + 2*v] != EMPTY && board[x + 2*v] != EDGE) + map[2*(x + 2*v) + start] += one[j]; + if(r < J) { // Lion power, also single step + if(board[x + v] != EMPTY && board[x + v] != EDGE) + map[2*(x + v) + start] += one[j]; + if(r == T) { // Lion Dog, also jump of 3 + if(board[x + 3*v] != EMPTY && board[x + 3*v] != EDGE) + map[2*(x + 3*v) + start] += one[j]; + } else + if(r <= L) { // true Lion, also Knight jump + if(r < L) { // Lion plus (limited) range + int y = x, n = 0; + r = (r == S ? 36 : 3); + while(n++ <= r) { + if(board[y+=v] == EDGE) break; + if(board[y] != EMPTY) { + if(n > 2) map[2*y + start] += one[j]; // outside Lion range + break; + } + } + } + v = nStep[j]; + if(board[x + v] != EMPTY && board[x + v] != EDGE) + map[2*(x + v) + start] += one[8]; + } + } + } + continue; + } + while(r-- > 0) { + if(board[x+=v] != EMPTY) { + if(board[x] != EDGE) map[2*x + start] += one[j]; + break; + } + } + } + } +} + +void +MapFromScratch (int *map) +{ + int i; + for(i=0; i<2*BSIZE; i++) map[i] = 0; + MapOneColor(0, last[BLACK], map); + MapOneColor(1, last[WHITE], map); +} + +void +Connect (int sqr, int piece, int dir) +{ + int x, step = kStep[dir], r1 = p[piece].range[dir], r2 = p[piece].range[dir+1], piece1, piece2; + int d1, d2, r, y, c; + + if((attacks[2*sqr] + attacks[2*sqr+1]) & attackMask[dir]) { // there are incoming attack(s) from 'behind' + x = sqr; + while(board[x-=step] == EMPTY); // in any case, scan to attacker, to see where / what it is + d1 = dist[x-sqr]; piece1 = board[x]; + attacks[2*x + stm] -= -(d1 <= r2) & one[dir+4]; // remove our attack on it if in-range + if((attacks[2*sqr] + attacks[2*sqr+1]) & attackMask[dir+4]) { // there are also incoming attack(s) from 'ahead' + + y = sqr; + while(board[y+=step] == EMPTY); // also always scan to that one to see what it is + d2 = dist[y-sqr]; piece2 = board[y]; + attacks[2*y+stm] -= -(d2 <= r1) & one[dir]; // remove our attack on it if in-range + // we have two pieces now shooting at each other. See how far they get. + if(d1 + d2 <= (r1 = p[piece1].range[dir])) { // 1 hits 2 + attacks[2*y + (piece1 & WHITE)] += one[dir]; // count attack + UPDATE_MOBILITY(piece1, d2); + } else UPDATE_MOBILITY(piece1, r1 - d1); // does not connect, but could still gain mobility + if(d1 + d2 <= (r2 = p[piece2].range[dir])) { // 2 hits 1 + attacks[2*x + (piece2 & WHITE)] += one[dir+4]; // count attack + UPDATE_MOBILITY(piece2, d1); + } else UPDATE_MOBILITY(piece2, r2 - d2); // does not connect, but could still gain mobility + + } else { // we were only attacked from behind + + r = (r2 = p[piece1].range[dir]) - d1; + if(r < 0 || c > one[dir+1]) { // Oops! This was not our attacker, or not the only one. There must be a jump attack from even further behind! + // for now, forget jumpers + } + y = sqr; + while(r--) + if(board[y+=step] != EMPTY) { + d2 = dist[y-sqr]; piece2 = board[y]; + if(piece2 != EDGE) { // extended move hits a piece + attacks[2*y + (piece1 & WHITE)] += one[dir]; // count attack + attacks[2*y + stm] -= -(d2 <= r1) & one[dir]; // remove our own attack on it, if in-range + } + UPDATE_MOBILITY(piece1, d2); // count extra mobility even if we hit edge + return; + } + // we hit nothing with the extended move of the attacker behind us. + UPDATE_MOBILITY(piece1, r2 - d1); + r = r1 - r2; // extra squares covered by mover + while(r-- > 0) + if(board[y+=step] != EMPTY) { + d2 = dist[y-sqr]; piece2 = board[y]; + if(piece2 != EDGE) { // extended move hits a piece + attacks[2*y + stm] -= one[dir]; // count attack + } + return; + } + } + + } else // no incoming attack from behind + if(c = (attacks[2*sqr] + attacks[2*sqr+1]) & attackMask[dir+4]) { // but incoming attack(s) from 'ahead' + + y = sqr; while(board[y+=step]); // locate attacker + d2 = dist[y-sqr]; piece2 = board[y]; + attacks[2*y + stm] -= -(d2 <= r1) & one[dir]; // remove our attack on it if in-range + r = (r1 = p[piece1].range[dir]) - d2; + if(r < 0 || c > one[dir]) { // Oops! This was not our attacker, or not the only one. There must be a jump attack from even further behind! + // for now, forget jumpers + } + x = sqr; + while(r--) + if(board[x-=step] != EMPTY) { + d1 = dist[x-sqr]; piece1 = board[x]; + if(piece1 != EDGE) { // extended move hits a piece + attacks[2*x + (piece2 & WHITE)] += one[dir+4]; // count attack + attacks[2*x + stm] -= -(d1 <= r2) & one[dir+4]; // remove our own attack on it, if in-range + } + UPDATE_MOBILITY(piece2, d1); // count extra mobility even if we hit edge + return; + } + // we hit nothing with the extended move of the attacker behind us. + UPDATE_MOBILITY(piece2, r2 - d1); + r = r2 - r1; // extra squares covered by mover + while(r-- > 0) + if(board[x-=step] != EMPTY) { + d1 = dist[x-sqr]; piece1 = board[x]; + if(piece1 != EDGE) { // extended move hits a piece + attacks[2*x + stm] -= one[dir+4]; // count attack + } + return; + } + + } else { // no incoming attacks from either side. Only delete attacks of mover on others + + x = sqr; + while(r1--) + if(board[x+=step] != EMPTY) { // piece found that we attacked + attacks[2*x + stm] -= one[dir]; // decrement attacks along that direction + break; + } + + x = sqr; + while(r2--) + if(board[x-=step] != EMPTY) { // piece found that we attacked + attacks[2*x + stm] -= one[dir+4]; // decrement attacks along opposite direction + break; + } + + } +} + +void +Disconnect (int sqr, int dir) +{ + int x = sqr, step = kStep[dir], piece1, piece2, y; + while( board[x+=step] == EMPTY ); + if(board[x] != EDGE) { // x has hit a piece + piece1 = board[x]; + y = sqr; while( board[y-=step] == EMPTY ); + if(board[y] != EDGE) { // both ends of the ray hit a piece + piece2 = board[y]; + + return; + } + } else { + x = sqr; while( board[x-=step] == EMPTY ); + if(board[x] == EDGE) return; // ray empty on both sides + } + // we only get here if one side hits a + +} + +void +Occupy (int sqr) +{ // determines attacks on square and blocking when a piece lands on an empty square + int i; + for(i=0; i<4; i++) { + Disconnect(sqr, i); + } +} + +void +Evacuate (int sqr, int piece) +{ // determines change in attacks on neighbors due to unblocking and mover when the mentioned piece vacates the given square + int i; + for(i=0; i<4; i++) Connect(sqr, piece, i); +} + +int +MakeMove(Move m, UndoInfo *u) +{ + int deferred = ABSENT; + // first execute move on board + u->from = m>>SQLEN & SQUARE; + u->to = m & SQUARE; + u->piece = board[u->from]; + board[u->from] = EMPTY; + u->booty = 0; + + if(m & (PROMOTE | DEFER)) { + if(m & DEFER) { + deferred = u->to; + u->new = u->piece; + } else { + p[u->piece].pos = ABSENT; + u->new = p[u->piece].promo; + u->booty = p[u->new].value - p[u->piece].value; + } + } else u->new = u->piece; + + u->booty += PST[p[u->new].pst + u->to] - PST[p[u->piece].pst + u->from]; + + if(u->to >= SPECIAL) { // two-step Lion move + // take care of first e.p. victim + u->epSquare = u->from + epList[u->to - SPECIAL]; // decode + u->epVictim = board[u->epSquare]; // remember for takeback + board[u->epSquare] = EMPTY; // and remove + p[u->epVictim].pos = ABSENT; + // now take care of (optional) second e.p. victim + u->ep2Square = u->from + ep2List[u->to - SPECIAL]; // This is the (already evacuated) from-square when there is none! + u->ep2Victim = board[u->ep2Square]; // remember + board[u->ep2Square] = EMPTY; // and remove + p[u->ep2Victim].pos = ABSENT; + // decode the true to-square, and correct difEval and hash key for the e.p. captures + u->to = u->from + toList[u->to - SPECIAL]; + u->booty += p[u->ep2Victim].value + PST[p[u->ep2Victim].pst + u->ep2Square]; + u->booty += p[u->epVictim].value + PST[p[u->epVictim].pst + u->epSquare]; + hashKeyL ^= p[u->epVictim].pieceKey * squareKey[u->epSquare]; + hashKeyH ^= p[u->epVictim].pieceKey * squareKey[u->epSquare+BH]; + hashKeyL ^= p[u->ep2Victim].pieceKey * squareKey[u->ep2Square]; + hashKeyH ^= p[u->ep2Victim].pieceKey * squareKey[u->ep2Square+BH]; + if(p[u->piece].value != 1000 && p[u->epVictim].value == 1000) deferred |= PROMOTE; // flag non-Lion x Lion + } else u->epVictim = EMPTY; + + u->victim = board[u->to]; + p[u->victim].pos = ABSENT; + u->booty += p[u->victim].value + PST[p[u->victim].pst + u->to]; +// if(p[u->victim].value == 1000 && p[u->piece].value != 1000) deferred |= PROMOTE; // flag non-Lion x Lion + + p[u->new].pos = u->to; + board[u->to] = u->new; + + hashKeyL ^= p[u->new].pieceKey * squareKey[u->to] + ^ p[u->piece].pieceKey * squareKey[u->from] + ^ p[u->victim].pieceKey * squareKey[u->to]; + hashKeyH ^= p[u->new].pieceKey * squareKey[u->to+BH] + ^ p[u->piece].pieceKey * squareKey[u->from+BH] + ^ p[u->victim].pieceKey * squareKey[u->to+BH]; + + return deferred; +} + +void +UnMake(UndoInfo *u) +{ + if(u->epVictim) { // put Lion victim of first leg back + p[u->ep2Victim].pos = u->ep2Square; + board[u->ep2Square] = u->ep2Victim; + p[u->epVictim].pos = u->epSquare; + board[u->epSquare] = u->epVictim; + } + + p[u->victim].pos = u->to; + board[u->to] = u->victim; // can be EMPTY + + p[u->new].pos = ABSENT; + p[u->piece].pos = u->from; // this can be the same as above + board[u->from] = u->piece; +} + +void +GenCapts(int sqr, int victimValue) +{ // generate all moves that capture the piece on the given square + int i, range, att = attacks[2*sqr + stm]; +//printf("GenCapts(%d,%d)\n",sqr,victimValue); + for(i=0; i<8; i++) { // try all rays + int x, v, jumper; + if(att & attackMask[i]) { // attacked by move in this direction + v = -kStep[i]; x = sqr; + while( board[x+=v] == EMPTY ); // scan towards source until we encounter a 'stop' +//printf("stop @ %c%d (dir %d)\n",x%BW+'a',x/BW,i); + if((board[x] & TYPE) == stm) { // stop is ours + int attacker = board[x], d = dist[x-sqr], r = p[attacker].range[i]; +//printf("attacker %d, range %d, dist %d\n", attacker, r, d); + if(r >= d || r < L && (d > 3 && r == S || d == 3 && r >= S)) { // it has a plain move in our direction that hits us + NewCapture(x, sqr + victimValue - SORTKEY(attacker), p[attacker].promoFlag); + att -= one[i]; + if(!(att & attackMask[i])) continue; + } else if(r < 0) goto lions; // test for special-move attacks by thiis stop + } + // we get here when the first stop on the ray is an opponent, or a normal mover which did not range fare enough + while(board[x+=v] == EMPTY); // next stop + lions: + if((board[x] & TYPE) == stm) { // second stop is ours + int attacker = board[x], d = dist[x-sqr], r = p[attacker].range[i]; + if(r < 0) { // stop has non-standard moves + switch(p[attacker].range[i]) { // figure out what he can do (including multi-captures, which causes the complexity) + case F: // Lion power + 3-step (as in FF) + case S: // Lion power + ranging (as in BS) + case L: // Lion + if(d > 2) break; + NewCapture(x, sqr + victimValue - SORTKEY(attacker), p[attacker].promoFlag); + att -= one[i]; + // now the multi-captures of designated victim together with lower-valued piece + if(d == 2) { // primary victim on second ring; look for victims to take in passing + if((board[sqr+v] & TYPE) == xstm && board[sqr+v] > board[sqr]) + NewCapture(x, SPECIAL + 9*i + victimValue - SORTKEY(attacker), p[attacker].promoFlag); + if((i&1) == 0) { // orthogonal: two extra bent paths + v = kStep[i-1]; + if((board[x+v] & TYPE) == xstm && board[x+v] > board[sqr]) + NewCapture(x, SPECIAL + 8*(i-1&7) + (i+1&7) + victimValue - SORTKEY(attacker), p[attacker].promoFlag); + v = kStep[i+1]; + if((board[x+v] & TYPE) == xstm && board[x+v] > board[sqr]) + NewCapture(x, SPECIAL + 8*(i+1&7) + (i-1&7) + victimValue - SORTKEY(attacker), p[attacker].promoFlag); + } + } else { // primary victim on first ring + int j; + for(j=0; j<8; j++) { // we can go on in 8 directions after we captured it in passing + int v = kStep[j]; + if(sqr + v == x || board[sqr+v] == EMPTY) // hit & run; make sure we include igui (attacker is still at x!) + NewCapture(x, SPECIAL + 8*i + j + victimValue, p[attacker].promoFlag); + else if((board[sqr+v] & TYPE) == xstm && board[sqr+v] > board[sqr]) { // double capture + NewCapture(x, SPECIAL + 8*i + j + victimValue, p[attacker].promoFlag); // other victim after primary + if(dist[sqr+v-x] == 1) // other victim also on first ring; reverse order is possible + NewCapture(x, SPECIAL + reverse[8*i + j] + victimValue, p[attacker].promoFlag); + } + } + } + break; + case D: // linear Lion move (as in HF, SE) + if(d > 2) break; + NewCapture(x, sqr + victimValue - SORTKEY(attacker), p[attacker].promoFlag); + att -= one[i]; + if(d == 2) { // check if we can take intermediate with it + if((board[x-v] & TYPE) == xstm && board[x-v] > board[sqr]) + NewCapture(x, SPECIAL + 9*i + victimValue - SORTKEY(attacker), p[attacker].promoFlag); // e.p. + } else { // d=1; can move on to second, or move back for igui + NewCapture(x, SPECIAL + 8*i + (i^4) + victimValue, p[attacker].promoFlag); // igui + if(board[sqr-v] == EMPTY || (board[sqr-v] & TYPE) == xstm && board[sqr-v] > board[sqr]) + NewCapture(x, SPECIAL + 9*i + victimValue - SORTKEY(attacker), p[attacker].promoFlag); // hit and run + } + break; + case J: // plain jump (as in KY, PH) + if(d != 2) break; + NewCapture(x, sqr + victimValue - SORTKEY(attacker), p[attacker].promoFlag); + att -= one[i]; + } + } +//printf("mask[%d] = %o\n", i, att); + if((att & attackMask[i]) == 0) continue; // no other attackers, so done with this direction + } + // now we get to the hairy part: there are other attackers than the stop, apparently jumping over it + } + } + // off-ray attacks + if(att & 0700000000) { // Knight attack + for(i=0; i<8; i++) { // scan all knight jumps to locate source + int x = sqr - nStep[i], attacker = board[x]; + if(attacker == EMPTY || (attacker & TYPE) != stm) continue; + if(p[attacker].range[i] <= N && p[attacker].range[i] >= S) { // has Knight jump in our direction + NewCapture(x, sqr + victimValue, p[attacker].promoFlag); // plain jump (as in N) + if(p[attacker].range[i] < N) { // Lion power; generate double captures over two possible intermediates + int v = kStep[i]; // leftish path + if((board[x+v] & TYPE) == xstm && board[x+v] > board[sqr]) + NewCapture(x, SPECIAL + 8*i + (i+1&7) + victimValue, p[attacker].promoFlag); + v = kStep[i+1]; // rightish path + if((board[x+v] & TYPE) == xstm && board[x+v] > board[sqr]) + NewCapture(x, SPECIAL + 8*(i+1&7) + i + victimValue, p[attacker].promoFlag); + } + } + } + } +} + +int +Evaluate () +{ + return 0; +} + +int flag; + +int +Search (int alpha, int beta, int difEval, int depth, int oldPromo, int promoSuppress) +{ + int i, j, k, firstMove, oldMSP = msp, curMove, sorted, bad, phase, king, iterDep, replyDep, nextVictim, to, defer, dubious, bestMoveNr; + int savHashL = hashKeyL, savHashH = hashKeyH; + int score, bestScore, curEval; + Move move, nullMove; + UndoInfo tb; +if(PATH) printf("search(%d) %d-%d eval=%d, stm=%d\n",depth,alpha,beta,difEval,stm),fflush(stdout); + xstm = stm ^ WHITE; +//printf("map made\n");fflush(stdout); + // KING CAPTURE + k = p[king=royal[xstm]].pos; + if( k != ABSENT) { + if(attacks[2*k + stm]) { + if( p[king + 2].pos == ABSENT ) return INF; // we have an attack on his only King + } + } else { // he has no king! Test for attacks on Crown Prince + k = p[king + 2].pos; + if(attacks[2*k + stm]) return INF; // we have attack on Crown Prince + } +//printf("King safe\n");fflush(stdout); + // EVALUATION & WINDOW SHIFT + curEval = difEval + Evaluate(); + alpha -= (alpha < curEval); + beta -= (beta <= curEval); + + firstMove = curMove = sorted = nonCapts = msp += 20; // leave 20 empty slots in front of move list + phase = 0; iterDep=1; replyDep = (depth < 1 ? depth : 1) - 1; + do { +if(flag && depth>= 0) printf("iter %d:%d\n", depth,iterDep),fflush(stdout); + bestScore = -INF; bestMoveNr = 0; + for(curMove = firstMove; ; curMove++) { // loop over moves +if(flag && depth>= 0) printf("phase=%d: first/curr/last = %d / %d / %d\n", phase, firstMove, curMove, msp);fflush(stdout); + // MOVE SOURCE + if(curMove >= msp) { // we ran out of moves; generate some new + switch(phase) { + case 0: // null move + if(depth <= 0) { + bestScore = curEval; + if(bestScore >= beta || depth < -1) goto cutoff; + } +#if 0 + if(curEval >= beta) { + stm ^= WHITE; + score = -Search(-beta, -alpha, difEval, depth-3, promoSuppress & SQUARE, ABSENT); + stm ^= WHITE; + if(score >= beta) { msp = oldMSP; return score + (score < curEval); } + } +#endif + phase = 1; + case 1: // hash move + phase = 2; + case 2: // capture-gen init + nextVictim = xstm; + phase = 3; + case 3: // generate captures + while(nextVictim < last[xstm]) { // more victims exist + int group, to = p[nextVictim += 2].pos; // take next + if(to == ABSENT) continue; // ignore if absent + if(!attacks[2*to + stm]) continue; // skip if not attacked + group = p[nextVictim].value; // remember value of this found victim + GenCapts(to, 0); + while(nextVictim < last[xstm] && p[nextVictim+2].value == group) { // more victims of same value exist + to = p[nextVictim += 2].pos; // take next + if(to == ABSENT) continue; // ignore if absent + if(!attacks[2*to + stm]) continue; // skip if not attacked + GenCapts(to, 0); + } +//printf("captures on %d generated, msp=%d\n", nextVictim, msp); + goto extractMove; + } + phase = 4; // out of victims: all captures generated + case 4: // dubious captures +#if 0 + while( dubious < framePtr + 250 ) // add dubious captures back to move stack + moveStack[msp++] = moveStack[dubious++]; + if(curMove != msp) break; +#endif + phase = 5; + case 5: // killers + if(depth <= 0) goto cutoff; + phase = 6; + case 6: // non-captures + nullMove = GenNonCapts(oldPromo); + phase = 7; + sorted = msp; // do not sort noncapts + break; + case 7: // bad captures + case 8: // PV null move + case 9: + goto cutoff; + } + } + + // MOVE EXTRACTION + extractMove: +if(flag & depth >= 0) printf("%2d:%d extract %d/%d\n", depth, iterDep, curMove, msp); + if(curMove < sorted) { + move = moveStack[sorted=j=curMove]; + for(i=curMove+1; i move) move = moveStack[j=i]; // search move with highest priority + moveStack[j] = moveStack[curMove]; moveStack[curMove] = move; // swap highest-priority move to front of remaining + if(move == 0) { msp = curMove--; continue; } // all remaining moves must be 0; clip off move list + } else { + move = moveStack[curMove]; + if(move == 0) continue; // skip invalidated move + } +if(flag & depth >= 0) printf("%2d:%d found %d/%d\n", depth, iterDep, curMove, msp); + // RECURSION + stm ^= WHITE; + defer = MakeMove(moveStack[curMove], &tb); +path[level++] = moveStack[curMove]; +attacks += 2*BSIZE; +MapFromScratch(attacks); // for as long as incremental update does not work. +if(PATH) pmap(attacks, stm); + if(chuFlag && p[tb.victim].value == 1000) { // verify legality of Lion capture in Chu Shogi + score = 0; + if(p[tb.piece].value == 1000) { // Ln x Ln: can make Ln 'vulnarable' (if distant and not through intemediate > GB) + if(dist[tb.from-tb.to] != 1 && attacks[2*tb.to + stm] && p[tb.epVictim].value <= 50) + score = -INF; // our Lion is indeed made vulnerable and can be recaptured + } else { // other x Ln + if(promoSuppress & PROMOTE) score = -INF; // non-Lion captures Lion after opponent did same + defer |= PROMOTE; // if we started, flag he cannot do it in reply + } + if(score == -INF) { moveStack[curMove] = 0; goto abortMove; } + } +#if 1 + score = -Search(-beta, -alpha, -difEval - tb.booty, replyDep, promoSuppress & ~PROMOTE, defer); +#else + score = 0; +#endif + abortMove: +attacks -= 2*BSIZE; +level--; + UnMake(&tb); + hashKeyL = savHashL; + hashKeyH = savHashH; + xstm = stm; stm ^= WHITE; +#if 1 +if(PATH) printf("%d:%2d:%d %3d %6x %-10s %6d %6d\n", level, depth, iterDep, curMove, moveStack[curMove], MoveToText(moveStack[curMove], 0), score, bestScore); + // ALPHA-BETA STUFF + if(score > bestScore) { + bestScore = score; bestMoveNr = curMove; + if(score > alpha) { + alpha = score; + if(curMove < firstMove + 5) { // if not too much work, sort move to front + int i; + for(i=curMove; i>firstMove; i--) { + moveStack[i] = moveStack[i-1]; + } + moveStack[firstMove] = move; + } else { // late move appended in front of list, and leaves hole in list + moveStack[--firstMove] = move; + moveStack[curMove] = 0; + } + bestMoveNr = firstMove; + if(score >= beta) { // beta cutoff + // update killer + goto cutoff; + } + } + + } +#endif + } // next move + cutoff: + replyDep = iterDep; + } while(++iterDep <= depth); // next depth + retMSP = msp; + retFirst = firstMove; + msp = oldMSP; + retMove = bestMoveNr ? moveStack[bestMoveNr] : 0; +if(flag && depth >= 0) printf("return %d: %d %d\n", depth, bestScore, curEval); + return bestScore + (bestScore < curEval); +} + +void +pplist() +{ + int i, j; + for(i=0; i<182; i++) { + printf("%3d. %3d %3d %4d %02x ", i, p[i].value, p[i].promo, p[i].pos, p[i].promoFlag&255); + for(j=0; j<8; j++) printf(" %2d", p[i].range[j]); + printf("\n"); + } + printf("last: %d / %d\nroyal %d / %d\n", last[WHITE], last[BLACK], royal[WHITE], royal[BLACK]); +} + +void +pboard (int *b) +{ + int i, j; + for(i=BH+2; i>-4; i--) { + printf("#"); + for(j=-3; j=0; i--) { + for(j=0; j=0; i--) { + printf("#"); + for(j=0; j>SQLEN & SQUARE; + t = m & SQUARE; + printf("# %3d. %08x %3d-%3d %s\n", i, m, f, t, MoveToText(m, 0)); + } +} + + /********************************************************/ + /* Example of a WinBoard-protocol driver, by H.G.Muller */ + /********************************************************/ + + #include + + // four different constants, with values for WHITE and BLACK that suit your engine + #define NONE 3 + #define ANALYZE 4 + + // some value that cannot occur as a valid move + #define INVALID 0 + + // some parameter of your engine + #define MAXMOVES 500 /* maximum game length */ + #define MAXPLY 60 /* maximum search depth */ + + #define OFF 0 + #define ON 1 + +#define ONE 0 +#define DEFAULT_FEN "" + +typedef Move MOVE; + + int moveNr; // part of game state; incremented by MakeMove + MOVE gameMove[MAXMOVES]; // holds the game history + + // Some routines your engine should have to do the various essential things + int MakeMove2(int stm, MOVE move); // performs move, and returns new side to move + void UnMake2(MOVE move); // unmakes the move; + int Setup2(char *fen); // sets up the position from the given FEN, and returns the new side to move + void SetMemorySize(int n); // if n is different from last time, resize all tables to make memory usage below n MB + char *MoveToText(MOVE move, int m); // converts the move from your internal format to text like e2e2, e1g1, a7a8q. + MOVE ParseMove(char *moveText); // converts a long-algebraic text move to your internal move format + int SearchBestMove(int stm, int timeLeft, int mps, int timeControl, int inc, int timePerMove, MOVE *move, MOVE *ponderMove); + void PonderUntilInput(int stm); // Search current position for stm, deepening forever until there is input. + +UndoInfo undoInfo; +int sup0, sup1, sup2; // promo suppression squares +int lastLift, lastPut; + +int +MakeMove2 (int stm, MOVE move) +{ + sup0 = sup1; sup1 = sup2; + sup2 = MakeMove(move, &undoInfo); + return stm ^ WHITE; +} + +void +UnMake2 (MOVE move) +{ + UnMake(&undoInfo); + sup2 = sup1; sup1 = sup0; +} + +int +Setup2 (char *fen) +{ + SetUp(chuArray, chuPieces); + sup0 = sup1 = sup2 = ABSENT; + return WHITE; +} + +void +SetMemorySize (int n) +{ +} + +char * +MoveToText (MOVE move, int multiLine) +{ + static char buf[50]; + int f = move>>SQLEN & SQUARE, g = f, t = move & SQUARE; + buf[0] = '\0'; + if(t >= SPECIAL) { // kludgy! Print as side effect non-standard WB command to remove victims from double-capture (breaks hint command!) + int e = f + epList[t - SPECIAL]; +// printf("take %c%d\n", e%BW+'a', e/BW+ONE); + sprintf(buf, "%c%d%c%d,", f%BW+'a', f/BW+ONE, e%BW+'a', e/BW+ONE); f = e; + if(multiLine) printf("move %s\n", buf), buf[0] = '\0'; + if(ep2List[t - SPECIAL]) { + e = g + ep2List[t - SPECIAL]; +// printf("take %c%d\n", e%BW+'a', e/BW+ONE); + sprintf(buf+strlen(buf), "%c%d%c%d,", f%BW+'a', f/BW+ONE, e%BW+'a', e/BW+ONE); f = e; + if(multiLine) printf("move %s\n", buf), buf[0] = '\0'; + } + t = g + toList[t - SPECIAL]; + } + sprintf(buf+strlen(buf), "%c%d%c%d%s", f%BW+'a', f/BW+ONE, t%BW+'a', t/BW+ONE, move & PROMOTE ? "+" : ""); + return buf; +} + +int +ReadSquare (char *p, int *sqr) +{ + int i=0, f, r; + f = p[0] - 'a'; + r = atoi(p + 1) - ONE; + *sqr = r*BW + f; + return 2 + (r > 9); +} + +MOVE +ParseMove (char *moveText) +{ + int i, j, f, t, t2, e, ret, deferred=0; + moveText += ReadSquare(moveText, &f); + moveText += ReadSquare(moveText, &t); t2 = t; + if(*moveText == ',') { + moveText++; + moveText += ReadSquare(moveText, &e); + if(e != t) return INVALID; // must continue with same piece + e = t; + moveText += ReadSquare(moveText, &t); + for(i=0; i<8; i++) if(f + kStep[i] == e) break; + if(i >= 8) return INVALID; // this rejects Lion Dog 2+1 and 2-1 moves! + for(j=0; j<8; j++) if(e + kStep[j] == t) break; + if(j >= 8) return INVALID; // this rejects Lion Dog 1+2 moves! + t2 = SPECIAL + 8*i + j; + } + ret = f<=retMSP) { // no exact match + if(deferred) { // but maybe non-sensical deferral + int flags = p[board[f]].promoFlag; + i = deferred; // in any case we take that move + if(!(flags & promoBoard[t] & (CANT_DEFER | LAST_RANK))) { // but change it into a deferral if that is allowed + moveStack[i] &= ~PROMOTE; + if(p[board[f]].value == 40) p[board[f]].promoFlag &= LAST_RANK; else + if(!(flags & promoBoard[t])) moveStack[i] |= DEFER; // came from outside zone, so essential deferral + } + } + if(i >= retMSP) + for(i=20; i= retMSP ? INVALID : moveStack[i]); +} + +void +Highlight(char *coords) +{ + int i, j, n, sqr, cnt=0; + char b[BSIZE], buf[2000], *q; + for(i=0; i>SQLEN & SQUARE)) { + int t = moveStack[i] & SQUARE; + if(t >= SPECIAL) continue; + b[t] = (board[t] == EMPTY ? 'Y' : 'R'); cnt++; + } + } + if(!cnt) { // no moves from given square + if(sqr != lastPut) return; // refrain from sending empty FEN + // we lifted a piece for second leg of move + for(i=20; i>SQLEN & SQUARE)) { + int e, t = moveStack[i] & SQUARE; + if(t < SPECIAL) continue; // only special moves + e = lastLift + epList[t - SPECIAL]; // decode + t = lastLift + toList[t - SPECIAL]; + if(e != sqr) continue; + b[t] = (board[t] == EMPTY ? 'Y' : 'R'); cnt++; + } + } + if(!cnt) return; + } else lastLift = sqr; // remember + lastPut = ABSENT; + q = buf; + for(i=BH-1; i>=0; i--) { + for(j=0; j buf && q[-1] <= '9' && q[-1] >= '0') { + q[-1]++; + if(q[-1] > '9') { + if(q > buf+1 && q[-2] <= '9' && q[-2] >= '0') q[-2]++; else q[-1] = '1', *q++ = '0'; + } + } else *q++ = '1'; + } + } + *q++ = '/'; + } + q[-1] = 0; + printf("highlight %s\n", buf); +} + +int +SearchBestMove (int stm, int timeLeft, int mps, int timeControl, int inc, int timePerMove, MOVE *move, MOVE *ponderMove) +{ + int score; +MapFromScratch(attacks); + score = Search(-INF-1, INF+1, 0, 3, sup1, sup2); + *move = retMove; + *ponderMove = INVALID; + return score; +} + +void +PonderUntilInput (int stm) +{ +} + + // Some global variables that control your engine's behavior + int ponder; + int randomize; + int postThinking; + int resign; // engine-defined option + int contemptFactor; // likewise + + int TakeBack(int n) + { // reset the game and then replay it to the desired point + int last, stm; + stm = Setup2(NULL); +printf("# setup done");fflush(stdout); + 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"); + } + + main() + { + int engineSide=NONE; // side played by engine + int timeLeft; // timeleft on engine's clock + int mps, timeControl, inc, timePerMove; // time-control parameters, to be used by Search + int maxDepth; // used by search + MOVE move, ponderMove; + int i, score; + char inBuf[80], command[80]; + + Init(); + SetUp(chuArray, chuPieces); +// pplist(); +// pboard(board); +// pbytes(promoBoard); +// Search(-INF, INF, 0, 1, ABSENT, ABSENT); +// pmoves(20, retMSP); +//MapFromScratch(attacks); +// MapOneColor(1, last[WHITE], attacks); + + 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 + + score = SearchBestMove(stm, timeLeft, mps, timeControl, inc, timePerMove, &move, &ponderMove); + + if(move == INVALID) { // game apparently ended + engineSide = NONE; // so stop playing + PrintResult(stm, score); + } else { + stm = MakeMove2(stm, move); // assumes MakeMove returns new side to move + gameMove[moveNr++] = move; // remember game + printf("move %s\n", MoveToText(move, 1)); + } + } + + fflush(stdout); // make sure everything is printed before we do something that might take time + + // 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 = MakeMove2(stm, ponderMove); + PonderUntilInput(newStm); + UnMake2(ponderMove); + } + } + + noPonder: + // wait for input, and read it until we have collected a complete line + for(i = 0; (inBuf[i] = getchar()) != '\n'; i++); + inBuf[i+1] = 0; +pboard(board); +pmoves(retFirst, retMSP); + + // extract the first word + sscanf(inBuf, "%s", command); +printf("in: %s\n", command); + + // recognize the command,and execute it + if(!strcmp(command, "quit")) { break; } // breaks out of infinite loop + if(!strcmp(command, "force")) { engineSide = NONE; continue; } + if(!strcmp(command, "analyze")) { engineSide = ANALYZE; continue; } + if(!strcmp(command, "exit")) { engineSide = NONE; continue; } + if(!strcmp(command, "otim")) { goto noPonder; } // do not start pondering after receiving time commands, as move will follow immediately + if(!strcmp(command, "time")) { sscanf(inBuf, "time %d", &timeLeft); goto noPonder; } + 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; + continue; + } + if(!strcmp(command, "protover")){ + printf("feature ping=1 setboard=1 colors=0 usermove=1 memory=1 debug=1\n"); + printf("feature variants=\"chu,12x12+0_fairy\"\n"); + printf("feature highlight=1\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"); + continue; + } + if(!strcmp(command, "option")) { // setting of engine-define option; find out which + if(sscanf(inBuf+7, "Resign=%d", &resign) == 1) continue; + if(sscanf(inBuf+7, "Contempt=%d", &contemptFactor) == 1) continue; + continue; + } + if(!strcmp(command, "sd")) { sscanf(inBuf, "sd %d", &maxDepth); continue; } + if(!strcmp(command, "st")) { sscanf(inBuf, "st %d", &timePerMove); continue; } + if(!strcmp(command, "memory")) { SetMemorySize(atoi(inBuf+7)); continue; } + if(!strcmp(command, "ping")) { printf("pong%s", inBuf+4); continue; } + // if(!strcmp(command, "")) { sscanf(inBuf, " %d", &); continue; } + if(!strcmp(command, "new")) { engineSide = BLACK; stm = Setup2(DEFAULT_FEN); maxDepth = MAXPLY; randomize = OFF; continue; } + if(!strcmp(command, "setboard")){ engineSide = NONE; stm = Setup2(inBuf+9); continue; } + if(!strcmp(command, "easy")) { ponder = OFF; continue; } + if(!strcmp(command, "hard")) { ponder = ON; continue; } + if(!strcmp(command, "undo")) { stm = TakeBack(1); continue; } + if(!strcmp(command, "remove")) { stm = TakeBack(2); continue; } + if(!strcmp(command, "go")) { engineSide = stm; continue; } + if(!strcmp(command, "post")) { postThinking = ON; continue; } + if(!strcmp(command, "nopost")) { postThinking = OFF;continue; } + if(!strcmp(command, "random")) { randomize = ON; continue; } + if(!strcmp(command, "hint")) { if(ponderMove != INVALID) printf("Hint: %s\n", MoveToText(ponderMove, 0)); continue; } + if(!strcmp(command, "lift")) { Highlight(inBuf+5); continue; } + if(!strcmp(command, "put")) { ReadSquare(inBuf+4, &lastPut); continue; } + if(!strcmp(command, "book")) { continue; } + if(!strcmp(command, "variant")) { continue; } + // non-standard commands + if(!strcmp(command, "p")) { pboard(board); continue; } + if(!strcmp(command, "w")) { pmap(attacks, WHITE); continue; } + if(!strcmp(command, "b")) { pmap(attacks, BLACK); continue; } + // ignored commands: + if(!strcmp(command, "xboard")) { continue; } + if(!strcmp(command, "computer")){ continue; } + if(!strcmp(command, "name")) { continue; } + if(!strcmp(command, "ics")) { continue; } + if(!strcmp(command, "accepted")){ continue; } + if(!strcmp(command, "rejected")){ continue; } + if(!strcmp(command, "hover")) { continue; } + if(!strcmp(command, "")) { continue; } + if(!strcmp(command, "usermove")){ + int move = ParseMove(inBuf+9); + if(move == INVALID) printf("Illegal move\n"); + else { + stm = MakeMove2(stm, move); + ponderMove = INVALID; + gameMove[moveNr++] = move; // remember game + } + continue; + } + printf("Error: unknown command\n"); + } + } +