home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Dream 57
/
Amiga_Dream_57.iso
/
Amiga
/
Jeux
/
Reflexion
/
Crafty-15.19.lha
/
crafty-15.19
/
src
/
book.c
< prev
next >
Wrap
C/C++ Source or Header
|
1998-09-16
|
50KB
|
1,252 lines
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "chess.h"
#include "data.h"
#if defined(UNIX)
# include <unistd.h>
#endif
/* last modified 08/26/98 */
/*
********************************************************************************
* *
* Book() is used to determine if the current position is in the book data- *
* base. it simply takes the set of moves produced by root_moves() and then *
* tries each position's hash key to see if it can be found in the data- *
* base. if so, such a move represents a "book move." the set of flags is *
* used to decide on a sub-set of moves to be used as the "book move pool" *
* from which a move is chosen randomly. *
* *
* the format of a book position is as follows: *
* *
* 64 bits: hash key for this position. *
* *
* 8 bits: flag bits defined as follows: *
* *
* 0000 0001 ?? flagged move (0001) (0x01) *
* 0000 0010 ? flagged move (0002) (0x02) *
* 0000 0100 = flagged move (0004) (0x04) *
* 0000 1000 ! flagged move (0010) (0x08) *
* 0001 0000 !! flagged move (0020) (0x10) *
* 0010 0000 black won at least 1 game (0040) (0x20) *
* 0100 0000 at least one game was drawn (0100) (0x40) *
* 1000 0000 white won at least 1 game (0200) (0x80) *
* *
* 24 bits: number of games this move was played. *
* *
* 32 bits: learned value (floating point). *
* *
* (note: counts are normalized to a max of 255. *
* *
********************************************************************************
*/
#define BAD_MOVE 002
#define GOOD_MOVE 010
int Book(TREE *tree, int wtm, int root_list_done) {
static int book_moves[200];
static BOOK_POSITION start_moves[200];
static int selected[200];
static int selected_order_played[200], selected_value[200];
static int selected_status[200], selected_percent[200], book_development[200];
static int bs_played[200], bs_percent[200];
static int book_status[200], evaluations[200], bs_learn[200];
static float bs_value[200], total_value;
int m1_status, forced=0, total_percent;
float tempr;
int done, i, j, last_move, temp, which, minlv=999999, maxlv=-999999;
int maxp=-999999, minev=999999, maxev=-999999;
int *mv, mp, value, np;
int cluster, scluster, test;
BITBOARD temp_hash_key, common;
int key, nmoves, num_selected, st;
int percent_played, total_played, total_moves, smoves;
int distribution;
int initial_development;
char *whisper_p;
/*
----------------------------------------------------------
| |
| if we have been out of book for several moves, return |
| and start the normal tree search. |
| |
----------------------------------------------------------
*/
if (moves_out_of_book > 3) return(0);
/*
----------------------------------------------------------
| |
| position is known, read the start book file and save |
| each move found. these will be used later to augment |
| the flags in the normal book to offer better control. |
| |
----------------------------------------------------------
*/
if (!root_list_done) RootMoveList(wtm);
test=HashKey>>49;
smoves=0;
if (books_file) {
fseek(books_file,test*sizeof(int),SEEK_SET);
fread(&key,sizeof(int),1,books_file);
if (key > 0) {
fseek(books_file,key,SEEK_SET);
fread(&scluster,sizeof(int),1,books_file);
fread(books_buffer,sizeof(BOOK_POSITION),scluster,books_file);
for (mv=tree->last[0];mv<tree->last[1];mv++) {
common=And(HashKey,mask_16);
MakeMove(tree,1,*mv,wtm);
if (RepetitionCheck(tree,2,ChangeSide(wtm))) {
UnMakeMove(tree,1,*mv,wtm);
return(0);
}
temp_hash_key=Xor(HashKey,wtm_random[wtm]);
temp_hash_key=Or(And(temp_hash_key,Compl(mask_16)),common);
for (i=0;i<scluster;i++)
if (!Xor(temp_hash_key,books_buffer[i].position)) {
start_moves[smoves++]=books_buffer[i];
break;
}
UnMakeMove(tree,1,*mv,wtm);
}
}
}
/*
----------------------------------------------------------
| |
| position is known, read in the appropriate cluster. |
| note that this cluster will have all possible book |
| moves from current position in it (as well as others |
| of course.) |
| |
----------------------------------------------------------
*/
test=HashKey>>49;
if (book_file) {
fseek(book_file,test*sizeof(int),SEEK_SET);
fread(&key,sizeof(int),1,book_file);
if (key > 0) {
fseek(book_file,key,SEEK_SET);
fread(&cluster,sizeof(int),1,book_file);
fread(book_buffer,sizeof(BOOK_POSITION),cluster,book_file);
}
else cluster=0;
if (!cluster) return(0);
/*
----------------------------------------------------------
| |
| now add any moves from books.bin to the end of the |
| cluster so that they will be played even if not in the |
| regular database of moves. |
| |
----------------------------------------------------------
*/
for (i=0;i<smoves;i++) {
for (j=0;j<cluster;j++)
if (!Xor(book_buffer[j].position,start_moves[i].position)) break;
if (j >= cluster) {
book_buffer[cluster]=start_moves[i];
book_buffer[cluster].status_played=
book_buffer[cluster].status_played&037700000000;
cluster++;
}
}
/*
----------------------------------------------------------
| |
| first cycle through the root move list, make each |
| move, and see if the resulting hash key is in the book |
| database. |
| |
----------------------------------------------------------
*/
initial_development=(wtm) ? EvaluateDevelopment(tree,1) :
-EvaluateDevelopment(tree,1);
total_moves=0;
nmoves=0;
for (mv=tree->last[0];mv<tree->last[1];mv++) {
common=And(HashKey,mask_16);
MakeMove(tree,1,*mv,wtm);
if (RepetitionCheck(tree,2,ChangeSide(wtm))) {
UnMakeMove(tree,1,*mv,wtm);
return(0);
}
temp_hash_key=Xor(HashKey,wtm_random[wtm]);
temp_hash_key=Or(And(temp_hash_key,Compl(mask_16)),common);
for (i=0;i<cluster;i++) {
if (!Xor(temp_hash_key,book_buffer[i].position)) {
book_status[nmoves]=book_buffer[i].status_played>>24;
bs_played[nmoves]=book_buffer[i].status_played&077777777;
bs_learn[nmoves]=(int) (book_buffer[i].learn*100.0);
if (puzzling) bs_played[nmoves]+=1;
tree->current_move[1]=*mv;
if (!Captured(*mv))
book_development[nmoves]=((wtm) ? EvaluateDevelopment(tree,2) :
-EvaluateDevelopment(tree,2))-initial_development;
else book_development[nmoves]=0;
total_moves+=bs_played[nmoves];
evaluations[nmoves]=Evaluate(tree,2,wtm,-999999,999999);
evaluations[nmoves]-=(wtm) ? Material : -Material;
bs_percent[nmoves]=0;
for (j=0;j<smoves;j++) {
if (!Xor(book_buffer[i].position,start_moves[j].position)) {
book_status[nmoves]|=start_moves[j].status_played>>24;
bs_percent[nmoves]=start_moves[j].status_played&077777777;
break;
}
}
book_moves[nmoves++]=*mv;
break;
}
}
UnMakeMove(tree,1,*mv,wtm);
}
if (!nmoves) return(0);
/*
----------------------------------------------------------
| |
| we have the book moves, now it's time to decide how |
| they are supposed to be sorted and compute the sort |
| key. |
| |
----------------------------------------------------------
*/
for (i=0;i<nmoves;i++) {
minlv=Min(minlv,bs_learn[i]);
maxlv=Max(maxlv,bs_learn[i]);
minev=Min(minev,evaluations[i]);
maxev=Max(maxev,evaluations[i]);
maxp=Max(maxp,bs_played[i]);
}
maxp++;
for (i=0;i<nmoves;i++) {
bs_value[i]=1;
bs_value[i]+=bs_played[i]/(float) maxp*1000.0*book_weight_freq;
if (minlv < maxlv)
bs_value[i]+=(bs_learn[i]-minlv)/
(float) (Max(maxlv-minlv,50))*1000.0*book_weight_learn;
if (minev < maxev)
bs_value[i]+=(evaluations[i]-minev)/(float)(Max(maxev-minev,50))*
1000.0*book_weight_eval;
}
total_played=total_moves;
/*
----------------------------------------------------------
| |
| if any moves have a very bad or a very good learn |
| value, set the appropriate ? or ! flag so the move |
| be played or avoided as appropriate. |
| |
----------------------------------------------------------
*/
for (i=0;i<nmoves;i++) {
if (bs_learn[i] <= LEARN_COUNTER_BAD && !bs_percent[i] &&
!(book_status[i] & 030)) book_status[i]|=BAD_MOVE;
if (wtm && !(book_status[i]&0200) && !bs_percent[i] &&
!(book_status[i] & 030)) book_status[i]|=BAD_MOVE;
if (!wtm && !(book_status[i]&040) && !bs_percent[i] &&
!(book_status[i] & 030)) book_status[i]|=BAD_MOVE;
if (bs_played[i] < maxp/10 && !bs_percent[i] && book_random &&
!(book_status[i] & 030)) book_status[i]|=BAD_MOVE;
if (bs_learn[i] >= LEARN_COUNTER_GOOD &&
!(book_status[i] & 003)) book_status[i]|=GOOD_MOVE;
if (bs_percent[i]) book_status[i]|=GOOD_MOVE;
}
/*
----------------------------------------------------------
| |
| if there are any ! moves, make their popularity count |
| huge since they have to be considered. |
| |
----------------------------------------------------------
*/
mp=0;
for (i=0;i<nmoves;i++) {
if (book_status[i] & 030) bs_value[i]+=8000.0;
if (!(book_status[i] & 003)) bs_value[i]+=4000.0;
}
/*
----------------------------------------------------------
| |
| now sort the moves based on the complete sort value. |
| |
----------------------------------------------------------
*/
if (nmoves)
do {
done=1;
for (i=0;i<nmoves-1;i++) {
if (bs_percent[i]<bs_percent[i+1] ||
(bs_percent[i]==bs_percent[i+1] && bs_value[i]<bs_value[i+1])) {
tempr=bs_played[i];
bs_played[i]=bs_played[i+1];
bs_played[i+1]=tempr;
tempr=bs_value[i];
bs_value[i]=bs_value[i+1];
bs_value[i+1]=tempr;
temp=evaluations[i];
evaluations[i]=evaluations[i+1];
evaluations[i+1]=temp;
temp=bs_learn[i];
bs_learn[i]=bs_learn[i+1];
bs_learn[i+1]=temp;
temp=book_development[i];
book_development[i]=book_development[i+1];
book_development[i+1]=temp;
temp=book_moves[i];
book_moves[i]=book_moves[i+1];
book_moves[i+1]=temp;
temp=book_status[i];
book_status[i]=book_status[i+1];
book_status[i+1]=temp;
temp=bs_percent[i];
bs_percent[i]=bs_percent[i+1];
bs_percent[i+1]=temp;
done=0;
}
}
} while (!done);
/*
----------------------------------------------------------
| |
| display the book moves, and total counts, etc. if the |
| operator has requested it. |
| |
----------------------------------------------------------
*/
if (show_book) {
Print(128," after screening, the following moves can be played\n");
Print(128," move played %% score learn sortv P%% P\n");
for (i=0;i<nmoves;i++) {
Print(128,"%6s", OutputMove(tree,book_moves[i],1,wtm));
st=book_status[i];
if (st & 037) {
if (st & 1) Print(128,"??");
else if (st & 2) Print(128,"? ");
else if (st & 4) Print(128,"= ");
else if (st & 8) Print(128,"! ");
else if (st & 16) Print(128,"!!");
}
else Print(128," ");
Print(128," %6d",bs_played[i]);
Print(128," %3d",100*bs_played[i]/Max(total_moves,1));
Print(128,"%s",DisplayEvaluation(evaluations[i]));
Print(128,"%9.2f",(float)bs_learn[i]/100.0);
Print(128," %9.1f",bs_value[i]);
Print(128," %3d",bs_percent[i]);
if ((book_status[i]&book_accept_mask &&
!(book_status[i]&book_reject_mask)) ||
(!(book_status[i]&book_reject_mask) &&
(bs_percent[i] || book_status[i]&030 ||
(wtm && book_status[i]&0200) ||
(!wtm && book_status[i]&040))))
Print(128," Y");
else Print(128," N");
Print(128,"\n");
}
}
/*
----------------------------------------------------------
| |
| delete ? and ?? moves first, which includes those |
| moves with bad learned results. |
| |
----------------------------------------------------------
*/
num_selected=0;
for (i=0;i<nmoves;i++)
if (!(book_status[i] & 003) || bs_percent[i]) {
selected_status[num_selected]=book_status[i];
selected_order_played[num_selected]=bs_played[i];
selected_value[num_selected]=bs_value[i];
selected_percent[num_selected]=bs_percent[i];
selected[num_selected++]=book_moves[i];
}
for (i=0;i<num_selected;i++) {
book_status[i]=selected_status[i];
bs_played[i]=selected_order_played[i];
bs_value[i]=selected_value[i];
bs_percent[i]=selected_percent[i];
book_moves[i]=selected[i];
}
nmoves=num_selected;
/*
----------------------------------------------------------
| |
| if this is a real search (not a puzzling search to |
| find a move by the opponent to ponder) then we need to |
| set up the whisper info for later. |
| |
----------------------------------------------------------
*/
if (!puzzling) do {
whisper_text[0]='\0';
if (!nmoves) break;
sprintf(whisper_text,"book moves (");
whisper_p=whisper_text+strlen(whisper_text);
for (i=0;i<nmoves;i++) {
sprintf(whisper_p,"%s %d%%",OutputMove(tree,book_moves[i],1,wtm),
100*bs_played[i]/Max(total_played,1));
whisper_p=whisper_text+strlen(whisper_text);
if (i < nmoves-1) {
sprintf(whisper_p,", ");
whisper_p=whisper_text+strlen(whisper_text);
}
}
sprintf(whisper_p,")\n");
} while(0);
/*
----------------------------------------------------------
| |
| now select a move from the set of moves just found. do |
| this in four distinct passes: (2) look for !! moves; |
| (2) look for ! moves; (3) look for any other moves. |
| note: book_accept_mask *should* have a bit set for any |
| move that is selected, including !! and ! type moves |
| so that they *can* be excluded if desired. note also |
| that book_reject_mask should have ?? and ? set (at a |
| minimum) to exclude these types of moves. |
| |
----------------------------------------------------------
*/
/*
first, check for !! moves
*/
num_selected=0;
if (!num_selected && !puzzling)
if (book_accept_mask & 16)
for (i=0;i<nmoves;i++)
if (book_status[i] & 16) {
forced=1;
selected_status[num_selected]=book_status[i];
selected_order_played[num_selected]=bs_played[i];
selected_value[num_selected]=bs_value[i];
selected[num_selected++]=book_moves[i];
}
/*
if none, then check for ! moves
*/
if (!num_selected && !puzzling)
if (book_accept_mask & 8)
for (i=0;i<nmoves;i++)
if (book_status[i] & 8) {
forced=1;
selected_status[num_selected]=book_status[i];
selected_order_played[num_selected]=bs_played[i];
selected_value[num_selected]=bs_value[i];
selected[num_selected++]=book_moves[i];
}
/*
if none, then check for = moves
*/
if (!num_selected && !puzzling)
if (book_accept_mask & 4)
for (i=0;i<nmoves;i++)
if (book_status[i] & 4) {
selected_status[num_selected]=book_status[i];
selected_order_played[num_selected]=bs_played[i];
selected_value[num_selected]=bs_value[i];
selected[num_selected++]=book_moves[i];
}
/*
if none, then check for any flagged moves
*/
if (!num_selected && !puzzling)
for (i=0;i<nmoves;i++)
if (book_status[i] & book_accept_mask) {
selected_status[num_selected]=book_status[i];
selected_order_played[num_selected]=bs_played[i];
selected_value[num_selected]=bs_value[i];
selected[num_selected++]=book_moves[i];
}
/*
if none, then any book move is acceptable
*/
if (!num_selected)
for (i=0;i<nmoves;i++) {
selected_status[num_selected]=book_status[i];
selected_order_played[num_selected]=bs_played[i];
selected_value[num_selected]=bs_value[i];
selected[num_selected++]=book_moves[i];
}
if (!num_selected) return(0);
/*
now copy moves to the right place.
*/
for (i=0;i<num_selected;i++) {
book_status[i]=selected_status[i];
book_moves[i]=selected[i];
bs_played[i]=selected_order_played[i];
bs_value[i]=selected_value[i];
}
nmoves=num_selected;
if (nmoves == 0) return(0);
Print(128," book moves {");
for (i=0;i<nmoves;i++) {
Print(128,"%s", OutputMove(tree,book_moves[i],1,wtm));
if (i < nmoves-1) Print(128,", ");
}
Print(128,"}\n");
nmoves=Min(nmoves,book_selection_width);
if (show_book) {
Print(128," moves considered {");
for (i=0;i<nmoves;i++) {
Print(128,"%s", OutputMove(tree,book_moves[i],1,wtm));
if (i < nmoves-1) Print(128,", ");
}
Print(128,"}\n");
}
/*
----------------------------------------------------------
| |
| we have the book moves, if any have specified percents |
| for play, then adjust the bs_value[] to reflect this |
| percentage. |
| |
----------------------------------------------------------
*/
total_value=0.0;
total_percent=0;
for (i=0;i<nmoves;i++) {
if (!bs_percent[i]) total_value+=bs_value[i];
total_percent+=bs_percent[i];
}
if (total_value == 0.0) total_value=1000.0;
total_percent=(total_percent>99) ? 99 : total_percent;
for (i=0;i<nmoves;i++)
if (bs_percent[i])
bs_value[i]=total_value/(1.0-(float)total_percent/100.0)*
(float)bs_percent[i]/100.0;
/*
----------------------------------------------------------
| |
| display the book moves, and total counts, etc. if the |
| operator has requested it. |
| |
----------------------------------------------------------
*/
if (show_book) {
Print(128," move played %% score learn sortv P%% P\n");
for (i=0;i<nmoves;i++) {
Print(128,"%6s", OutputMove(tree,book_moves[i],1,wtm));
st=book_status[i];
if (st & 037) {
if (st & 1) Print(128,"??");
else if (st & 2) Print(128,"? ");
else if (st & 4) Print(128,"= ");
else if (st & 8) Print(128,"! ");
else if (st & 16) Print(128,"!!");
}
else Print(128," ");
Print(128," %6d",bs_played[i]);
Print(128," %3d",100*bs_played[i]/Max(total_moves,1));
Print(128,"%s",DisplayEvaluation(evaluations[i]));
Print(128,"%9.2f",(float)bs_learn[i]/100.0);
Print(128," %9.1f",bs_value[i]);
Print(128," %3d",bs_percent[i]);
if ((book_status[i]&book_accept_mask &&
!(book_status[i]&book_reject_mask)) ||
(!(book_status[i]&book_reject_mask) &&
((wtm && book_status[i]&0200) ||
(!wtm && book_status[i]&040))))
Print(128," Y");
else Print(128," N");
Print(128,"\n");
}
}
/*
----------------------------------------------------------
| |
| if random=0, then we search the set of legal book |
| moves with the normal search engine (but with a short |
| time limit) to choose among them. |
| |
----------------------------------------------------------
*/
if (nmoves && (!puzzling || mode!=tournament_mode)) {
np=bs_played[nmoves-1];
if (!puzzling && (!book_random ||
(mode==tournament_mode && np<book_search_trigger))) {
if (!forced) {
for (i=0;i<nmoves;i++) *(tree->last[0]+i)=book_moves[i];
tree->last[1]=tree->last[0]+nmoves;
last_pv.path_iteration_depth=0;
booking=1;
value=Iterate(wtm,booking,1);
booking=0;
if (value <- 50) {
last_pv.path_iteration_depth=0;
return(0);
}
}
else {
tree->pv[1].path[1]=book_moves[0];
tree->pv[1].path_length=1;
}
return(1);
}
}
/*
----------------------------------------------------------
| |
| if puzzling, in tournament mode we try to find the |
| best non-book move, because a book move will produce |
| a quick move anyway. we therefore would rather search |
| for a non-book move, just in case the opponent goes |
| out of book here. |
| |
----------------------------------------------------------
*/
else if (mode==tournament_mode && puzzling && !auto232) {
RootMoveList(wtm);
for (i=0;i<(tree->last[1]-tree->last[0]);i++)
for (j=0;j<nmoves;j++)
if (*(tree->last[0]+i)==book_moves[j]) *(tree->last[0]+i)=0;
for (i=0,j=0;i<(tree->last[1]-tree->last[0]);i++)
if (*(tree->last[0]+i) != 0) *(tree->last[0]+j++)=*(tree->last[0]+i);
tree->last[1]=tree->last[0]+j;
Print(128," moves considered {only non-book moves}\n");
nmoves=j;
if (nmoves > 1) {
last_pv.path_iteration_depth=0;
booking=1;
(void) Iterate(wtm,booking,1);
booking=0;
}
else {
tree->pv[1].path[1]=book_moves[0];
tree->pv[1].path_length=1;
}
return(1);
}
last_move=nmoves;
/*
----------------------------------------------------------
| |
| compute a random value and use this to generate a |
| book move based on a probability distribution of |
| the number of games won by each book move. |
| |
----------------------------------------------------------
*/
which=Random32();
j=ReadClock(microseconds)/100 % 13;
for (i=0;i<j;i++) which=Random32();
total_moves=0;
for (i=0;i<last_move;i++) {
if (bs_percent[0]) total_moves+=bs_value[i];
else total_moves+=bs_value[i]*bs_value[i];
}
distribution=abs(which) % Max(total_moves,1);
for (which=0;which<last_move;which++) {
if (bs_percent[0]) distribution-=bs_value[which];
else distribution-=bs_value[which]*bs_value[which];
if (distribution < 0) break;
}
which=Min(which,last_move-1);
tree->pv[1].path[1]=book_moves[which];
percent_played=100*bs_played[which]/Max(total_played,1);
total_played=bs_played[which];
m1_status=book_status[which];
tree->pv[1].path_length=1;
MakeMove(tree,1,book_moves[which],wtm);
UnMakeMove(tree,1,book_moves[which],wtm);
Print(128," book 0.0s %3d%% ", percent_played);
Print(128," %s",OutputMove(tree,tree->pv[1].path[1],1,wtm));
st=m1_status & book_accept_mask & (~224);
if (st) {
if (st & 1) Print(128,"??");
else if (st & 2) Print(128,"?");
else if (st & 4) Print(128,"=");
else if (st & 8) Print(128,"!");
else if (st & 16) Print(128,"!!");
}
Print(128,"\n");
return(1);
}
return(0);
}
/* last modified 08/22/98 */
/*
********************************************************************************
* *
* BookUp() is used to create/add to the opening book file. typing "book *
* create" will erase the old book file and start from scratch, typing "book *
* add" will simply add more moves to the existing file. *
* *
* the format of the input data is a left bracket ("[") followed by any title *
* information desired, followed by a right bracket ("]") followed by a *
* sequence of moves. the sequence of moves is assumed to start at ply=1, *
* with white-to-move (normal opening position) and can contain as many moves *
* as desired (no limit on the depth of each variation.) the file *must* be *
* terminated with a line that begins with "end", since handling the EOF *
* condition makes portable code difficult. *
* *
* book moves can either be typed in by hand, directly into book_add(), by *
* using the "book create/add" command. using the command "book add/create *
* filename" will cause book_add() to read its opening text moves from *
* filename rather than from the key *
* *
* in addition to the normal text for a move (reduced or full algebraic is *
* accepted, ie, e4, ed, exd4, e3d4, etc. are all acceptable) some special *
* characters can be appended to a move. *
* *
* ?? -> never play this move. since the same book is used for both *
* black and white, you can enter moves in that white might play, *
* but prevent the program from choosing them on its own. *
* ? -> avoid this move except for non-important games. these openings *
* are historically those that the program doesn't play very well, *
* but which aren't outright losing. *
* = -> drawish move, only play this move if drawish moves are allowed *
* by the operator. this is used to encourage the program to play *
* drawish openings (Petrov's comes to mind) when the program needs *
* to draw or is facing a formidable opponent (deep thought comes *
* to mind.) *
* ! -> always play this move, if there isn't a move with the !! flag *
* set also. this is a strong move, but not as strong as a !! *
* moveing traps. *
* !! -> always play this move. this can be used to make the program *
* favor particular lines, or to mark a strong move for certain *
* opening traps. *
* *
* {play nn%} is used to force this specific book move to be played a specific *
* percentage of the time, and override the frequency of play that *
* comes from the large pgn database. *
* *
********************************************************************************
*/
void BookUp(TREE *tree, char *output_filename, int nargs, char **args) {
BB_POSITION *bbuffer;
BITBOARD temp_hash_key, common;
FILE *book_input;
char fname[64], *start, *ch, schar[2]={"."};
int result=0, played, i, mask_word, total_moves;
int move, move_num, wtm, book_positions, major, minor;
int cluster, max_cluster, discarded=0, discarded_mp=0, discarded_lose=0;
int errors, data_read;
int start_cpu_time, start_elapsed_time, ply, max_ply=256;
int stat, files=0, buffered=0, min_played=0, games_parsed=0;
int wins, losses;
BOOK_POSITION current, next;
BB_POSITION temp;
int last, cluster_seek, next_cluster;
int counter, *index, max_search_depth;
POSITION cp_save;
SEARCH_POSITION sp_save;
double wl_percent=0.0;
/*
----------------------------------------------------------
| |
| open the correct book file for writing/reading |
| |
----------------------------------------------------------
*/
if (!strcmp(args[0],"create")) {
if (nargs < 3) {
Print(4095,"usage: book|books create filename ");
Print(4095,"maxply [minplay] [win/lose %]\n");
return;
}
max_ply=atoi(args[2]);
if (nargs >= 4) {
min_played=atoi(args[3]);
}
if (nargs > 4) {
wl_percent=atof(args[4])/100.0;
}
}
else if (!strcmp(args[0],"off")) {
if (book_file) fclose(book_file);
if (books_file) fclose(books_file);
book_file=0;
books_file=0;
Print(4095,"book file disabled.\n");
return;
}
else if (!strcmp(args[0],"on")) {
if (!book_file) {
#if defined (MACOS)
sprintf(fname,":%s:book.bin",book_path);
book_file=fopen(fname,"rb+");
sprintf(fname,":%s:books.bin",book_path);
books_file=fopen(fname,"rb+");
#else
sprintf(fname,"%s/book.bin",book_path);
book_file=fopen(fname,"rb+");
sprintf(fname,"%s/books.bin",book_path);
books_file=fopen(fname,"rb+");
#endif
Print(4095,"book file enabled.\n");
}
return;
}
else if (!strcmp(args[0],"mask")) {
if (nargs < 3) {
Print(4095,"usage: book mask accept|reject value\n");
return;
}
else if (!strcmp(args[1],"accept")) {
book_accept_mask=BookMask(args[2]);
book_reject_mask=book_reject_mask & ~book_accept_mask;
}
else if (!strcmp(args[1],"reject")) {
book_reject_mask=BookMask(args[2]);
book_accept_mask=book_accept_mask & ~book_reject_mask;
}
}
else if (!strcmp(args[0],"random")) {
if (nargs < 2) {
Print(4095,"usage: book random <n>\n");
return;
}
book_random=atoi(args[1]);
switch (book_random) {
case 0:
Print(4095,"play best book line after search.\n");
break;
case 1:
Print(4095,"choose from book moves randomly (using weights.)\n");
break;
default:
Print(4095,"valid options are 0-1.\n");
break;
}
return;
}
else if (!strcmp(args[0],"trigger")) {
if (nargs < 2) {
Print(4095,"usage: book trigger <n>\n");
return;
}
book_search_trigger=atoi(args[1]);
Print(4095,"search book moves if the most popular was not played\n");
Print(4095,"at least %d times.\n", book_search_trigger);
return;
}
else if (!strcmp(args[0],"width")) {
if (nargs < 2) {
Print(4095,"usage: book width <n>\n");
return;
}
book_selection_width=atoi(args[1]);
Print(4095,"choose from %d best moves.\n", book_selection_width);
return;
}
else {
Print(4095,"usage: book [option] [filename] [maxply] [minplay]\n");
return;
}
if (!(book_input=fopen(args[1],"r"))) {
printf("file %s does not exist.\n",args[1]);
return;
}
InitializeChessBoard(&tree->position[1]);
cp_save=tree->pos;
sp_save=tree->position[1];
if (book_file) fclose(book_file);
book_file=fopen(output_filename,"wb+");
bbuffer=(BB_POSITION *) malloc(sizeof(BB_POSITION)*SORT_BLOCK);
if (!bbuffer) {
Print(4095,"Unable to malloc() sort buffer, aborting\n");
exit(1);
}
fseek(book_file,0,SEEK_SET);
/*
----------------------------------------------------------
| |
| now, read in a series of moves (terminated by the "[" |
| of the next title or by "end" for end of the file) |
| and make them. after each MakeMove(), we can grab |
| the hash key, and use it to access the book data file |
| to add this position. note that we have to check the |
| last character of a move for the special flags and |
| set the correct bit in the status for this position. |
| when we reach the end of a book line, we back up to |
| the starting position and start over. |
| |
----------------------------------------------------------
*/
start=strstr(output_filename,"books.bin");
printf("parsing pgn move file (10000 moves/dot)\n");
start_cpu_time=ReadClock(cpu);
start_elapsed_time=ReadClock(elapsed);
if (book_file) {
total_moves=0;
max_search_depth=0;
errors=0;
do {
data_read=ReadPGN(book_input,0);
if (data_read == -1) Print(4095,"end-of-file reached\n");
} while (data_read == 0);
do {
if (data_read < 0) {
Print(4095,"end-of-file reached\n");
break;
}
if (data_read == 1) {
if (strstr(buffer,"Site")) {
games_parsed++;
result=3;
}
else if (strstr(buffer,"esult")) {
if (strstr(buffer,"1-0")) result=2;
else if (strstr(buffer,"0-1")) result=1;
else if (strstr(buffer,"1/2-1/2")) result=0;
else if (strstr(buffer,"*")) result=3;
}
data_read=ReadPGN(book_input,0);
}
else do {
wtm=1;
move_num=1;
tree->position[2]=tree->position[1];
ply=0;
data_read=0;
while (data_read==0) {
mask_word=0;
if ((ch=strpbrk(buffer,"?!"))) {
mask_word=BookMask(ch);
*ch=0;
}
if (!strchr(buffer,'$') && !strchr(buffer,'*')) {
if (ply < max_ply)
move=ReadNextMove(tree,buffer,2,wtm);
else move=0;
if (move) {
ply++;
max_search_depth=Max(max_search_depth,ply);
total_moves++;
common=And(HashKey,mask_16);
MakeMove(tree,2,move,wtm);
tree->position[2]=tree->position[3];
if (ply <= max_ply) {
temp_hash_key=Xor(HashKey,wtm_random[wtm]);
temp_hash_key=Or(And(temp_hash_key,Compl(mask_16)),common);
memcpy(bbuffer[buffered].position,(char*)&temp_hash_key,8);
if (result == 0) mask_word|=0100;
else if (result&2) mask_word|=0200;
else if (result&1) mask_word|=040;
bbuffer[buffered].status=mask_word;
bbuffer[buffered++].percent_play=pgn_suggested_percent+(wtm<<7);
if (buffered >= SORT_BLOCK) {
BookSort(bbuffer,buffered,++files);
buffered=0;
strcpy(schar,"S");
}
}
else {
discarded++;
}
if (!(total_moves % 10000)) {
printf(schar);
strcpy(schar,".");
if (!(total_moves % 600000)) printf(" (%d)\n",total_moves);
fflush(stdout);
}
wtm=ChangeSide(wtm);
if (wtm) move_num++;
}
else if (strspn(buffer,"0123456789/-.*") != strlen(buffer) &&
ply < max_ply) {
errors++;
Print(4095,"ERROR! move %d: %s is illegal (line %d)\n",
move_num,buffer,ReadPGN(book_input,-2));
ReadPGN(book_input,-1);
DisplayChessBoard(stdout,tree->pos);
do {
data_read=ReadPGN(book_input,0);
if (data_read == -1) Print(4095,"end-of-file reached\n");
} while (data_read == 0);
break;
}
}
data_read=ReadPGN(book_input,0);
}
} while(0);
InitializeChessBoard(&tree->position[1]);
tree->pos=cp_save;
tree->position[1]=sp_save;
} while (strcmp(buffer,"end") && data_read!=-1);
if (book_input != stdin) fclose(book_input);
if (buffered) BookSort(bbuffer,buffered,++files);
free(bbuffer);
printf("S <done>\n");
/*
----------------------------------------------------------
| |
| now merge these "chunks" into book.bin, keeping all of |
| the "flags" as well as counting the number of times |
| that each move was played. |
| |
----------------------------------------------------------
*/
printf("merging sorted files (%d) (10K/dot)\n",files);
counter=0;
index=malloc(32768*sizeof(int));
if (!index) {
Print(4095,"Unable to malloc() index block, aborting\n");
exit(1);
}
for (i=0;i<32768;i++) index[i]=-1;
temp=BookUpNextPosition(files,1);
memcpy((char*)¤t.position,temp.position,8);
current.status_played=temp.status<<24;
if (start) current.status_played+=temp.percent_play;
current.learn=0.0;
played=1;
book_file=fopen(output_filename,"wb+");
fseek(book_file,sizeof(int)*32768,SEEK_SET);
last=current.position>>49;
index[last]=ftell(book_file);
book_positions=0;
cluster=0;
cluster_seek=sizeof(int)*32768;
fseek(book_file,cluster_seek+sizeof(int),SEEK_SET);
max_cluster=0;
wins=0;
losses=0;
if (temp.status&128 && temp.percent_play&128) wins++;
if (temp.status&128 && !(temp.percent_play&128)) losses++;
if (temp.status&32 && !(temp.percent_play&128)) wins++;
if (temp.status&32 && temp.percent_play&128) losses++;
while (1) {
temp=BookUpNextPosition(files,0);
memcpy((char*)&next.position,temp.position,8);
next.status_played=temp.status<<24;
if (start) next.status_played+=temp.percent_play&127;
next.learn=0.0;
counter++;
if (counter%10000 == 0) {
printf(".");
if (counter%600000 == 0) printf(" (%d)\n",counter);
fflush(stdout);
}
if (current.position == next.position) {
current.status_played=current.status_played|next.status_played;
played++;
if (temp.status&128 && temp.percent_play&128) wins++;
if (temp.status&128 && !(temp.percent_play&128)) losses++;
if (temp.status&32 && !(temp.percent_play&128)) wins++;
if (temp.status&32 && temp.percent_play&128) losses++;
}
else {
if (played>=min_played && wins>=(losses*wl_percent)) {
book_positions++;
cluster++;
max_cluster=Max(max_cluster,cluster);
if (!start) current.status_played+=played;
current.learn=0.0;
stat=fwrite(¤t,sizeof(BOOK_POSITION),1,book_file);
if (stat != 1)
Print(4095,"ERROR! write failed, disk probably full.\n");
}
else if (played < min_played) discarded_mp++;
else discarded_lose++;
if (last != (int) (next.position>>49)) {
next_cluster=ftell(book_file);
fseek(book_file,cluster_seek,SEEK_SET);
stat=fwrite(&cluster,sizeof(int),1,book_file);
if (stat != 1)
Print(4095,"ERROR! write failed, disk probably full.\n");
if (next.position == 0) break;
fseek(book_file,next_cluster+sizeof(int),SEEK_SET);
cluster_seek=next_cluster;
last=next.position>>49;
index[last]=next_cluster;
cluster=0;
}
wins=0;
losses=0;
if (temp.status&128 && temp.percent_play&128) wins++;
if (temp.status&128 && !(temp.percent_play&128)) losses++;
if (temp.status&32 && !(temp.percent_play&128)) wins++;
if (temp.status&32 && temp.percent_play&128) losses++;
current=next;
played=1;
if (next.position == 0) break;
}
}
fseek(book_file,0,SEEK_SET);
fwrite(index,sizeof(int),32768,book_file);
fseek(book_file,0,SEEK_END);
major=atoi(version);
minor=atoi(strchr(version,'.')+1);
major=(major<<16)+minor;
fwrite(&major,sizeof(int),1,book_file);
/*
----------------------------------------------------------
| |
| now clean up, remove the sort.n files, and print the |
| statistics for building the book. |
| |
----------------------------------------------------------
*/
for (i=1;i<=files;i++) {
sprintf(fname,"sort.%d",i);
remove(fname);
}
free(index);
start_cpu_time=ReadClock(cpu)-start_cpu_time;
start_elapsed_time=ReadClock(elapsed)-start_elapsed_time;
Print(4095,"\n\nparsed %d moves (%d games).\n",total_moves,games_parsed);
Print(4095,"found %d errors during parsing.\n",errors);
Print(4095,"discarded %d moves (maxply=%d).\n",discarded,max_ply);
Print(4095,"discarded %d moves (minplayed=%d).\n",discarded_mp,min_played);
Print(4095,"discarded %d moves (win/lose=%.1f%%).\n",discarded_lose,
wl_percent*100);
Print(4095,"book contains %d unique positions.\n",book_positions);
Print(4095,"deepest book line was %d plies.\n",max_search_depth);
Print(4095,"longest cluster of moves was %d.\n",max_cluster);
Print(4095,"time used: %s cpu ", DisplayTime(start_cpu_time));
Print(4095," %s elapsed.\n", DisplayTime(start_elapsed_time));
}
}
/* last modified 06/18/98 */
/*
********************************************************************************
* *
* BookMask() is used to convert the flags for a book move into an 8 bit *
* mask that is either kept in the file, or is set by the operator to select *
* which opening(s) the program is allowed to play. *
* *
********************************************************************************
*/
int BookMask(char *flags) {
int i, mask;
mask=0;
for (i=0;i<(int) strlen(flags);i++) {
if (flags[i] == '?') {
if (flags[i+1] == '?') {
mask=mask | 1;
i++;
}
else
mask=mask | 2;
}
else if (flags[i] == '=') {
mask=mask | 4;
}
else if (flags[i] == '!') {
if (flags[i+1] == '!') {
mask=mask | 16;
i++;
}
else
mask=mask | 8;
}
}
return(mask);
}
/* last modified 06/19/98 */
/*
********************************************************************************
* *
* BookSort() is called to sort a block of moves after they have been parsed *
* and converted to hash keys. *
* *
********************************************************************************
*/
void BookSort(BB_POSITION *buffer, int number, int fileno) {
char fname[16];
FILE *output_file;
int stat;
qsort((char *) buffer,number,sizeof(BB_POSITION),BookUpCompare);
sprintf(fname,"sort.%d",fileno);
if(!(output_file=fopen(fname,"wb+")))
printf("ERROR. unable to open sort output file\n");
stat=fwrite(buffer,sizeof(BB_POSITION),number,output_file);
if (stat != number)
Print(4095,"ERROR! write failed, disk probably full.\n");
fclose(output_file);
}
/* last modified 06/19/98 */
/*
********************************************************************************
* *
* BookUpNextPosition() is the heart of the "merge" operation that is done *
* after the chunks of the parsed/hashed move file are sorted. this code *
* opens the sort.n files, and returns the least (lexically) position key to *
* counted/merged into the main book database. *
* *
********************************************************************************
*/
BB_POSITION BookUpNextPosition(int files, int init) {
char fname[20];
static FILE *input_file[100];
static BB_POSITION *buffer[100];
static int data_read[100], next[100];
int i, used;
BB_POSITION least;
if (init) {
for (i=1;i<=files;i++) {
sprintf(fname,"sort.%d",i);
if (!(input_file[i]=fopen(fname,"rb"))) {
printf("unable to open sort.%d file, may be too many files open.\n",i);
exit(1);
}
buffer[i]=malloc(sizeof(BB_POSITION)*MERGE_BLOCK);
if (!buffer[i]) {
printf("out of memory. aborting. \n");
exit(1);
}
fseek(input_file[i],0,SEEK_SET);
data_read[i]=fread(buffer[i],sizeof(BB_POSITION),MERGE_BLOCK,
input_file[i]);
next[i]=0;
}
}
for (i=0;i<8;i++) least.position[i]=0;
used=-1;
for (i=1;i<=files;i++)
if (data_read[i]) {
least=buffer[i][next[i]];
used=i;
break;
}
if (i > files) {
for (i=1;i<=files;i++) fclose(input_file[i]);
return(least);
}
for (i++;i<=files;i++) {
if (data_read[i]) {
BITBOARD p1, p2;
memcpy((char*)&p1,least.position,8);
memcpy((char*)&p2,buffer[i][next[i]].position,8);
if (p1 > p2) {
least=buffer[i][next[i]];
used=i;
}
}
}
if (--data_read[used] == 0) {
data_read[used]=fread(buffer[used],sizeof(BB_POSITION),
MERGE_BLOCK,input_file[used]);
next[used]=0;
}
else
next[used]++;
return(least);
}
#if defined(NT_i386)
int _cdecl BookUpCompare(const void *pos1, const void *pos2) {
#else
int BookUpCompare(const void *pos1, const void *pos2) {
#endif
static BITBOARD p1, p2;
memcpy((char*)&p1,((BB_POSITION *)pos1)->position,8);
memcpy((char*)&p2,((BB_POSITION *)pos2)->position,8);
if (p1 < p2) return(-1);
if (p1 > p2) return(+1);
return(0);
}