// EXchess source code, (c) Daniel C. Homan  1997-2011
// Released under the GNU public license, see file license.txt



/* Book.Cpp functions to construct a book file from
   a pgn-like text.  Also includes functions to probe
   the book during search, edit the book, and incorporate
   learned information into the book */

#include "define.h"
#include "chess.h"
#include "funct.h"
#include "const.h"
#include "extern.h"
#include <cstdio>
#include <string.h>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cmath>
#include <ctime>
using namespace std;

/* variables for book learning */
book_rec learn_book[100];          // book learning array
int learn_filepos[100];            // file positions of played moves 
int learn_choice[100];             // number of move choices

/* variables for returning book moves */
int GAMBIT_SCORE = 80;                     // Gambit threshold 

char BOOK_FILE[FILENAME_MAX] = "main_bk.dat";
char START_BOOK[FILENAME_MAX] = "start_bk.dat";

/* Function to build an opening book from a text file (pgn) of games */
void build_book(position ipos)
{

  /* variables for book building */
  fstream chunk_file[TEMP_FILES];    // temporary files
  ofstream out;                      // final file
  int chunk_count = 1;               // number of temp files
  book_rec chunk_record[TEMP_FILES]; // current record in each temp file
  book_rec *record, *record_place;   // pointers to working records in memory
  char file[100], chunk[10];         // file names
  char instring[100], line[100];     // strings from input files
  char outbook[100], resp;
  position temp_pos={0};                 // temporary position
  move bmove;                        // book move under consideration
  unsigned __int64 pcode;            // hash code for position
  int i = -1, j = 0, k = 0, p,q;     // loop variables
  int r, s;                   
  int count = 0, thresh, LINE_DEPTH; // control variables
  int start_bk = 0, good_move = 1;

  /* initialize working record space in memory */
  record = new book_rec[BOOK_POS];
  for(p = 0; p < BOOK_POS; p++) {
   record[p].pos_code = ZERO;
   record[p].score = 0;
   record[p].gambit = 0;
   record[p].wins = 0;
  }

  /* find out what the user wants */
  cout << " Enter name of book text file: ";
  cin >> file;
  cout << " Will this be a starting book\n  (special format for pgn file, see start.pgn)? (y/n): ";
  cin >> resp;
  if(resp != 'y') {
   cout << " Enter line depth of book: ";
   cin >> LINE_DEPTH;
   cout << " Enter the minimum number of times a move must be played: ";
   cin >> thresh;
  } else { LINE_DEPTH = 60; thresh = 1; start_bk = 1;}

  cout << " Enter the name for the output book: ";
  cin >> outbook;
  cout << " Building book.... please wait.\n";

  /* open the pgn file and start work */
  ifstream infile(file);
  if(!infile) { cout << "File not found!\n"; return; }

  infile.seekg(0,ios::end);
  unsigned int file_size = infile.tellg();
  infile.seekg(0,ios::beg);

  while(!infile.eof()) {   /* start !infile.eof() loop */

    infile >> instring;
    for(r=1;r<5;r++) {
     if(instring[r] == '\0') break;
	 if(instring[r] == '.' && instring[r+1] != '\0') {
	   for(s=0;instring[r]!='\0';r++,s++) { 
        instring[s] = instring[r+1];
       } 
     }
    } 

    switch(instring[0]) {
     case '[':
      i++; count=0; infile.getline(line,99); temp_pos = ipos; break;
     case '#':
      i++; count=0; infile.getline(line,99); temp_pos = ipos; break;
     case '1': break;
     case '2': break;
     case '3': break;
     case '4': break;
     case '5': break;
     case '6': break;
     case '7': break;
     case '8': break;
     case '9': break;
     default :
       count++; if(count > LINE_DEPTH) break;
       if(start_bk) {
         q = strlen(instring);
	 if(instring[q-1] == '!') { 
          good_move = 1; 
          instring[q-1] = '\0'; 
         } else { good_move = 0; }
       }
       bmove = parse_move(temp_pos, instring);
       if(!bmove.t) { count = LINE_DEPTH; break; }
       temp_pos.exec_move(bmove, 1);
       pcode = temp_pos.hcode;
       record_place = record;
       for(j = 0; ; j++) {
          if(record_place->pos_code == pcode ||
              !record_place->pos_code) break;
          record_place++;
       }
       if(!(j%1000) && j) {
         cout << "Adding " << j << "th record to chunk "
              << chunk_count << ", " << setprecision(3)
              << (float(infile.tellg())/file_size)*100 <<  "% done\n";
         cout.flush();
       }
       record_place->pos_code = pcode;
       record_place->score += good_move;
       break;
     }

     if (j>=BOOK_POS-1) {
      if(chunk_count >= TEMP_FILES) break;
      cout << "Sorting records for chunk " << chunk_count << "\n";
      for(j = 0; j <= BOOK_POS-1; j++)
       { record_place = record+j;
         if(!record_place->pos_code) break; }

      QuickSortBook(record, record_place-1);
      sprintf(chunk, "temp_bk.%i", chunk_count);
      chunk_file[chunk_count-1].open(chunk, IOS_OUT);
      for(i = 0; i < j; i++) {
       record_place = record+i;
       chunk_file[chunk_count-1].write((char *) record_place, sizeof(book_rec));
      }
      chunk_file[chunk_count-1].close();
      chunk_file[chunk_count-1].open(chunk, IOS_IN);
      chunk_count++;

      /* initialize record structure for next chunk
         of the file to be digested */
      for(p = 0; p < BOOK_POS; p++) {
       record[p].pos_code = ZERO;
       record[p].score = 0;
      }
      j = 0;
     }
  }            /* end !infile.eof() loop */



  for(j = 0; j < BOOK_POS-1; j++)
   { record_place = record+j;
     if(!record_place->pos_code) break; }

  /* Sort and Write the last chunk to disk */
  if(j) {
    cout << "Sorting records for last chunk\n ";
    cout.flush();
    QuickSortBook(record, record_place-1);
    sprintf(chunk, "temp_bk.%i", chunk_count);
    chunk_file[chunk_count-1].open(chunk, IOS_OUT);
    for(i = 0; i < j; i++) {
     record_place = record+i;
     chunk_file[chunk_count-1].write((char *) record_place, sizeof(book_rec));
    }
    chunk_file[chunk_count-1].close();
    chunk_file[chunk_count-1].open(chunk, IOS_IN);
  }


  /* Write out big book file by sorting through the individual
     chunks */

  cout << "Writing book file...\n";
  out.open(outbook, IOS_OUT);
  int min = 0;

   // first reading in a record from each chunk
   for(i = 0; i < chunk_count; i++) {
      if(chunk_file[i].eof()) { chunk_file[i].close(); break; }
      chunk_file[i].seekg(0,ios::beg);
      chunk_file[i].read((char *) &chunk_record[i], sizeof(book_rec));
   }

 while(1) {

   // Now probe for minimum (meaning smallest hash-code) entry
   min = 0;
   for(i = 0; i < chunk_count; i++) {
     if(min == i && !chunk_record[i].pos_code) { min++; continue; }
     if(chunk_record[min].pos_code > chunk_record[i].pos_code &&
        chunk_record[i].pos_code) min = i;
   }

   if(min == chunk_count) break;

   // merge all minimum records together - reading in more when necessary
   for(i = 0; i < chunk_count; i++) {
     if(i == min) continue;
     if(chunk_record[min].pos_code == chunk_record[i].pos_code) {
         chunk_record[min].score += chunk_record[i].score;
         if(!chunk_file[i].eof())
          chunk_file[i].read((char *) &chunk_record[i], sizeof(book_rec));\
         else chunk_record[i].pos_code = ZERO;
     }
   }

   // Adjust total score for the position then write into book
   //  - notice that the scores are squared to discourage unusual moves
   if (chunk_record[min].score >= thresh) {
     chunk_record[min].score = MIN(chunk_record[min].score, 5000*thresh);
     chunk_record[min].score = int( (float(chunk_record[min].score)/thresh)
                                  *(float(chunk_record[min].score)/thresh) );
     out.write((char *) &chunk_record[min], sizeof(book_rec));
   }

   if(!chunk_file[min].eof())
       chunk_file[min].read((char *) &chunk_record[min], sizeof(book_rec));
   else chunk_record[min].pos_code = ZERO;


 }

  out.close();
  delete [] record;

  // remove temporary files
  for(j = 1; j <= chunk_count; j++) {
     chunk_file[j-1].close();
     sprintf(chunk, "temp_bk.%i", j);
     if(remove(chunk) == -1) cout << "Error deleting " << chunk << "\n";
  }

}

