From: H.G. Muller Date: Tue, 3 Jul 2012 12:54:54 +0000 (+0200) Subject: Initial commit of Linux code X-Git-Tag: 0.18~104 X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=8712dc34fb0b839386db7bf7256e0a80352841f4;p=hachu.git Initial commit of Linux code This embrionic code was developed on Linux. The move generator is sort of working, but the multi-move feature is barely tested, because the standard XBoard does not support that. --- 8712dc34fb0b839386db7bf7256e0a80352841f4 diff --git a/hachu.c b/hachu.c new file mode 100644 index 0000000..e1b156a --- /dev/null +++ b/hachu.c @@ -0,0 +1,1626 @@ +/***********************************************************************/ +/* 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"); + } + } +