/*
CuckooChess - A java chess program.
Copyright (C) 2011 Peter Ă–sterlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package chess;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* Implements an opening book.
* @author petero
*/
public class Book {
public static class BookEntry {
Move move;
int count;
BookEntry(Move move) {
this.move = move;
count = 1;
}
}
private static Map<Long, List<BookEntry>> bookMap;
private static Random rndGen;
private static int numBookMoves = -1;
private boolean verbose;
public Book(boolean verbose) {
this.verbose = verbose;
}
private final void initBook() {
if (numBookMoves >= 0)
return;
long t0 = System.currentTimeMillis();
bookMap = new HashMap<Long, List<BookEntry>>();
rndGen = new SecureRandom();
rndGen.setSeed(System.currentTimeMillis());
numBookMoves = 0;
try {
InputStream inStream = getClass().getResourceAsStream("/book.bin");
List<Byte> buf = new ArrayList<Byte>(8192);
byte[] tmpBuf = new byte[1024];
while (true) {
int len = inStream.read(tmpBuf);
if (len <= 0) break;
for (int i = 0; i < len; i++)
buf.add(tmpBuf[i]);
}
inStream.close();
Position startPos = TextIO.readFEN(TextIO.startPosFEN);
Position pos = new Position(startPos);
UndoInfo ui = new UndoInfo();
int len = buf.size();
for (int i = 0; i < len; i += 2) {
int b0 = buf.get(i); if (b0 < 0) b0 += 256;
int b1 = buf.get(i+1); if (b1 < 0) b1 += 256;
int move = (b0 << 8) + b1;
if (move == 0) {
pos = new Position(startPos);
} else {
boolean bad = ((move >> 15) & 1) != 0;
int prom = (move >> 12) & 7;
Move m = new Move(move & 63, (move >> 6) & 63,
promToPiece(prom, pos.whiteMove));
if (!bad)
addToBook(pos, m);
pos.makeMove(m, ui);
}
}
} catch (ChessParseError ex) {
throw new RuntimeException();
} catch (IOException ex) {
System.out.println("Can't read opening book resource");
throw new RuntimeException();
}
if (verbose) {
long t1 = System.currentTimeMillis();
System.out.printf("Book moves:%d (parse time:%.3f)%n", numBookMoves,
(t1 - t0) / 1000.0);
}
}
/** Add a move to a position in the opening book. */
private final void addToBook(Position pos, Move moveToAdd) {
List<BookEntry> ent = bookMap.get(pos.zobristHash());
if (ent == null) {
ent = new ArrayList<BookEntry>();
bookMap.put(pos.zobristHash(), ent);
}
for (int i = 0; i < ent.size(); i++) {
BookEntry be = ent.get(i);
if (be.move.equals(moveToAdd)) {
be.count++;
return;
}
}
BookEntry be = new BookEntry(moveToAdd);
ent.add(be);
numBookMoves++;
}
/** Return a random book move for a position, or null if out of book. */
public final Move getBookMove(Position pos) {
initBook();
List<BookEntry> bookMoves = bookMap.get(pos.zobristHash());
if (bookMoves == null) {
return null;
}
MoveGen.MoveList legalMoves = new MoveGen().pseudoLegalMoves(pos);
MoveGen.removeIllegal(pos, legalMoves);
int sum = 0;
for (int i = 0; i < bookMoves.size(); i++) {
BookEntry be = bookMoves.get(i);
boolean contains = false;
for (int mi = 0; mi < legalMoves.size; mi++)
if (legalMoves.m[mi].equals(be.move)) {
contains = true;
break;
}
if (!contains) {
// If an illegal move was found, it means there was a hash collision.
return null;
}
sum += getWeight(bookMoves.get(i).count);
}
if (sum <= 0) {
return null;
}
int rnd = rndGen.nextInt(sum);
sum = 0;
for (int i = 0; i < bookMoves.size(); i++) {
sum += getWeight(bookMoves.get(i).count);
if (rnd < sum) {
return bookMoves.get(i).move;
}
}
// Should never get here
throw new RuntimeException();
}
final private int getWeight(int count) {
double tmp = Math.sqrt(count);
return (int)(tmp * Math.sqrt(tmp) * 100 + 1);
}
/** Return a string describing all book moves. */
public final String getAllBookMoves(Position pos) {
initBook();
StringBuilder ret = new StringBuilder();
List<BookEntry> bookMoves = bookMap.get(pos.zobristHash());
if (bookMoves != null) {
for (BookEntry be : bookMoves) {
String moveStr = TextIO.moveToString(pos, be.move, false);
ret.append(moveStr);
ret.append("(");
ret.append(be.count);
ret.append(") ");
}
}
return ret.toString();
}
/** Creates the book.bin file. */
public static void main(String[] args) throws IOException {
List<Byte> binBook = createBinBook();
FileOutputStream out = new FileOutputStream("../src/book.bin");
int bookLen = binBook.size();
byte[] binBookA = new byte[bookLen];
for (int i = 0; i < bookLen; i++)
binBookA[i] = binBook.get(i);
out.write(binBookA);
out.close();
}
public static List<Byte> createBinBook() {
List<Byte> binBook = new ArrayList<Byte>(0);
try {
InputStream inStream = new Object().getClass().getResourceAsStream("/book.txt");
InputStreamReader inFile = new InputStreamReader(inStream);
BufferedReader inBuf = new BufferedReader(inFile);
LineNumberReader lnr = new LineNumberReader(inBuf);
String line;
while ((line = lnr.readLine()) != null) {
if (line.startsWith("#") || (line.length() == 0)) {
continue;
}
if (!addBookLine(line, binBook)) {
System.out.printf("Book parse error, line:%d\n", lnr.getLineNumber());
throw new RuntimeException();
}
// System.out.printf("no:%d line:%s%n", lnr.getLineNumber(), line);
}
lnr.close();
} catch (ChessParseError ex) {
throw new RuntimeException();
} catch (IOException ex) {
System.out.println("Can't read opening book resource");
throw new RuntimeException();
}
return binBook;
}
/** Add a sequence of moves, starting from the initial position, to the binary opening book. */
private static boolean addBookLine(String line, List<Byte> binBook) throws ChessParseError {
Position pos = TextIO.readFEN(TextIO.startPosFEN);
UndoInfo ui = new UndoInfo();
String[] strMoves = line.split(" ");
for (String strMove : strMoves) {
// System.out.printf("Adding move:%s\n", strMove);
int bad = 0;
if (strMove.endsWith("?")) {
strMove = strMove.substring(0, strMove.length() - 1);
bad = 1;
}
Move m = TextIO.stringToMove(pos, strMove);
if (m == null) {
return false;
}
int prom = pieceToProm(m.promoteTo);
int val = m.from + (m.to << 6) + (prom << 12) + (bad << 15);
binBook.add((byte)(val >> 8));
binBook.add((byte)(val & 255));
pos.makeMove(m, ui);
}
binBook.add((byte)0);
binBook.add((byte)0);
return true;
}
private static int pieceToProm(int p) {
switch (p) {
case Piece.WQUEEN: case Piece.BQUEEN:
return 1;
case Piece.WROOK: case Piece.BROOK:
return 2;
case Piece.WBISHOP: case Piece.BBISHOP:
return 3;
case Piece.WKNIGHT: case Piece.BKNIGHT:
return 4;
default:
return 0;
}
}
private static int promToPiece(int prom, boolean whiteMove) {
switch (prom) {
case 1: return whiteMove ? Piece.WQUEEN : Piece.BQUEEN;
case 2: return whiteMove ? Piece.WROOK : Piece.BROOK;
case 3: return whiteMove ? Piece.WBISHOP : Piece.BBISHOP;
case 4: return whiteMove ? Piece.WKNIGHT : Piece.BKNIGHT;
default: return Piece.EMPTY;
}
}
}