/* Global variables for finding a position in the book */

int file_pos;            // current position in file
fstream book_f;          // Book file
book_rec book_record;    // working record in book file
position temporary_pos;  // working position

/* Function to find a position in the book */
int find_record(position p, move m, int file_size)
{
  file_pos = 0;
  int jump = int(file_size/2);
  unsigned __int64 pcode = ZERO;

  temporary_pos = p;
  if(!temporary_pos.exec_move(m, 1)) return 0;

  book_f.seekg(0,ios::beg);

  pcode = temporary_pos.hcode;

  while(jump) {
   book_f.seekg(int(jump*sizeof(book_rec)), ios::cur);
   file_pos += jump;
   book_f.read((char *) &book_record, sizeof(book_rec));
   book_f.seekg(-int(sizeof(book_rec)), ios::cur);
   if(book_record.pos_code == pcode) return 1;
   if(jump == 1) {
     if(file_pos > 10) {
       book_f.seekg(-int(10*sizeof(book_rec)), ios::cur);
       file_pos -= 10;
     } else {
       book_f.seekg(0,ios::beg);
       file_pos = 0;
     }
     for(int i = 0; (i <= 20 && file_pos < file_size); i++) {
       book_f.read((char *) &book_record, sizeof(book_rec));
       if(book_record.pos_code == pcode) {
        book_f.seekg(-int(sizeof(book_rec)), ios::cur);
        return 1;
       }
       file_pos++;
       if(book_record.pos_code > pcode) { jump = 0; i = 21; }
       if(file_pos > file_size) { jump = 0; i = 21; }
     }
     jump = 0;
   } else {
     jump = int(ABS(jump)/2); if(!jump) jump = 1;
     if(book_record.pos_code > pcode) jump = -jump;
     if(jump == -1) jump = 1;
   }
 }

 book_record.pos_code = pcode;   // for ease in adding records to book
 return 0;

}


