-/***********************************************************************/\r
-/* HaChu */\r
-/* A WinBoard engine for large (dropless) Shogi variants by H.G.Muller */\r
-/* The engine is based on incremental updating of an attack map and */\r
-/* mobility scores, since the effort in this only grows proportional */\r
-/* to board edge length, rather than board area. */\r
-/***********************************************************************/\r
+/**************************************************************************/\r
+/* HaChu */\r
+/* A WinBoard engine for Chu Shogi (and some related games) by H.G.Muller */\r
+/**************************************************************************/\r
+/* This source code is released in the public domain */\r
+/**************************************************************************/\r
\r
// TODO:\r
// in GenCapts we do not generate jumps of more than two squares yet\r
// promotions by pieces with Lion power stepping in & out the zone in same turn\r
// promotion on capture\r
\r
-#define VERSION "0.15"\r
+#define VERSION "0.16 k+ky"\r
\r
#define PATH level==0 /*|| path[0] == 0x3490a && (level==1 || path[1] == 0x285b3 && (level == 2 || path[2] == 0x8710f && (level == 3 /*|| path[3] == 0x3e865 && (level == 4 || path[4] == 0x4b865 && (level == 5)))))*/\r
//define PATH 0\r
#define LIONTRAP\r
#define WINGS\r
#define KINGSAFETY\r
+#define KSHIELD\r
#define XFORTRESS\r
#define PAWNBLOCK\r
+#define KYLIN 100 /* extra end-game value of Kylin for promotability */\r
+#define PROMO 0 /* extra bonus for 'vertical' piece when it actually promotes (diagonal pieces get half) */\r
\r
#include <stdio.h>\r
#include <stdlib.h>\r
\r
int epList[96], ep2List[96], toList[96], reverse[96]; // decoding tables for double and triple moves\r
int kingStep[10], knightStep[10]; // raw tables for step vectors (indexed as -1 .. 8)\r
+int neighbors[9]; // similar to kingStep, but starts with null-step\r
char fireFlags[10]; // flags for Fire-Demon presence (last two are dummies, which stay 0, for compactify)\r
#define kStep (kingStep+1)\r
#define nStep (knightStep+1)\r
char distance[2*BSIZE]; // distance table\r
char promoBoard[BSIZE]; // flags to indicate promotion zones\r
char rawFire[BSIZE+2*BWMAX]; // flags to indicate squares controlled by Fire Demons\r
-signed char PST[4*BSIZE];\r
+signed char PST[5*BSIZE];\r
\r
#define board (rawBoard + 6*BHMAX + 3)\r
#define fireBoard (rawFire + BWMAX + 1)\r
EasyProm (signed char *r)\r
{\r
int i;\r
- return r[0] == X || r[1] == X || r[7] == X;\r
+ if(r[0] == X) return 30 + PROMO*((unsigned int)(r[1] | r[2] | r[3] | r[5] | r[6] | r[7]) <= 1);\r
+ if(r[1] == X || r[7] == X) return 30 + PROMO/2;\r
+ return 0;\r
}\r
\r
int\r
}\r
for(i=0; i<BH; i++) for(j=0; j<BH; j++) board[BW*i+j] = EMPTY;\r
for(i=WHITE+2; i<=last[WHITE]; i+=2) if(p[i].pos != ABSENT) {\r
- if((j = p[i].promo) > 0 && p[i].promoGain)\r
- p[i].promoGain = (p[j].value - p[i].value - 30)*1.25, p[i].value = p[j].value - 30;\r
+ int g = p[i].promoGain;\r
+ if((j = p[i].promo) > 0 && g)\r
+ p[i].promoGain = (p[j].value - p[i].value - g)*1.25, p[i].value = p[j].value - g;\r
else p[i].promoGain = 0;\r
+ if(i == kylin[WHITE]) p[i].promoGain = 1.25*KYLIN, p[i].value += KYLIN;\r
if(j > 0 && p[i].pst == BH) p[i].pst = 3*BW*BH; // use white pre-prom bonus\r
board[p[i].pos] = i;\r
rootEval += p[i].value + PST[p[i].pst + p[i].pos];\r
filling += p[i].bulk;\r
} else p[i].promoGain = 0;\r
for(i=BLACK+2; i<=last[BLACK]; i+=2) if(p[i].pos != ABSENT) {\r
- if((j = p[i].promo) > 0 && p[i].promoGain)\r
- p[i].promoGain = (p[j].value - p[i].value - 30)*1.25, p[i].value = p[j].value - 30;\r
+ int g = p[i].promoGain;\r
+ if((j = p[i].promo) > 0 && g)\r
+ p[i].promoGain = (p[j].value - p[i].value - g)*1.25, p[i].value = p[j].value - g;\r
else p[i].promoGain = 0;\r
+ if(i == kylin[BLACK]) p[i].promoGain = 1.25*KYLIN, p[i].value += KYLIN;\r
if(j > 0 && p[i].pst == BH) p[i].pst = 3*BW*BH + BH; // use black pre-prom bonus\r
+ if(j > 0 && p[i].pst == bsize) p[i].pst = 4*BW*BH; // use white pre-prom bonus\r
board[p[i].pos] = i;\r
rootEval -= p[i].value + PST[p[i].pst + p[i].pos];\r
promoDelta -= p[i].promoGain;\r
kStep[i] = STEP(direction[i&7].x, direction[i&7].y); // King\r
nStep[i] = STEP(direction[(i&7)+8].x, direction[(i&7)+8].y); // Knight\r
}\r
+ for(i=0; i<8; i++) neighbors[i+1] = kStep[i];\r
\r
for(i=0; i<8; i++) { // Lion double-move decoding tables\r
for(j=0; j<8; j++) {\r
for(j=0; j<BH; j++) {\r
for(i=0; i<BH; i++) {\r
int s = BW*i + j, d = BH*(BH-2) - abs(2*i - BH + 1)*(BH-1) - (2*j - BH + 1)*(2*j - BH + 1);\r
- PST[s] = 2*(i==0 | i==BH-1) + (i==1 | i==BH-2); // last-rank markers in null table\r
- PST[BH+s] = d/4 - (i == 0 || i == BH-1 ? 5 : 0) - (j == 0 || j == BH-1 ? 5 : 0)\r
- + 2*(i==zone || i==BH-zone-1); // stepper centralization\r
- PST[BH*BW+s] = d/6; // double-stepper centralization\r
- PST[BH*BW+BH+s] = d/12; // slider centralization\r
+ PST[s] = 2*(i==0 | i==BH-1) + (i==1 | i==BH-2); // last-rank markers in null table\r
+ PST[BH+s] = d/4 - (i < 2 || i > BH-3 ? 3 : 0) - (j == 0 || j == BH-1 ? 5 : 0)\r
+ + 3*(i==zone || i==BH-zone-1); // stepper centralization\r
+ PST[BH*BW+s] = d/6; // double-stepper centralization\r
+ PST[BH*BW+BH+s] = d/12 - 5*(i==BH/2 || i==(BH-1)/2); // slider centralization\r
PST[2*BH*BW+s] = j < 3 || j > BH-4 ? (i < 3 ? 7 : i == 3 ? 4 : i == 4 ? 2 : 0) : 0;\r
PST[2*BH*BW+BH+s] = ((BH-1)*(BH-1) - (2*i - BH + 1)*(2*i - BH + 1) - (2*j - BH + 1)*(2*j - BH + 1))/6;\r
- PST[3*BH*BW+s] = PST[3*BH*BW+BH+s] = PST[BH+s]; // as stepper, but with pre-promotion bonus W/B\r
+ PST[3*BH*BW+s] = PST[3*BH*BW+BH+s] = PST[BH+s]; // as stepper, but with pre-promotion bonus W/B\r
+ PST[4*BH*BW+s] = PST[BW*BH+s]; // as jumper, but with pre-promotion bonus B\r
+ PST[4*BH*BW+BH+s] = BW*(zone - 1 - i); // board step to enter promo zone black\r
}\r
- if(zone > 1) PST[3*BW*BH+BW*(BH-1-zone) + j] += 10, PST[3*BW*BH+BH + BW*zone + j] += 10;\r
+ if(zone > 0) PST[3*BW*BH+BW*(BH-1-zone) + j] += 10, PST[3*BW*BH+BH + BW*zone + j] += 10;\r
+#if KYLIN\r
+ // pre-promotion bonuses for jumpers\r
+ if(zone > 0) PST[BW*BH + BW*(BH-2-zone) + j] = PST[4*BW*BH + BW*(zone+1) + j] = 100,\r
+ PST[BW*BH + BW*(BH-1-zone) + j] = PST[4*BW*BH + BW*zone + j] = 200;\r
+#endif\r
}\r
\r
p[EDGE].qval = 5; // tenjiku jump-capturer sentinel\r
mobilityScore -= MapOneColor(0, last[BLACK], map);\r
}\r
\r
-void\r
-Connect (int sqr, int piece, int dir)\r
-{ // scan to both sides along ray to elongate attacks from there, and remove our own attacks on there, if needed\r
- int x, step = kStep[dir], r1 = p[piece].range[dir], r2 = p[piece].range[dir+4], r3, r4, piece1, piece2;\r
- int d1, d2, r, y, c;\r
-\r
- if((attacks[2*sqr] + attacks[2*sqr+1]) & attackMask[dir]) { // there are incoming attack(s) from 'behind'\r
- x = sqr;\r
- while(board[x-=step] == EMPTY); // in any case, scan to attacker, to see where / what it is\r
- d1 = dist[x-sqr]; piece1 = board[x];\r
- attacks[2*x + stm] -= -(d1 <= r2) & one[dir+4]; // remove our attack on it if in-range\r
- if((attacks[2*sqr] + attacks[2*sqr+1]) & attackMask[dir+4]) { // there are also incoming attack(s) from 'ahead'\r
-\r
- y = sqr;\r
- while(board[y+=step] == EMPTY); // also always scan to that one to see what it is\r
- d2 = dist[y-sqr]; piece2 = board[y];\r
- attacks[2*y+stm] -= -(d2 <= r1) & one[dir]; // remove our attack on it if in-range\r
- // we have two pieces now shooting at each other. See how far they get.\r
- if(d1 + d2 <= (r3 = p[piece1].range[dir])) { // 1 hits 2\r
- attacks[2*y + (piece1 & WHITE)] += one[dir]; // count attack\r
- UPDATE_MOBILITY(piece1, d2);\r
- } else UPDATE_MOBILITY(piece1, r3 - d1); // does not connect, but could still gain mobility\r
- if(d1 + d2 <= (r4 = p[piece2].range[dir+4])) { // 2 hits 1\r
- attacks[2*x + (piece2 & WHITE)] += one[dir+4]; // count attack\r
- UPDATE_MOBILITY(piece2, d1);\r
- } else UPDATE_MOBILITY(piece2, r4 - d2); // does not connect, but could still gain mobility\r
- // if r1 or r2<0, moves typically jump, and thus cannot be unblocked. Exceptions are FF and BS distant moves.\r
- // test for d1+d2 > 2 && rN == F && d== 3 or rN == S\r
- if(d1 <= 2) { // could be jump interactions\r
- if(d1 == 2) {\r
- if(r2 <= J) attacks[2*x + stm] -= one[dir+4];\r
- if(r1 <= J) attacks[2*y + stm] -= one[dir];\r
- } else { // d1 == 1\r
- if(r2 < J) attacks[2*x + stm] -= one[dir+4];\r
- if(r1 < J) attacks[2*y + stm] -= one[dir];\r
- if(board[x-step] != EMPTY && board[x-step] != EDGE)\r
- attacks[2*(x-step) + stm] -= one[dir+4];\r
- }\r
- }\r
-\r
- } else { // we were only attacked from behind\r
-\r
- r = (r2 = p[piece1].range[dir]) - d1;\r
- if(r < 0 || c > one[dir+4]) { // Oops! This was not our attacker, or not the only one. There must be a jump attack from even further behind!\r
- // for now, forget jumpers\r
- }\r
- y = sqr; \r
- while(r--)\r
- if(board[y+=step] != EMPTY) {\r
- d2 = dist[y-sqr]; piece2 = board[y];\r
- if(piece2 != EDGE) { // extended move hits a piece\r
- attacks[2*y + (piece1 & WHITE)] += one[dir]; // count attack\r
- attacks[2*y + stm] -= -(d2 <= r1) & one[dir]; // remove our own attack on it, if in-range\r
- }\r
- UPDATE_MOBILITY(piece1, d2); // count extra mobility even if we hit edge\r
- return;\r
- }\r
- // we hit nothing with the extended move of the attacker behind us.\r
- UPDATE_MOBILITY(piece1, r2 - d1);\r
- r = r1 - r2 + d1; // extra squares covered by mover\r
- while(r-- > 0)\r
- if(board[y+=step] != EMPTY) {\r
- d2 = dist[y-sqr]; piece2 = board[y];\r
- if(piece2 != EDGE) { // extended move hits a piece\r
- attacks[2*y + stm] -= one[dir]; // count attack\r
- }\r
- return;\r
- }\r
- }\r
- // if r2<0 we should again test for F and S moves\r
-\r
- } else // no incoming attack from behind\r
- if(c = (attacks[2*sqr] + attacks[2*sqr+1]) & attackMask[dir+4]) { // but incoming attack(s) from 'ahead'\r
-\r
- y = sqr; while(board[y+=step]); // locate attacker\r
- d2 = dist[y-sqr]; piece2 = board[y];\r
- attacks[2*y + stm] -= -(d2 <= r1) & one[dir]; // remove our attack on it if in-range\r
- r = (r1 = p[piece1].range[dir]) - d2;\r
- 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!\r
- // for now, forget jumpers\r
- }\r
- x = sqr;\r
- while(r--)\r
- if(board[x-=step] != EMPTY) {\r
- d1 = dist[x-sqr]; piece1 = board[x];\r
- if(piece1 != EDGE) { // extended move hits a piece\r
- attacks[2*x + (piece2 & WHITE)] += one[dir+4]; // count attack\r
- attacks[2*x + stm] -= -(d1 <= r2) & one[dir+4]; // remove our own attack on it, if in-range\r
- }\r
- UPDATE_MOBILITY(piece2, d1); // count extra mobility even if we hit edge\r
- return;\r
- }\r
- // we hit nothing with the extended move of the attacker behind us.\r
- UPDATE_MOBILITY(piece2, r2 - d1);\r
- r = r2 - r1 + d2; // extra squares covered by mover\r
- while(r-- > 0)\r
- if(board[x-=step] != EMPTY) {\r
- d1 = dist[x-sqr]; piece1 = board[x];\r
- if(piece1 != EDGE) { // extended move hits a piece\r
- attacks[2*x + stm] -= one[dir+4]; // count attack\r
- }\r
- return;\r
- }\r
-\r
- } else { // no incoming attacks from either side. Only delete attacks of mover on others\r
-\r
- x = sqr;\r
- while(r1--)\r
- if(board[x+=step] != EMPTY) { // piece found that we attacked\r
- attacks[2*x + stm] -= one[dir]; // decrement attacks along that direction\r
- break;\r
- }\r
-\r
- x = sqr;\r
- while(r2--)\r
- if(board[x-=step] != EMPTY) { // piece found that we attacked\r
- attacks[2*x + stm] -= one[dir+4]; // decrement attacks along opposite direction\r
- break;\r
- }\r
-\r
- }\r
-}\r
-\r
-inline int\r
-Hit (int r, int d)\r
-{ // test if move with range r reaches over (un-obstructed) distance d\r
- if(r < 0) switch(r) {\r
- case J: return (d == 2);\r
- case D:\r
- case L: return (d <= 2);\r
- case T:\r
- case F: return (d <= 3);\r
- case S: return 1;\r
- default: return 0;\r
- } else return (d <= r);\r
- return 0; // not reached\r
-}\r
-\r
-void\r
-Disconnect (int sqr, int piece, int dir)\r
-{\r
- int x = sqr, step = kStep[dir], piece1, piece2, d1, d2, r1, r2, y;\r
- while( board[x+=step] == EMPTY );\r
- piece1 = board[x];\r
- if(piece1 != EDGE) { // x has hit a piece\r
- d1 = dist[x-sqr];\r
- r1 = p[piece1].range[dir+4];\r
- y = sqr; while( board[y-=step] == EMPTY );\r
- piece2 = board[y];\r
- if(piece2 != EDGE) { // both ends of the ray hit a piece\r
- d2 = dist[y-sqr];\r
- r2 = p[piece2].range[dir];\r
- if(r1 >= d1) { // piece1 hits us\r
- attacks[2*sqr + (piece1 & WHITE)] += one[dir+4];\r
- if(r1 >= d1 + d2) // was hitting piece2 before, now blocked\r
- attacks[2*y + (piece1 & WHITE)] -= one[dir+4];\r
- }\r
- if(r2 >= d2) { // piece2 hits us\r
- attacks[2*sqr + (piece2 & WHITE)] += one[dir];\r
- if(r2 >= d1 + d2) // was hitting piece1 before, now blocked\r
- attacks[2*x + (piece2 & WHITE)] -= one[dir];\r
- }\r
- if( Hit(p[piece].range[dir], d1) )\r
- attacks[2*sqr + stm] += one[dir];\r
- if( Hit(p[piece].range[dir+4], d2) )\r
- attacks[2*sqr + stm] += one[dir+4];\r
- return;\r
- }\r
- } else {\r
- x = sqr; while( board[x-=step] == EMPTY );\r
- piece1 = board[x];\r
- if(piece1 == EDGE) return; // ray empty on both sides\r
- d1 = dist[x-sqr];\r
- r1 = p[piece1].range[dir];\r
- dir += 4;\r
- }\r
- // we only get here if one side looks to the board edge\r
- if(r1 >= d1) // piece1 hits us\r
- attacks[2*sqr + (piece1 & WHITE)] += one[dir^4];\r
- if( Hit(p[piece].range[dir], d1) )\r
- attacks[2*sqr + stm] += one[dir];\r
-}\r
-\r
-void\r
-Occupy (int sqr)\r
-{ // determines attacks on square and blocking when a piece lands on an empty square\r
- int i;\r
- for(i=0; i<4; i++) {\r
- Disconnect(sqr, board[sqr], i);\r
- }\r
-}\r
-\r
-void\r
-Evacuate (int sqr, int piece)\r
-{ // determines change in attacks on neighbors due to unblocking and mover when the mentioned piece vacates the given square\r
- int i;\r
- for(i=0; i<4; i++) Connect(sqr, piece, i);\r
-}\r
-\r
int\r
MakeMove(Move m, UndoInfo *u)\r
{\r
}\r
\r
int\r
+Surround (int stm, int king, int start)\r
+{\r
+ int i, s=0;\r
+ for(i=start; i<9; i++) {\r
+ int v, piece, sq = king + neighbors[i];\r
+ if((piece = board[sq]) == EDGE || !piece || piece&1^stm) continue;\r
+ if(p[piece].promoGain) continue;\r
+ v = p[piece].value;\r
+ s += -(v > 70) & v;\r
+ }\r
+ return (s > 512 ? 512 : s);\r
+}\r
+\r
+int\r
Ftest (int side)\r
{\r
int lion = ABSENT, king;\r
if(wLion != ABSENT) f -= Fortress(-BW, bKing, wLion);\r
score += (filling < 96 ? f : f*(224 - filling) >> 7); // build up slowly\r
# endif\r
+\r
+# ifdef KSHIELD\r
+ score += Surround(WHITE, wKing, 1) - Surround(BLACK, bKing, 1) >> 3;\r
+# endif\r
+\r
#endif\r
\r
-#ifdef KYLIN\r
- // bonus for having Kylin in late end-game, where it could promote to Lion\r
- if((filling < 64) {\r
- if((wLion = kylin[WHITE]) && p[wLion].pos != ABSENT)\r
- score += (64 - filling)*kylinProm[wLion];\r
- if((filling < 64 && (bLion = kylin[BLACK]) && p[bLion].pos != ABSENT)\r
- score -= (64 - filling)*kylinProm[bLion];\r
+#if KYLIN\r
+ // bonus for having Kylin in end-game, where it could promote to Lion\r
+ // depends on board population, defenders around zone entry and proximity to zone\r
+ if(filling < 128) {\r
+ int sq;\r
+ if((wLion = kylin[WHITE]) && (sq = p[wLion].pos) != ABSENT) {\r
+ int anchor = sq - PST[5*BW*BH - 1 - sq];\r
+ score += (512 - Surround(BLACK, anchor, 0))*(128 - filling)*PST[p[wLion].pst + sq] >> 15;\r
+ }\r
+ if((bLion = kylin[BLACK]) && (sq = p[bLion].pos) != ABSENT) {\r
+ int anchor = sq + PST[4*BW*BH + BH + sq];\r
+ score -= (512 - Surround(WHITE, anchor, 0))*(128 - filling)*PST[p[bLion].pst + sq] >> 15;\r
+ }\r
}\r
#endif\r
\r
#ifdef PAWNBLOCK\r
// penalty for blocking own P or GB: 20 by slider, 10 by other, but 50 if only retreat mode is straight back\r
for(i=last[WHITE]; i > 1 && p[i].value<=50; i-=2) {\r
- if((f = p[i].pos) != ABSENT && (j = board[f + BW])&1) // P present, square before it white (odd) piece\r
- score -= 10 + 10*(p[j].promoGain > 0) + 30*!(p[j].range[3] || p[j].range[5] || p[j].value==50);\r
+ if((f = p[i].pos) != ABSENT) { // P present,\r
+ if((j = board[f + BW])&1) // square before it white (odd) piece\r
+ score -= 10 + 10*(p[j].promoGain > 0) + 30*!(p[j].range[3] || p[j].range[5] || p[j].value==50);\r
+ if((j = board[f - BW])&1) // square behind it white (odd) piece\r
+ score += 7*(p[j].promoGain == 0 & p[j].value<=151);\r
+ }\r
}\r
for(i=last[BLACK]; i > 1 && p[i].value<=50; i-=2) {\r
- if((f = p[i].pos) != ABSENT && (j = board[f - BW]) && !(j&1)) // P present, square before non-empty and even (black)\r
- score += 10 + 10*(p[j].promoGain > 0) + 30*!(p[j].range[1] || p[j].range[7] || p[j].value==50);\r
+ if((f = p[i].pos) != ABSENT) { // P present,\r
+ if((j = board[f - BW]) && !(j&1)) // square before non-empty and even (black)\r
+ score += 10 + 10*(p[j].promoGain > 0) + 30*!(p[j].range[1] || p[j].range[7] || p[j].value==50);\r
+ if((j = board[f + BW]) && !(j&1)) // square behind non-empty and even (black)\r
+ score -= 7*(p[j].promoGain == 0 & p[j].value<=151);\r
+ }\r
}\r
#endif\r
\r
postThinking--; repCnt = 0; tlim1 = tlim2 = tlim3 = 1e8; abortFlag = msp = 0;\r
Search(-INF-1, INF+1, 0, QSdepth+1, 0, sup1 & ~PROMOTE, sup2, INF);\r
postThinking++;\r
+\r
listStart = retFirst; listEnd = msp = retMSP;\r
}\r
\r