//////////////////////////////////////////////////////////////// // *** AllYourBoxAreBelongToUs *** // // // // Copyright (C) 2006 by William Little // // All rights reserved. // //////////////////////////////////////////////////////////////// #include #include #include #include #include //Pseudo-random number generator #define MYRANDMAX 0x7fffffffU #define myrand() ((unsigned int)((myrandseed=myrandseed*1684056135+342964307)>>33)) #define myseed(x) myrandseed=((unsigned long long)(x))<<33; unsigned long long myrandseed=1ULL<<33; //Defines #define MIN_DEPTH 2 //Complete searches of this depth even if time runs out #define MAX_STACK 150 #define HASH_SIZE 0x10000 //Thus must be a power of 2 #define EVALUATE() (Score*100+NimValue) //Evaluation as a macro, for speed //Structs struct HashElement { unsigned int Key; short Min, Max; //Inclusive short GoodMove, Depth; }; struct StackElement { short Alignment, Index, Mask, OldScore; }; //Classes class Engine { int Size[2], Score, NimValue, MovesRemaining; unsigned int HashNum[2]; unsigned char Board[2][9]; StackElement * const StackBottom; StackElement * Stack; const static short NimLookup[256], BitCount[256]; const static unsigned int HashMix[288]; unsigned static int HashChange[2][9][256][2]; static int HashReady; HashElement *Hash; clock_t StopTime; void ClearHash() { for(int i=0;i=3 && x<=8 && y>=3 && y<=8); Size[0]=x; Size[1]=y; int i; for(i=0;i<=x;i++) Board[0][i]=256-(1<MIN_DEPTH) if(clock()>=StopTime) return(0); if(f>=beta) min=f; else max=f; } while(minmyhash->Depth) { //We're more important, so stomp on the old entry myhash->Depth=depth; myhash->Key=HashNum[1]; myhash->Min=-10000; myhash->Max=10000; } if((myhash->Key==HashNum[1]) && (myhash->Depth==depth)) { if((myhash->Min+myeval)>=beta) return(myhash->Min+myeval); if((myhash->Max+myeval)Max+myeval); } bestmove=myhash->GoodMove; if(bestmove>0) //All valid moves will be greater than zero { const int alignment=(bestmove>>12)&1, index=(bestmove>>8)&15, mask=bestmove&255; if((Board[alignment][index]&mask)==0) { MakeMove(alignment,index,mask); int eval=-MTDfNegaMax(depth-1,1-beta); TakeBack(); if(depth>MIN_DEPTH) if(clock()>=StopTime) return(0); if(eval>besteval) besteval=eval; if(besteval>=beta) { if((myhash->Key==HashNum[1]) && (myhash->Depth==depth) && (besteval>(myhash->Min+myeval))) myhash->Min=besteval-myeval; assert(myhash->Max>=myhash->Min); return(besteval); } } } for(int alignment=0;alignment<=1;alignment++) for(int index=0;index<=Size[alignment];index++) { for(int a=1;a<=Size[1-alignment];a++) { for(int b=a-1;b>=0;b--) { int mask=(1<MIN_DEPTH) if(clock()>=StopTime) return(0); if(eval>besteval) { besteval=eval; bestmove=(alignment<<12)|(index<<8)|mask; } if(besteval>=beta) { if((myhash->Key==HashNum[1]) && (myhash->Depth==depth) && (besteval>(myhash->Min+myeval))) { myhash->Min=besteval-myeval; myhash->GoodMove=bestmove; } assert(myhash->Max>=myhash->Min); return(besteval); } } } } if((myhash->Key==HashNum[1]) && (myhash->Depth==depth)) { myhash->GoodMove=bestmove; if(besteval<(myhash->Max+myeval)) myhash->Max=besteval-myeval; }; assert(myhash->Max>=myhash->Min); return(besteval); } int RandomMTDfNegaMax(int depth, int beta) //No alpha needed //A modified version of MTDfNegaMax, used for only the first ply { if(depth==0 || MovesRemaining==0) return(EVALUATE()); HashElement * const myhash=Hash+(HashNum[0]&(HASH_SIZE-1)); const int myeval=EVALUATE(); int besteval=-20000; short bestmove; if(depth>myhash->Depth) { //We're more important, so stomp on the old entry myhash->Depth=depth; myhash->Key=HashNum[1]; myhash->Min=-10000; myhash->Max=10000; } if((myhash->Key==HashNum[1]) && (myhash->Depth==depth)) { if((myhash->Min+myeval)>=beta) return(myhash->Min+myeval); if((myhash->Max+myeval)Max+myeval); } bestmove=myhash->GoodMove; if(bestmove>0) //All valid moves will be greater than zero { const int alignment=(bestmove>>12)&1, index=(bestmove>>8)&15, mask=bestmove&255; if((Board[alignment][index]&mask)==0) { MakeMove(alignment,index,mask); int eval=-MTDfNegaMax(depth-1,1-beta); TakeBack(); if(depth>MIN_DEPTH) if(clock()>=StopTime) return(0); if(eval>besteval) besteval=eval; if(besteval>=beta) { if((myhash->Key==HashNum[1]) && (myhash->Depth==depth) && (besteval>(myhash->Min+myeval))) myhash->Min=besteval-myeval; assert(myhash->Max>=myhash->Min); return(besteval); } } } //Make a list of all valid moves which are different from bestmove int listlen=0; short * const movelist=new short[648]; assert(movelist); for(int alignment=0;alignment<=1;alignment++) for(int index=0;index<=Size[alignment];index++) { for(int a=1;a<=Size[1-alignment];a++) { for(int b=a-1;b>=0;b--) { int mask=(1<0;i--) { const int j=myrand()%(i+1); const int temp=movelist[i]; movelist[i]=movelist[j]; movelist[j]=temp; } while(listlen) { listlen--; MakeMove((movelist[listlen]>>12)&1,(movelist[listlen]>>8)&15, movelist[listlen]&255); int eval=-MTDfNegaMax(depth-1,1-beta); TakeBack(); if(depth>MIN_DEPTH) if(clock()>=StopTime) { delete [] movelist; return(0); }; if(eval>besteval) { besteval=eval; bestmove=movelist[listlen]; } if(besteval>=beta) { if((myhash->Key==HashNum[1]) && (myhash->Depth==depth) && (besteval>(myhash->Min+myeval))) { myhash->Min=besteval-myeval; myhash->GoodMove=bestmove; } assert(myhash->Max>=myhash->Min); delete [] movelist; return(besteval); } } if((myhash->Key==HashNum[1]) && (myhash->Depth==depth)) { myhash->GoodMove=bestmove; if(besteval<(myhash->Max+myeval)) myhash->Max=besteval-myeval; }; assert(myhash->Max>=myhash->Min); delete [] movelist; return(besteval); } public: Engine(const char *filename) { //Read in the input file, and get everything ready FILE *fp=fopen(filename,"rt"); assert(fp); int rc, sidetomove, width, height, playernum, playerscore; float timeremaining, mytime=-1; rc=fscanf(fp,"%d %d %d\n",&sidetomove,&height,&width); assert(rc==3); Initialize(width,height); for(int i=0;i<2;i++) { rc=fscanf(fp,"%d %d %f\n",&playernum,&playerscore,&timeremaining); assert(rc==3); if(playernum==sidetomove) mytime=timeremaining; } while(!feof(fp)) { int movetype; char buf1[5], buf2[5]; rc=fscanf(fp,"%d %2s %2s\n",&movetype,buf1,buf2); if(rc!=3) break; assert((buf1[2]==0) && (buf2[2]==0)); int x1=toupper(buf1[0])-'A', y1=buf1[1]-'1', x2=toupper(buf2[0])-'A', y2=buf2[1]-'1'; assert((x1>=0) && (x1<=width) && (x2>=0) && (x2<=width)); assert((y1>=0) && (y1<=height) && (y2>=0) && (y2<=height)); assert(((x1==x2) && (y10); if(MovesRemaining>12) StopTime=(clock_t)(CLOCKS_PER_SEC*(mytime-2)/((MovesRemaining-11)/2)); else StopTime=(clock_t)(CLOCKS_PER_SEC*mytime/2); } ~Engine() { delete [] Hash; delete [] StackBottom; } void MakeMove(int alignment, int index, int mask) { assert((Board[alignment][index]&mask)==0); assert(Stack-StackBottomAlignment=alignment; Stack->Index=index; Stack->Mask=mask; Stack->OldScore=Score; Stack++; MovesRemaining-=BitCount[mask]; NimValue^=NimLookup[Board[alignment][index]]; NimValue^=NimLookup[Board[alignment][index]|=mask]; HashNum[0]^=HashChange[alignment][index][mask][0]; HashNum[1]^=HashChange[alignment][index][mask][1]; for(int i=Size[1-alignment]-1;i>=0;i--) if(mask&(1<0) if((Board[alignment][index-1]&(1<=0 && y2<=Size[1] && y2>y1 && x1>=0 && x1<=Size[0]); MakeMove(0,x1,(1<=0 && x2<=Size[0] && x2>x1 && y1>=0 && y1<=Size[1]); MakeMove(1,y1,(1<>12)&1, index=(move>>8)&15, mask=move&255; MakeMove(alignment,index,mask); } void TakeBack() { assert(Stack>StackBottom); Stack--; HashNum[0]^=HashChange[Stack->Alignment][Stack->Index][Stack->Mask][0]; HashNum[1]^=HashChange[Stack->Alignment][Stack->Index][Stack->Mask][1]; NimValue^=NimLookup[Board[Stack->Alignment][Stack->Index]]; NimValue^=NimLookup[Board[Stack->Alignment][Stack->Index]^=Stack->Mask]; MovesRemaining+=BitCount[Stack->Mask]; Score=Stack->OldScore; assert((Board[Stack->Alignment][Stack->Index]&Stack->Mask)==0); } int GetHashMove() { HashElement * const myhash=Hash+(HashNum[0]&(HASH_SIZE-1)); assert((myhash->Key==HashNum[1]) && (myhash->GoodMove>0)); return(myhash->GoodMove); } int ChooseMoveMTDf(int depth) { ClearHash(); int guess=EVALUATE(); int bestmove=-1; for(int d=1;d<=depth;d++) { guess=MTDf(guess,d); if(d>MIN_DEPTH) if(clock()>=StopTime) break; bestmove=GetHashMove(); } assert(bestmove>0); return(bestmove); } void PrintMove(int move) { const int alignment=(move>>12)&1, index=(move>>8)&15; int mask=move&255, low=0; while(!(mask&1)) { low++; mask>>=1; } const int high=low+BitCount[mask]; int x1, y1, x2, y2; if(alignment) { x1=low; x2=high; y1=y2=index; } else { y1=low; y2=high; x1=x2=index; } printf("%c%c %c%c\n",'A'+x1,'1'+y1,'A'+x2,'1'+y2); } }; //This is a lookup table used to rapidly update NimValue const short Engine::NimLookup[256]= { 8,7,7,6,7,4,4,5,7,6,4,5,6,5,5,4,7,0,0,1,0,3,3,2,0,1,3,2,1,2,2,3, 7,6,0,1,2,1,1,0,0,1,3,2,1,2,2,3,6,1,1,0,1,2,2,3,1,0,2,3,0,3,3,2, 7,4,4,5,0,3,3,2,0,1,3,2,1,2,2,3,4,3,3,2,3,0,0,1,3,2,0,1,2,1,1,0, 4,5,3,2,1,2,2,3,3,2,0,1,2,1,1,0,5,2,2,3,2,1,1,0,2,3,1,0,3,0,0,1, 7,6,4,5,6,5,5,4,0,1,3,2,1,2,2,3,6,1,1,0,1,2,2,3,1,0,2,3,0,3,3,2, 4,5,3,2,1,2,2,3,3,2,0,1,2,1,1,0,5,2,2,3,2,1,1,0,2,3,1,0,3,0,0,1, 6,5,5,4,1,2,2,3,1,0,2,3,0,3,3,2,5,2,2,3,2,1,1,0,2,3,1,0,3,0,0,1, 5,4,2,3,0,3,3,2,2,3,1,0,3,0,0,1,4,3,3,2,3,0,0,1,3,2,0,1,2,1,1,0 }; //This table just lists the number of on bits in a byte. It could be //dynamically calculated, but I figured this would be faster. const short Engine::BitCount[256]= { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8 }; //This is a list of high quality random numbers, i.e. NOT pseudo-random const unsigned int Engine::HashMix[288]= { 0xf818b2f5,0xefdbd7dd,0x4a62e650,0xa375a84d,0xaffbfa14,0x5c7de49a, 0xc5a5afdc,0xd760d248,0x2b658b2f,0x6c46c6d7,0x7bb3644e,0xc6b672a0, 0x07172241,0x4441683b,0x28c9ed64,0xfb995390,0x0494dab7,0x09894fd1, 0x54eb399f,0xd83727f4,0x9fae3db9,0x26591667,0x1aa59f94,0xbc762035, 0xdfd89758,0xf549a821,0x66b800ff,0x6dfd4e3e,0x24e1490b,0xfea949f6, 0x0940b7f4,0x69d1bc29,0x8e6d9fde,0xd4e28a36,0x8b3da267,0x9e277243, 0x10885db1,0xd63f1355,0xf2415f65,0xf695d022,0xcddb9506,0x9e1b25c9, 0x37a2dcd1,0xf88e86f7,0x824c2732,0x508d47c5,0xf7dc77ad,0x964c0fad, 0x5eeb49bd,0x0e5d6b85,0xbeb3c8b6,0xdedfb9db,0x5a73bc02,0x69a1bb6d, 0x5937ebeb,0x34ce2ab2,0x91b38365,0x3b149dd6,0xc6709c28,0x9d70abd8, 0xb66444f6,0x2d1f1f02,0x85c225ac,0xa525b118,0xce164851,0x5f22d74a, 0x81f2e7df,0x58223f90,0x6f57351f,0xa6a4007b,0x52a60cbe,0xe084f7cf, 0x7e841f9b,0xb79a5895,0xea8f7624,0x90f9b60b,0x6d151e39,0x2baa5426, 0x79723abe,0x31e071f5,0x8de2ea15,0x25d5dc39,0x6d9aba10,0x62c697d8, 0xd54fcfda,0xbe377ea3,0x5f4ceb31,0xd966387a,0x19a8133d,0xabde8031, 0x6b542dab,0xe20d1f1d,0x5caf31e6,0xe010f042,0x97ae0838,0x2afea796, 0xbb9b41af,0xd39aa80b,0x973a6cd8,0x2d9b78bb,0x166d6c3c,0x5110dec8, 0x9f2e8c79,0x612a2c4a,0x06a29479,0x4e5b50c8,0x048bc924,0x425b12c2, 0x51b12e1e,0x4bc62dbf,0x79424a99,0xe6dd5ec6,0x8e9cc96d,0xe9103ea6, 0x1ea85b40,0x3feaf3d6,0x05ede71f,0xba9aa4a8,0xd37aa78d,0x41c472aa, 0x499f3a93,0x946e4f64,0xd905cb5e,0x89002981,0x76415732,0xef9928b9, 0xa5fc8966,0xde0e0e0a,0x4a9ef023,0xf50186bd,0x08e6c7c1,0xd68e6db3, 0x4f0d3f4c,0xb6567472,0xdf9263a2,0x29e4e487,0x3d3dee14,0xabe0c148, 0x9703c392,0x274716ee,0x09db4a8f,0x4e616122,0xc1f18a06,0xde67c05c, 0xd18e6f07,0x8a3ae2bb,0xa6d060b3,0x702a26a3,0x77220d94,0xab1806d9, 0x39f0f3cd,0x79de97bc,0x07a1e23b,0xde42776c,0x80aaf597,0xacd99cb1, 0xd4671051,0xe3345a78,0x5c812ead,0x42be8031,0xc3a95772,0x8620652c, 0x1e5adf16,0xb1c8b97d,0x6a7459a7,0x687848eb,0x56a37f04,0xc55dc871, 0x1ab1c595,0x563a88be,0x6e304e26,0x481211fe,0x06065d00,0x7831c276, 0x0994db7e,0xd665c7f5,0x232a34a5,0xdfa695ae,0x880ff230,0xb1f0e47f, 0xf85848f5,0x80dac07b,0x7d90a7e5,0x5822a2ee,0xa59f1c89,0x1cc813c8, 0x14ef00bd,0x0c29ebf1,0xc8daddfd,0x0d782657,0x88ce4d70,0xcf193f42, 0xbdb9a2e9,0x9add9e27,0x23c584bf,0x26213c90,0xcb03a9cf,0xe7b1e8e7, 0xba3fd50a,0x748fa091,0x59edd789,0x95dd8287,0xeb3041b0,0xcd1bceac, 0xdc940a37,0x8bace33e,0xaf85c9bf,0x029a45c8,0x46a45e9b,0x994784c1, 0x6843f6cb,0x21326195,0x3d538cfd,0x7757935e,0x5453ac6f,0x66b1cc04, 0x1e5c087f,0xf4823699,0x87d4dfa2,0x6323c65c,0xb4839549,0x39cfa283, 0x9228a102,0x3baaf106,0x8b901263,0x31da94b4,0x263a29c5,0x7dea139d, 0xc237b529,0xdb27bcfd,0xc9bd1eca,0x6b67dc11,0x4a576a78,0x515e874f, 0xe0e6a33b,0xd6dccabc,0xe59f7847,0x8c3be1ea,0xfd93b8bf,0x94d7f030, 0x05b55131,0x130b1671,0x0ad05477,0x69569352,0x578fa619,0x2f8f6384, 0x4e4f14dc,0x192e129a,0x5dc39f02,0x48512563,0x17459ca5,0x9b75aa88, 0xbdf834d8,0xac3cdd14,0x59af3e19,0x241f372f,0x5572e16b,0xe0c10632, 0x0f1a5e73,0x6dfbca20,0x3d392432,0x0ff9ef40,0x1d0028b1,0xfaf153f5, 0x730e5f54,0x3360abfe,0xcd13701f,0x4fed9bd9,0xb5ee4374,0x3ac9d318, 0xc0558c44,0xf2632035,0x86350a6a,0xc1c66d80,0x8b05f0b0,0x93a492cd, 0x369f3a8b,0xd662fcfb,0x2c2e6de8,0x235ff7f5,0x12d65412,0x4a9b22cd, 0x47475c72,0x7dd4b3ee,0x059172ec,0xf538baa3,0xf5f89740,0x3b86602a }; int Engine::HashReady=0; unsigned int Engine::HashChange[2][9][256][2]; int main(int argc, char *argv[]) { assert(argc==2); myseed(time(0)); Engine thunderdome(argv[1]); //Two men enter, one man leaves thunderdome.PrintMove(thunderdome.ChooseMoveMTDf(30)); return(0); }