/* Opening Book Function */
// This function retrieves a move from the opening book.
// It does this by making each of the individual moves
// in a given position and checking if the subsequent
// position is in the book.  If so, the move in question
// becomes a candidate move.  Information is stored to
// facilitate easy learning during the game.

move opening_book(h_code hash_code, position p, game_rec *gr)
{
  int file_size, mflag = 0, j;
  int candidates = 0, total_score = 0;
  int logger = 0, poster = 0;
  move_list list;
  move nomove; nomove.t = 0;
  char book_file[100];   // file name for the book

  /************* First try starting book *************/
  /* no check on gambit scores and no learning, all moves
     are played with equal probability (score irrelevant) */    

  // generate legal moves
  p.allmoves(&list, &(gr->ts));

  // look for book
  strcpy(book_file, exec_path);    // try executable directory
  strcat(book_file, START_BOOK);
  book_f.open(book_file, IOS_IN);

  if(!book_f.is_open()) {
   // try working directory
   book_f.open(START_BOOK, IOS_IN);
  }

  if(book_f.is_open()) {   // if no book is found jump ahead to main book

   book_f.seekg(0,ios::end);
   file_size = book_f.tellg()/sizeof(book_rec);

   for(j = 0; j < list.count; j++) {

    mflag = find_record(p,list.mv[j].m,file_size);

    // If there is a record for this position in the book
    // then do some testing to set the move score
    if(mflag) {
     if(book_record.score > 0) book_record.score = 1;      // define all non-zero start books moves as equal
     list.mv[j].score = book_record.score;
     if(book_record.score > 0) {
        candidates++;
        total_score += book_record.score;
     }
    } else list.mv[j].score = 0; 
 
   }

   // Sort moves based on their score
   QuickSortMove(&list.mv[0], &list.mv[list.count-1]);

   // Now select a move from the list to play
   if(candidates) {
    int running_score = 0;
    float random = genrand();
    for(j = 0; j < candidates; j++) {
     running_score += list.mv[j].score;
     if(random <= (float(running_score)/total_score)) {
       book_f.close();
       return list.mv[j].m;      
      }
     }
    }
 
    book_f.close();
  }

  /************* Now go to main book *************/
  book_f.clear();

  // generate legal moves
  p.allmoves(&list, &(gr->ts));

  // look for book
  strcpy(book_file, exec_path);  // try executable directory
  strcat(book_file, BOOK_FILE);
  book_f.open(book_file, IOS_IN);

  if(!book_f.is_open()) {
   // try working directory
   book_f.open(BOOK_FILE, IOS_IN);
  }

  if(!book_f.is_open()) { return nomove; }   // if no book is found

  book_f.seekg(0,ios::end);
  file_size = book_f.tellg()/sizeof(book_rec);

  for(j = 0; j < list.count; j++) {

   mflag = find_record(p,list.mv[j].m,file_size);

   // If there is a record for this position in the book
   // then do some testing to set the move score
   if(mflag) {
    list.mv[j].score = int(float(book_record.score)*(pow(1.1,book_record.wins)));
    if(book_record.score > 0) {
       candidates++;
       total_score += int(float(book_record.score)*(pow(1.1,book_record.wins)));
    } else list.mv[j].score = 0;
   } else list.mv[j].score = 0;

  }

 // Sort moves based on their score
 QuickSortMove(&list.mv[0], &list.mv[list.count-1]);

 // Now select a move from the list to play
 if(candidates) {
  int running_score = 0;
  float random = genrand();

  for(j = 0; j < candidates; j++) {
   running_score += list.mv[j].score;
   if(random <= (float(running_score)/total_score)) {
     /* now find this record in the book again.
       this is done to get the file position. */
      mflag = find_record(p,list.mv[j].m,file_size);

      // Now that we found it, store learning
      // information for the move
      if(mflag) {
       if(candidates) learn_choice[gr->learn_count] = 1;
       else learn_choice[gr->learn_count] = 0;
       learn_book[gr->learn_count] = book_record;
       learn_filepos[gr->learn_count] = file_pos;
       gr->learn_count++;
       book_f.close();
       return list.mv[j].m;
      }
    }
   }
  }

  book_f.close();

  return nomove;
}



