/*
DroidFish - An Android 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 org.petero.droidfish.book;
import android.annotation.SuppressLint;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import org.petero.droidfish.Util;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.MoveGen;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gamelogic.Pair;
/** Implements an opening book. */
public final class DroidBook {
static final class BookEntry {
Move move;
float weight;
BookEntry(Move move) {
this.move = move;
weight = 1;
}
@Override
public String toString() {
return TextIO.moveToUCIString(move) + " (" + weight + ")";
}
}
@SuppressLint("TrulyRandom")
private Random rndGen = new SecureRandom();
private IOpeningBook externalBook = new NullBook();
private IOpeningBook ecoBook = new EcoBook();
private IOpeningBook internalBook = new InternalBook();
private BookOptions options = null;
private static final DroidBook INSTANCE = new DroidBook();
/** Get singleton instance. */
public static DroidBook getInstance() {
return INSTANCE;
}
private DroidBook() {
rndGen.setSeed(System.currentTimeMillis());
}
/** Set opening book options. */
public final synchronized void setOptions(BookOptions options) {
this.options = options;
if (CtgBook.canHandle(options))
externalBook = new CtgBook();
else if (PolyglotBook.canHandle(options))
externalBook = new PolyglotBook();
else
externalBook = new NullBook();
externalBook.setOptions(options);
ecoBook.setOptions(options);
internalBook.setOptions(options);
}
/** Return a random book move for a position, or null if out of book. */
public final synchronized Move getBookMove(Position pos) {
if ((options != null) && (pos.fullMoveCounter > options.maxLength))
return null;
List<BookEntry> bookMoves = getBook().getBookEntries(pos);
if (bookMoves == null || bookMoves.isEmpty())
return null;
ArrayList<Move> legalMoves = new MoveGen().legalMoves(pos);
double sum = 0;
final int nMoves = bookMoves.size();
for (int i = 0; i < nMoves; i++) {
BookEntry be = bookMoves.get(i);
if (!legalMoves.contains(be.move)) {
// If an illegal move was found, it means there was a hash collision,
// or a corrupt external book file.
return null;
}
sum += scaleWeight(bookMoves.get(i).weight);
}
if (sum <= 0) {
return null;
}
double rnd = rndGen.nextDouble() * sum;
sum = 0;
for (int i = 0; i < nMoves; i++) {
sum += scaleWeight(bookMoves.get(i).weight);
if (rnd < sum)
return bookMoves.get(i).move;
}
return bookMoves.get(nMoves-1).move;
}
/** Return all book moves, both as a formatted string and as a list of moves. */
public final synchronized Pair<String,ArrayList<Move>> getAllBookMoves(Position pos,
boolean localized) {
StringBuilder ret = new StringBuilder();
ArrayList<Move> bookMoveList = new ArrayList<Move>();
ArrayList<BookEntry> bookMoves = getBook().getBookEntries(pos);
// Check legality
if (bookMoves != null) {
ArrayList<Move> legalMoves = new MoveGen().legalMoves(pos);
for (int i = 0; i < bookMoves.size(); i++) {
BookEntry be = bookMoves.get(i);
if (!legalMoves.contains(be.move)) {
bookMoves = null;
break;
}
}
}
if (bookMoves != null) {
Collections.sort(bookMoves, new Comparator<BookEntry>() {
public int compare(BookEntry arg0, BookEntry arg1) {
double wd = arg1.weight - arg0.weight;
if (wd != 0)
return (wd > 0) ? 1 : -1;
String str0 = TextIO.moveToUCIString(arg0.move);
String str1 = TextIO.moveToUCIString(arg1.move);
return str0.compareTo(str1);
}});
double totalWeight = 0;
for (BookEntry be : bookMoves)
totalWeight += scaleWeight(be.weight);
if (totalWeight <= 0) totalWeight = 1;
boolean first = true;
for (BookEntry be : bookMoves) {
Move m = be.move;
bookMoveList.add(m);
String moveStr = TextIO.moveToString(pos, m, false, localized);
if (first)
first = false;
else
ret.append(' ');
ret.append(Util.boldStart);
ret.append(moveStr);
ret.append(Util.boldStop);
ret.append(':');
int percent = (int)Math.round(scaleWeight(be.weight) * 100 / totalWeight);
ret.append(percent);
}
}
return new Pair<String, ArrayList<Move>>(ret.toString(), bookMoveList);
}
private final double scaleWeight(double w) {
if (w <= 0)
return 0;
if (options == null)
return w;
return Math.pow(w, Math.exp(-options.random));
}
private final IOpeningBook getBook() {
if (externalBook.enabled()) {
return externalBook;
} else if (ecoBook.enabled()) {
return ecoBook;
} else {
return internalBook;
}
}
}