diff --git a/DroidFishApp/src/androidTest/java/org/petero/droidfish/book/BookTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/book/BookTest.java index 3ee7c57..39e8aae 100644 --- a/DroidFishApp/src/androidTest/java/org/petero/droidfish/book/BookTest.java +++ b/DroidFishApp/src/androidTest/java/org/petero/droidfish/book/BookTest.java @@ -23,7 +23,7 @@ import java.util.ArrayList; import junit.framework.TestCase; -import org.petero.droidfish.book.DroidBook; +import org.petero.droidfish.book.IOpeningBook.BookPosInput; import org.petero.droidfish.gamelogic.ChessParseError; import org.petero.droidfish.gamelogic.Move; import org.petero.droidfish.gamelogic.MoveGen; @@ -38,19 +38,21 @@ public class BookTest extends TestCase { public void testGetBookMove() throws ChessParseError { Position pos = TextIO.readFEN(TextIO.startPosFEN); DroidBook book = DroidBook.getInstance(); - Move move = book.getBookMove(pos); + BookPosInput posInput = new BookPosInput(pos, null, null); + Move move = book.getBookMove(posInput); checkValid(pos, move); // Test "out of book" condition pos.setCastleMask(0); - move = book.getBookMove(pos); + move = book.getBookMove(posInput); assertEquals(null, move); } public void testGetAllBookMoves() throws ChessParseError { Position pos = TextIO.readFEN(TextIO.startPosFEN); DroidBook book = DroidBook.getInstance(); - ArrayList moves = book.getAllBookMoves(pos, false).second; + BookPosInput posInput = new BookPosInput(pos, null, null); + ArrayList moves = book.getAllBookMoves(posInput, false).second; assertTrue(moves.size() > 1); for (Move m : moves) { checkValid(pos, m); diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java index ebcf892..45c6018 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java @@ -2500,7 +2500,7 @@ public class DroidFish extends Activity if (dotIdx < 0) return false; String ext = filename.substring(dotIdx+1); - return ("ctg".equals(ext) || "bin".equals(ext)); + return ("ctg".equals(ext) || "bin".equals(ext) || "abk".equals(ext)); }); final int numFiles = fileNames.length; final String[] items = new String[numFiles + 3]; diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/book/AbkBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/AbkBook.java new file mode 100644 index 0000000..7076460 --- /dev/null +++ b/DroidFishApp/src/main/java/org/petero/droidfish/book/AbkBook.java @@ -0,0 +1,294 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2020 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 . +*/ +package org.petero.droidfish.book; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.ArrayList; + +import org.petero.droidfish.book.DroidBook.BookEntry; +import org.petero.droidfish.gamelogic.ChessParseError; +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.Piece; +import org.petero.droidfish.gamelogic.Position; +import org.petero.droidfish.gamelogic.TextIO; + +/** Handle Arena Chess GUI opening books. */ +class AbkBook implements IOpeningBook { + private File abkFile; // The ".abk" file + private Position startPos; + + /** Constructor. */ + public AbkBook() { + try { + startPos = TextIO.readFEN(TextIO.startPosFEN); + } catch (ChessParseError ex) { + throw new RuntimeException(ex); + } + } + + static boolean canHandle(BookOptions options) { + String filename = options.filename; + return filename.endsWith(".abk"); + } + + @Override + public boolean enabled() { + return abkFile.canRead(); + } + + @Override + public void setOptions(BookOptions options) { + abkFile = new File(options.filename); + } + + private static class MoveData { + Move move; + double weightPrio; + double weightNGames; + double weightScore; + } + + @Override + public ArrayList getBookEntries(BookPosInput posInput) { + if (!startPos.equals(posInput.getPrevPos())) + return null; + + try (RandomAccessFile abkF = new RandomAccessFile(abkFile, "r")) { + ArrayList gameMoves = posInput.getMoves(); + + BookSettings bs = new BookSettings(abkF); + if (gameMoves.size() >= bs.maxPly) + return null; + + AbkBookEntry ent = new AbkBookEntry(); + int entNo = 900; + for (Move m : gameMoves) { + int iter = 0; + while (true) { + if (entNo < 0) + return null; + ent.read(abkF, entNo); + if (ent.getMove().equals(m) && ent.isValid()) { + entNo = ent.nextMove; + break; + } + entNo = ent.nextSibling; + iter++; + if (iter > 255) + return null; // Corrupt book + } + } + if (entNo < 0) + return null; + + boolean wtm = (gameMoves.size() % 2) == 0; + ArrayList moves = new ArrayList<>(); + while (entNo >= 0) { + ent.read(abkF, entNo); + MoveData md = new MoveData(); + md.move = ent.getMove(); + + int nWon = wtm ? ent.nWon : ent.nLost; + int nLost = wtm ? ent.nLost : ent.nWon; + int nDraw = ent.nGames - nWon - nLost; + md.weightPrio = scaleWeight(ent.priority, bs.prioImportance); + md.weightNGames = scaleWeight(ent.nGames, bs.nGamesImportance); + double score = (nWon + nDraw * 0.5) / ent.nGames; + md.weightScore = scaleWeight(score, bs.scoreImportance); + + if (ent.isValid() && + (!bs.skipPrio0Moves || ent.priority > 0) && + (ent.nGames >= bs.minGames) && + (nWon >= bs.minWonGames) && + (score * 100 >= (wtm ? bs.minWinPercentWhite : bs.minWinPercentBlack))) { + moves.add(md); + } + + if (moves.size() > 255) + return null; // Corrupt book + entNo = ent.nextSibling; + } + + double sumWeightPrio = 0; + double sumWeightNGames = 0; + double sumWeightScore = 0; + for (MoveData md : moves) { + sumWeightPrio += md.weightPrio; + sumWeightNGames += md.weightNGames; + sumWeightScore += md.weightScore; + } + + ArrayList ret = new ArrayList<>(); + boolean hasNonZeroWeight = false; + for (MoveData md : moves) { + BookEntry be = new BookEntry(md.move); + double wP = sumWeightPrio > 0 ? md.weightPrio / sumWeightPrio : 0.0; + double wN = sumWeightNGames > 0 ? md.weightNGames / sumWeightNGames : 0.0; + double wS = sumWeightScore > 0 ? md.weightScore / sumWeightScore : 0.0; + double a = 0.624; + double w = wP * Math.exp(a * bs.prioImportance) + + wN * Math.exp(a * bs.nGamesImportance) + + wS * Math.exp(a * bs.scoreImportance) * 1.4; + hasNonZeroWeight |= w > 0; + be.weight = (float)w; + ret.add(be); + } + if (!hasNonZeroWeight) + for (BookEntry be : ret) + be.weight = 1; + return ret; + } catch (IOException e) { + return null; + } + } + + private static class AbkBookEntry { + private byte[] data = new byte[28]; + + private byte from; // From square, 0 = a1, 7 = h1, 8 = a2, 63 = h8 + private byte to; // To square + private byte promotion; // 0 = none, +-1 = rook, +-2 = knight, +-3 = bishop, +-4 = queen + byte priority; // 0 = bad, >0 better, 9 best + int nGames; // Number of times games in which move was played + int nWon; // Number of won games for white + int nLost; // Number of lost games for white + int flags; // Value is 0x01000000 if move has been deleted + int nextMove; // First following move (by opposite color) + int nextSibling; // Next alternative move (by same color) + + AbkBookEntry() { + } + + void read(RandomAccessFile f, long entNo) throws IOException { + f.seek(entNo * 28); + f.readFully(data); + + from = data[0]; + to = data[1]; + promotion = data[2]; + priority = data[3]; + nGames = extractInt(4); + nWon = extractInt(8); + nLost = extractInt(12); + flags = extractInt(16); + nextMove = extractInt(20); + nextSibling = extractInt(24); + } + + Move getMove() { + int prom; + switch (promotion) { + case 0: prom = Piece.EMPTY; break; + case -1: prom = Piece.WROOK; break; + case -2: prom = Piece.WKNIGHT; break; + case -3: prom = Piece.WBISHOP; break; + case -4: prom = Piece.WQUEEN; break; + case 1: prom = Piece.BROOK; break; + case 2: prom = Piece.BKNIGHT; break; + case 3: prom = Piece.BBISHOP; break; + case 4: prom = Piece.BQUEEN; break; + default: prom = -1; break; + } + return new Move(from, to, prom); + } + + boolean isValid() { + return flags != 0x01000000; + } + + private int extractInt(int offs) { + return AbkBook.extractInt(data, offs); + } + } + + /** Convert 4 bytes starting at "offs" in buf[] to an integer. */ + private static int extractInt(byte[] buf, int offs) { + int ret = 0; + for (int i = 3; i >= 0; i--) { + int b = buf[offs + i]; + if (b < 0) b += 256; + ret = (ret << 8) + b; + } + return ret; + } + + private static class BookSettings { + private byte[] buf = new byte[256]; + + int minGames; + int minWonGames; + int minWinPercentWhite; // 0 - 100 + int minWinPercentBlack; // 0 - 100 + + int prioImportance; // 0 - 15 + int nGamesImportance; // 0 - 15 + int scoreImportance; // 0 - 15 + + int maxPly; + + boolean skipPrio0Moves = false; // Not stored in abk file + + public BookSettings(RandomAccessFile abkF) throws IOException { + abkF.seek(0); + abkF.readFully(buf); + + minGames = getInt(0xde, Integer.MAX_VALUE); + minWonGames = getInt(0xe2, Integer.MAX_VALUE); + minWinPercentWhite = getInt(0xe6, 100); + minWinPercentBlack = getInt(0xea, 100); + + prioImportance = getInt(0xee, 15); + nGamesImportance = getInt(0xf2, 15); + scoreImportance = getInt(0xf6, 15); + + maxPly = getInt(0xfa, 9999); + + if (prioImportance == 0 && nGamesImportance == 0 && scoreImportance == 0) { + minGames = 0; + minWonGames = 0; + minWinPercentWhite = 0; + minWinPercentBlack = 0; + } + } + + private int getInt(int offs, int maxVal) { + int val = extractInt(buf, offs); + return Math.min(Math.max(val, 0), maxVal); + } + } + + private static double scaleWeight(double w, int importance) { + double e; + switch (importance) { + case 0: + return 0; + case 1: + e = 0.66; + break; + case 2: + e = 0.86; + break; + default: + e = 1 + ((double)importance - 3) / 6; + break; + } + return Math.pow(w, e); + } +} diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/book/CtgBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/CtgBook.java index 81a852e..64e97b0 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/book/CtgBook.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/book/CtgBook.java @@ -62,7 +62,8 @@ class CtgBook implements IOpeningBook { } @Override - public ArrayList getBookEntries(Position pos) { + public ArrayList getBookEntries(BookPosInput posInput) { + Position pos = posInput.getCurrPos(); try (RandomAccessFile ctgF = new RandomAccessFile(ctgFile, "r"); RandomAccessFile ctbF = new RandomAccessFile(ctbFile, "r"); RandomAccessFile ctoF = new RandomAccessFile(ctoFile, "r")) { diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/book/DroidBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/DroidBook.java index 63274f4..8370577 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/book/DroidBook.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/book/DroidBook.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Random; import org.petero.droidfish.Util; +import org.petero.droidfish.book.IOpeningBook.BookPosInput; import org.petero.droidfish.gamelogic.Move; import org.petero.droidfish.gamelogic.MoveGen; import org.petero.droidfish.gamelogic.Position; @@ -64,7 +65,6 @@ public final class DroidBook { } private DroidBook() { - rndGen.setSeed(System.currentTimeMillis()); } /** Set opening book options. */ @@ -74,6 +74,8 @@ public final class DroidBook { externalBook = new CtgBook(); else if (PolyglotBook.canHandle(options)) externalBook = new PolyglotBook(); + else if (AbkBook.canHandle(options)) + externalBook = new AbkBook(); else externalBook = new NullBook(); externalBook.setOptions(options); @@ -83,10 +85,11 @@ public final class DroidBook { } /** Return a random book move for a position, or null if out of book. */ - public final synchronized Move getBookMove(Position pos) { + public final synchronized Move getBookMove(BookPosInput posInput) { + Position pos = posInput.getCurrPos(); if ((options != null) && (pos.fullMoveCounter > options.maxLength)) return null; - List bookMoves = getBook().getBookEntries(pos); + List bookMoves = getBook().getBookEntries(posInput); if (bookMoves == null || bookMoves.isEmpty()) return null; @@ -116,11 +119,12 @@ public final class DroidBook { } /** Return all book moves, both as a formatted string and as a list of moves. */ - public final synchronized Pair> getAllBookMoves(Position pos, + public final synchronized Pair> getAllBookMoves(BookPosInput posInput, boolean localized) { + Position pos = posInput.getCurrPos(); StringBuilder ret = new StringBuilder(); ArrayList bookMoveList = new ArrayList<>(); - ArrayList bookMoves = getBook().getBookEntries(pos); + ArrayList bookMoves = getBook().getBookEntries(posInput); // Check legality if (bookMoves != null) { diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/book/EcoBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/EcoBook.java index 283f150..deb3589 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/book/EcoBook.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/book/EcoBook.java @@ -43,7 +43,8 @@ public class EcoBook implements IOpeningBook { } @Override - public ArrayList getBookEntries(Position pos) { + public ArrayList getBookEntries(BookPosInput posInput) { + Position pos = posInput.getCurrPos(); ArrayList moves = EcoDb.getInstance().getMoves(pos); ArrayList entries = new ArrayList<>(); for (int i = 0; i < moves.size(); i++) { diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/book/IOpeningBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/IOpeningBook.java index 2249ccf..a67a39c 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/book/IOpeningBook.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/book/IOpeningBook.java @@ -18,18 +18,61 @@ package org.petero.droidfish.book; +import android.util.Pair; + import java.util.ArrayList; import org.petero.droidfish.book.DroidBook.BookEntry; +import org.petero.droidfish.gamelogic.Game; +import org.petero.droidfish.gamelogic.Move; import org.petero.droidfish.gamelogic.Position; -interface IOpeningBook { +public interface IOpeningBook { /** Return true if book is currently enabled. */ boolean enabled(); /** Set book options, including filename. */ void setOptions(BookOptions options); + /** Information required to query an opening book. */ + class BookPosInput { + private final Position currPos; + + private Game game; + private Position prevPos; + private ArrayList moves; + + public BookPosInput(Position currPos, Position prevPos, ArrayList moves) { + this.currPos = currPos; + this.prevPos = prevPos; + this.moves = moves; + } + + public BookPosInput(Game game) { + currPos = game.currPos(); + this.game = game; + } + + public Position getCurrPos() { + return currPos; + } + public Position getPrevPos() { + lazyInit(); + return prevPos; + } + public ArrayList getMoves() { + lazyInit(); + return moves; + } + + private void lazyInit() { + if (prevPos == null) { + Pair> ph = game.getUCIHistory(); + prevPos = ph.first; + moves = ph.second; + } + } + } /** Get all book entries for a position. */ - ArrayList getBookEntries(Position pos); + ArrayList getBookEntries(BookPosInput posInput); } diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/book/InternalBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/InternalBook.java index e57fd22..dc1e11b 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/book/InternalBook.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/book/InternalBook.java @@ -52,7 +52,8 @@ final class InternalBook implements IOpeningBook { } @Override - public ArrayList getBookEntries(Position pos) { + public ArrayList getBookEntries(BookPosInput posInput) { + Position pos = posInput.getCurrPos(); initInternalBook(); ArrayList ents = bookMap.get(pos.zobristHash()); if (ents == null) diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/book/NoBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/NoBook.java index 028650f..e4d86df 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/book/NoBook.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/book/NoBook.java @@ -37,7 +37,7 @@ class NoBook implements IOpeningBook { } @Override - public ArrayList getBookEntries(Position pos) { + public ArrayList getBookEntries(BookPosInput posInput) { return null; } } diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/book/NullBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/NullBook.java index 3903a1f..98d9ac6 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/book/NullBook.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/book/NullBook.java @@ -35,7 +35,7 @@ class NullBook implements IOpeningBook { } @Override - public ArrayList getBookEntries(Position pos) { + public ArrayList getBookEntries(BookPosInput posInput) { return null; } } diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/book/PolyglotBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/PolyglotBook.java index 9e801ef..64f7aa5 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/book/PolyglotBook.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/book/PolyglotBook.java @@ -370,7 +370,8 @@ class PolyglotBook implements IOpeningBook { } @Override - public final ArrayList getBookEntries(Position pos) { + public final ArrayList getBookEntries(BookPosInput posInput) { + Position pos = posInput.getCurrPos(); try (RandomAccessFile f = new RandomAccessFile(bookFile, "r")) { long numEntries = f.length() / 16; long key = getHashKey(pos); diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/engine/DroidComputerPlayer.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/DroidComputerPlayer.java index 08dce30..39ce377 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/engine/DroidComputerPlayer.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/engine/DroidComputerPlayer.java @@ -28,6 +28,7 @@ import java.util.TreeMap; import org.petero.droidfish.EngineOptions; import org.petero.droidfish.book.BookOptions; import org.petero.droidfish.book.DroidBook; +import org.petero.droidfish.book.IOpeningBook.BookPosInput; import org.petero.droidfish.gamelogic.Move; import org.petero.droidfish.gamelogic.MoveGen; import org.petero.droidfish.gamelogic.Position; @@ -95,7 +96,7 @@ public class DroidComputerPlayer { int searchId; // Unique identifier for this search request long startTime; // System time (milliseconds) when search request was created - Position prevPos; // Position at last irreversible move + Position prevPos; // Position at last null move ArrayList mList; // Moves after prevPos, including ponderMove Position currPos; // currPos = prevPos + mList - ponderMove boolean drawOffer; // True if other side made draw offer @@ -361,8 +362,9 @@ public class DroidComputerPlayer { } /** Return all book moves, both as a formatted string and as a list of moves. */ - public final Pair> getBookHints(Position pos, boolean localized) { - return book.getAllBookMoves(pos, localized); + public final Pair> getBookHints(BookPosInput posInput, + boolean localized) { + return book.getAllBookMoves(posInput, localized); } /** Get engine reported name. */ @@ -454,7 +456,8 @@ public class DroidComputerPlayer { if (sr.ponderMove == null) { // If we have a book move, play it. - Move bookMove = book.getBookMove(sr.currPos); + BookPosInput posInput = new BookPosInput(sr.currPos, sr.prevPos, sr.mList); + Move bookMove = book.getBookMove(posInput); if (bookMove != null) { if (canClaimDraw(sr.currPos, posHashList, posHashListSize, bookMove).isEmpty()) { listener.notifySearchResult(sr.searchId, diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/DroidChessController.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/DroidChessController.java index 4824a46..89c126b 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/DroidChessController.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/DroidChessController.java @@ -39,6 +39,7 @@ import org.petero.droidfish.PGNOptions; import org.petero.droidfish.Util; import org.petero.droidfish.book.BookOptions; import org.petero.droidfish.book.EcoDb; +import org.petero.droidfish.book.IOpeningBook.BookPosInput; import org.petero.droidfish.engine.DroidComputerPlayer; import org.petero.droidfish.engine.UCIOptions; import org.petero.droidfish.engine.DroidComputerPlayer.EloData; @@ -955,7 +956,8 @@ public class DroidChessController { private void updateBookHints() { if (game != null) { - Pair> bi = computerPlayer.getBookHints(game.currPos(), localPt()); + BookPosInput posInput = new BookPosInput(game); + Pair> bi = computerPlayer.getBookHints(posInput, localPt()); EcoDb.Result ecoData = EcoDb.getInstance().getEco(game.tree); String eco = ecoData.getName(); listener.notifyBookInfo(searchId, bi.first, bi.second, eco, ecoData.distToEcoTree); diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Game.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Game.java index b1a2936..f355170 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Game.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Game.java @@ -112,7 +112,7 @@ public class Game { return ret; } - final Position currPos() { + public final Position currPos() { return tree.currentPos; } diff --git a/DroidFishApp/src/main/res/values/strings.xml b/DroidFishApp/src/main/res/values/strings.xml index 219608f..2336744 100644 --- a/DroidFishApp/src/main/res/values/strings.xml +++ b/DroidFishApp/src/main/res/values/strings.xml @@ -389,7 +389,7 @@ If you are running on battery power, it is recommended that you change settings Ignore moves marked as not for tournament play Book Randomization Book Filename - Polyglot or CTG book file in DroidFish directory on SD Card + Polyglot, ABK or CTG book file in DroidFish directory on SD Card PGN Settings Settings for import and export of portable game notation (PGN) data PGN viewer diff --git a/README.md b/README.md index 08558a6..dd68bf4 100644 --- a/README.md +++ b/README.md @@ -560,16 +560,19 @@ restored. ## Installing additional opening books -To use *polyglot* or *CTG* book files: +To use *polyglot*, *CTG* or *ABK* book files: -1. Copy one or more polyglot book files to the `DroidFish/book` directory on the - external storage. Polyglot books must have the file extension `.bin`. - **Note!** The Android file system is case sensitive, so the extension must be - `.bin`, not `.Bin` or `.BIN`. +1. Copy one or more opening book files to the `DroidFish/book` directory on the + external storage. -1. Copy one or more CTG book files to the `DroidFish/book` directory. A CTG - book consists of three files with file extensions `.ctg`, `.ctb` and - `.cto`. You must copy all three files. + 1. Polyglot books must have the file extension `.bin`. + **Note!** The Android file system may be case sensitive, in which case the + extension must be `.bin`, not `.Bin` or `.BIN`. + + 1. A *CTG* book consists of three files with file extensions `.ctg`, `.ctb` + and `.cto`. You must copy all three files. + + 1. an *ABK* book must have the file extension `.abk`. 1. Go to *Left drawer menu* -> *Select opening book*. @@ -862,11 +865,11 @@ You can change aspects of the opening book from *Left drawer menu* -> *Settings* * *Prefer main lines*: When enabled, moves that are marked as main line moves in the book are given a higher weight so they will be played more often by the chess engine. - **Note!** This option only has an effect for CTG opening books. + **Note!** This option only has an effect for *CTG* opening books. * *Tournament mode*: When enabled, only book moves that are marked for tournament play are played by the chess engine. - **Note!** This option only has an effect for CTG opening books. + **Note!** This option only has an effect for *CTG* opening books. * *Book randomization*: Controls how often different book moves are played by the engine. The default is 50% which means that the statistics from the @@ -882,6 +885,10 @@ You can change aspects of the opening book from *Left drawer menu* -> *Settings* very big opening book stored somewhere on the device but it would be impractical to copy it to the `DroidFish/book` directory. -**Note!** The move percentages calculated by *DroidFish* for CTG books are +**Note!** The move percentages calculated by *DroidFish* for *CTG* books are unlikely to agree with percentages calculated by other chess programs that can -use CTG books. +use *CTG* books. + +**Note!** The move percentages calculated by *DroidFish* for *ABK* books are not +always equal to percentages shown in the Arena Chess GUI, because the algorithm +used by Arena to compute the percentages is unknown. diff --git a/doc/droidfish_manual.pdf b/doc/droidfish_manual.pdf index 6171231..f5b6484 100644 Binary files a/doc/droidfish_manual.pdf and b/doc/droidfish_manual.pdf differ