/* Editing book function */

// very rough right now

int edit_book(h_code hash_code, position *p)

{

  char mstring[10];

  int file_size, mflag = 0, j, outflag = 0;

  int search_time = 0, total_score = 0;

  move_list list;

  move nomove; nomove.t = 0;

  char resp[2];

  char book_file[100];   // file name for the book

  game_rec temp_game;

  // generate legal moves

  p->allmoves(&list, &(temp_game.ts));



  // Get file from user

  cout << " Enter book file to modify: ";

  cin >> book_file;



  book_f.open(book_file, IOS_IN | IOS_OUT);



  if(!book_f) 

   { cout << " File not found!\n"; return 0; }   // if no book is found



  book_f.seekg(0,ios::end);

  file_size = book_f.tellg()/sizeof(book_rec);



  post = 1;   // turn on search posting

  temp_game.book = 0;   // turn off book in search



  for(j = 0; j < list.count; j++) {

   // see if move is in the book

   mflag = find_record((*p),list.mv[j].m,file_size);

   // if so, add it to the total score

   if(mflag) {

     total_score += int(float(book_record.score)*(pow(1.1,book_record.wins)));

   }

  }



  cout << "******************* Book Editing Mode ***********************\n";

  cout << "Each move in the book has a \"score\" and a \"gambit flag\".\n";

  cout << "The frequency with which moves are played is given by its\n"

       << "percentage (which is computed from the scores of all the moves).\n"

       << "A move with a percentage of 0, has a less than a 0.1% chance\n"

       << "of being played.  To prevent such a move entirely, its\n"

       << "raw score must be set to zero (via editing). The gambit flag\n"

       << "determines whether a quick blunder search will be done before\n"

       << "the move is played in a game.  To play this move as a gambit\n"

       << "and skip the blunder search, set the \"gambit flag\" to 1.\n"

       << "A blunder search is never done on a move from the starting book.\n"

       << "To add a move to the book, simply try to edit that move.\n";



  for(j = 0; j < list.count; j++) {

   // see if move is in the book

   mflag = find_record((*p),list.mv[j].m,file_size);

   // if so, edit the record

   if(mflag && book_record.score > 0) {

     outflag++;

     cout << "  Move: ";

     print_move(*p, list.mv[j].m, mstring);

     cout << setw(5) << mstring << " play %: " << setprecision(3) << setw(4)

          << float(int(1000*(float(book_record.score)*(pow(1.1,book_record.wins)/total_score))))/10

          << ", gambit: " << book_record.gambit;

     if(outflag > 1) {

       outflag = 0; cout << "\n";

     }

   }

  }

  if(outflag == 1) cout << "\n";



  /* Edit mode for individual moves */

  move edit_move;

  while(1) {

    cout << "\nEnter a move to be edited/investigated (0 = quit): ";

    cin >> mstring; if(mstring[0] == '0') break;

    edit_move = parse_move((*p), mstring);

    if(find_record((*p), edit_move, file_size)) {

      cout << " raw score: " << book_record.score

           << " gambit flag: " << book_record.gambit 
           << " wins: " << book_record.wins << "\n";

      cout << " Search time in seconds (0 = no search): ";

      cin >> search_time;

      if(search_time) {

       cout << " Search of position *after* book move, so positive scores are bad! \n";

       temp_game.ts.search(temporary_pos, search_time*100, 0, &temp_game);

       cout << "\n";

      }

      cout << " Edit the record for this move (y/n) "; cin >> resp;

      if(!strcmp(resp, "y")) {

        cout << " Enter new percentage (0 = don't play): ";

        total_score -= book_record.score;

        cin >> book_record.score;

        book_record.score = MAX(0,(total_score*book_record.score)/(100-book_record.score));

        total_score += book_record.score;

        if(book_record.score) {

         cout << " Enter gambit flag: ";

         cin >> book_record.gambit;

         cout << " Enter book wins: ";

         cin >> book_record.wins;

        }

        book_f.seekp((book_f.tellg()),ios::beg);

        book_f.write((char *) &book_record, sizeof(book_rec));

      }

    } else {

      cout << " No such move in book!\n"

           << " Add a new record for this move (y/n)? ";

      cin >> resp;

      if(!strcmp(resp, "y")) {

	for(j = 0; j < list.count; j++) {

         if(list.mv[j].m.t == edit_move.t) break;

        }

        if(j == list.count) {

	 cout << " Move illegal!\n"; continue;

        }

        cout << " Enter raw score (current total = "

             << total_score << "): ";

        cin >> book_record.score;

        cout << " Enter gambit flag: ";

        cin >> book_record.gambit;

        book_record.wins = 0;

        book_rec temp_rec1, temp_rec2;

        book_f.seekg(0,ios::beg);

        book_f.seekp(0,ios::beg);

        temp_rec2 = book_record;

        for(j = 0; j < file_size; j++) {

         book_f.read((char *) &temp_rec1, sizeof(book_rec));

         if(temp_rec1.pos_code > book_record.pos_code) {

           book_f.seekp((int(book_f.tellg())-(sizeof(book_rec))), ios::beg);

           book_f.write((char *) &temp_rec2, sizeof(book_rec));

	   temp_rec2 = temp_rec1;  

         } 

        }

        book_f.write((char *) &temp_rec2, sizeof(book_rec));

        file_size++;

      }

    }

  }



  book_f.close();

  return 0;

}



/* Function to execute the book learning */

// This function simply writes a new score for each

// book position reached into the opening book.



void book_learn(int flag, game_rec *gr)

{



  int gcount = 0, bi = 0, choice_flag = 1;



  // if we are winning, increase the win number for this
  // opening.  Don't increase beyond 23 in-order to prevent
  // overflow in the scores above (see routine for choosing a move)

  if(flag == 1) {

    for(bi = 0; bi < gr->learn_count; bi++) {

      if(learn_book[bi].wins < 23)  learn_book[bi].wins++;

    }

  }



  // If we are losing decrease the win number

  if(flag < 1) {

    for(bi = gr->learn_count-1; bi >= 0; bi--) {

      learn_book[bi].wins--;

     }

   }



  // Now write the changes to the file...

  fstream out(BOOK_FILE, IOS_IN|IOS_OUT);



  if(!out) { cout << "\nError(NoBookUpdate)"; out.close(); return; }



  if(flag == 1 || flag == -1) {

    for(bi = 0; bi < gr->learn_count; bi++) {

       out.seekp(learn_filepos[bi]*sizeof(book_rec), ios::beg);

       out.write((char *) &learn_book[bi], sizeof(book_rec));

    }

   } else if(!flag) {

       out.seekp(learn_filepos[bi]*sizeof(book_rec), ios::beg);

       out.write((char *) &learn_book[bi], sizeof(book_rec));

   }



   out.seekp(0,ios::end);

   out.close();



}









