/* GtbCuckoo - Interface to Gaviota endgame tablebases. Copyright (C) 2011-2012 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.tb; import java.util.ArrayList; import org.petero.droidfish.gamelogic.ChessParseError; import org.petero.droidfish.gamelogic.Move; import org.petero.droidfish.gamelogic.MoveGen; import org.petero.droidfish.gamelogic.Pair; import org.petero.droidfish.gamelogic.Piece; import org.petero.droidfish.gamelogic.Position; import org.petero.droidfish.gamelogic.TextIO; import org.petero.droidfish.gamelogic.UndoInfo; /** Interface between Position class and GTB/RTB probing code. */ public class Probe { private final GtbProbe gtb; private final RtbProbe rtb; private final int whiteSquares[]; private final int blackSquares[]; private final byte whitePieces[]; private final byte blackPieces[]; private static final Probe instance = new Probe(); /** Get singleton instance. */ public static Probe getInstance() { return instance; } /** Constructor. */ private Probe() { gtb = new GtbProbe(); rtb = new RtbProbe(); whiteSquares = new int[65]; blackSquares = new int[65]; whitePieces = new byte[65]; blackPieces = new byte[65]; } public void setPath(String gtbPath, String rtbPath, boolean forceReload) { gtb.setPath(gtbPath, forceReload); rtb.setPath(rtbPath, forceReload); } private static final class GtbProbeResult { public final static int DRAW = 0; public final static int WMATE = 1; public final static int BMATE = 2; public final static int UNKNOWN = 3; public int result; public int pliesToMate; // Plies to mate, or 0 if DRAW or UNKNOWN. } /** * Probe GTB tablebases. * @param pos The position to probe. * @param result Two element array. Set to [tbinfo, plies]. * @return True if success. */ private final GtbProbeResult gtbProbe(Position pos) { GtbProbeResult ret = gtbProbeRaw(pos); if (ret.result == GtbProbeResult.DRAW && pos.getEpSquare() != -1) { ArrayList<Move> moveList = MoveGen.instance.legalMoves(pos); int pawn = pos.whiteMove ? Piece.WPAWN : Piece.BPAWN; int maxMate = -1; UndoInfo ui = new UndoInfo(); for (Move move : moveList) { if ((move.to != pos.getEpSquare()) || (pos.getPiece(move.from) != pawn)) return ret; pos.makeMove(move, ui); GtbProbeResult ret2 = gtbProbe(pos); pos.unMakeMove(move, ui); switch (ret2.result) { case GtbProbeResult.DRAW: break; case GtbProbeResult.WMATE: case GtbProbeResult.BMATE: maxMate = Math.max(maxMate, ret2.pliesToMate); break; case GtbProbeResult.UNKNOWN: ret.result = GtbProbeResult.UNKNOWN; return ret; } } if (maxMate != -1) { ret.result = pos.whiteMove ? GtbProbeResult.BMATE : GtbProbeResult.WMATE; ret.pliesToMate = maxMate; } } return ret; } private final GtbProbeResult gtbProbeRaw(Position pos) { int castleMask = 0; if (pos.a1Castle()) castleMask |= GtbProbe.A1_CASTLE; if (pos.h1Castle()) castleMask |= GtbProbe.H1_CASTLE; if (pos.a8Castle()) castleMask |= GtbProbe.A8_CASTLE; if (pos.h8Castle()) castleMask |= GtbProbe.H8_CASTLE; int nWhite = 0; int nBlack = 0; for (int sq = 0; sq < 64; sq++) { int p = pos.getPiece(sq); switch (p) { case Piece.WKING: whiteSquares[nWhite] = sq; whitePieces[nWhite++] = GtbProbe.KING; break; case Piece.WQUEEN: whiteSquares[nWhite] = sq; whitePieces[nWhite++] = GtbProbe.QUEEN; break; case Piece.WROOK: whiteSquares[nWhite] = sq; whitePieces[nWhite++] = GtbProbe.ROOK; break; case Piece.WBISHOP: whiteSquares[nWhite] = sq; whitePieces[nWhite++] = GtbProbe.BISHOP; break; case Piece.WKNIGHT: whiteSquares[nWhite] = sq; whitePieces[nWhite++] = GtbProbe.KNIGHT; break; case Piece.WPAWN: whiteSquares[nWhite] = sq; whitePieces[nWhite++] = GtbProbe.PAWN; break; case Piece.BKING: blackSquares[nBlack] = sq; blackPieces[nBlack++] = GtbProbe.KING; break; case Piece.BQUEEN: blackSquares[nBlack] = sq; blackPieces[nBlack++] = GtbProbe.QUEEN; break; case Piece.BROOK: blackSquares[nBlack] = sq; blackPieces[nBlack++] = GtbProbe.ROOK; break; case Piece.BBISHOP: blackSquares[nBlack] = sq; blackPieces[nBlack++] = GtbProbe.BISHOP; break; case Piece.BKNIGHT: blackSquares[nBlack] = sq; blackPieces[nBlack++] = GtbProbe.KNIGHT; break; case Piece.BPAWN: blackSquares[nBlack] = sq; blackPieces[nBlack++] = GtbProbe.PAWN; break; } } whiteSquares[nWhite] = GtbProbe.NOSQUARE; blackSquares[nBlack] = GtbProbe.NOSQUARE; whitePieces[nWhite] = GtbProbe.NOPIECE; blackPieces[nBlack] = GtbProbe.NOPIECE; int epSquare = pos.getEpSquare(); if (epSquare == -1) epSquare = GtbProbe.NOSQUARE; int[] result = new int[2]; boolean res = false; if (nWhite + nBlack <= 5) { gtb.initIfNeeded(); res = gtb.probeHard(pos.whiteMove, epSquare, castleMask, whiteSquares, blackSquares, whitePieces, blackPieces, result); } GtbProbeResult ret = new GtbProbeResult(); if (res) { switch (result[0]) { case GtbProbe.DRAW: ret.result = GtbProbeResult.DRAW; ret.pliesToMate = 0; break; case GtbProbe.WMATE: ret.result = GtbProbeResult.WMATE; ret.pliesToMate = result[1]; break; case GtbProbe.BMATE: ret.result = GtbProbeResult.BMATE; ret.pliesToMate = result[1]; break; default: ret.result = GtbProbeResult.UNKNOWN; ret.pliesToMate = 0; break; } } else { ret.result = GtbProbeResult.UNKNOWN; ret.pliesToMate = 0; } return ret; } private final ProbeResult rtbProbe(Position pos) { if (pos.nPieces() > 6) return new ProbeResult(ProbeResult.Type.NONE, 0, 0); // Make sure position is valid. Required by native move generation code. try { TextIO.readFEN(TextIO.toFEN(pos)); } catch (ChessParseError ex) { return new ProbeResult(ProbeResult.Type.NONE, 0, 0); } rtb.initIfNeeded(); byte[] squares = new byte[64]; for (int sq = 0; sq < 64; sq++) squares[sq] = (byte)pos.getPiece(sq); int[] result = new int[2]; rtb.probe(squares, pos.whiteMove, pos.getEpSquare(), pos.getCastleMask(), pos.halfMoveClock, pos.fullMoveCounter, result); int wdl = 0; if (result[1] != RtbProbe.NOINFO) { int score = result[1]; if (score > 0) { wdl = 1; } else if (score < 0) { wdl = -1; score = -score; } return new ProbeResult(ProbeResult.Type.DTZ, wdl, score); } else if (result[0] != RtbProbe.NOINFO) { return new ProbeResult(ProbeResult.Type.WDL, result[0], 0); } else { return new ProbeResult(ProbeResult.Type.NONE, 0, 0); } } final ProbeResult probe(Position pos) { GtbProbeResult gtbRes = gtbProbe(pos); if (gtbRes.result != GtbProbeResult.UNKNOWN) { int wdl = 0; int score = 0; if (gtbRes.result == GtbProbeResult.WMATE) { wdl = 1; score = gtbRes.pliesToMate; } else if (gtbRes.result == GtbProbeResult.BMATE) { wdl = -1; score = gtbRes.pliesToMate; } if (!pos.whiteMove) wdl = -wdl; return new ProbeResult(ProbeResult.Type.DTM, wdl, score); } return rtbProbe(pos); } /** Return a list of all moves in moveList that are not known to be non-optimal. * Returns null if no legal move could be excluded. */ public final ArrayList<Move> removeNonOptimal(Position pos, ArrayList<Move> moveList) { ArrayList<Move> optimalMoves = new ArrayList<Move>(); ArrayList<Move> unknownMoves = new ArrayList<Move>(); final int MATE0 = 100000; int bestScore = -1000000; UndoInfo ui = new UndoInfo(); for (Move m : moveList) { pos.makeMove(m, ui); int pliesToDraw = Math.max(100 - pos.halfMoveClock, 1); GtbProbeResult res = gtbProbe(pos); pos.unMakeMove(m, ui); if (res.result == GtbProbeResult.UNKNOWN) { unknownMoves.add(m); } else { int wScore; if (res.result == GtbProbeResult.WMATE) { if (res.pliesToMate <= pliesToDraw) wScore = MATE0 - res.pliesToMate; else wScore = 1; } else if (res.result == GtbProbeResult.BMATE) { if (res.pliesToMate <= pliesToDraw) wScore = -(MATE0 - res.pliesToMate); else wScore = -1; } else { wScore = 0; } int score = pos.whiteMove ? wScore : -wScore; if (score > bestScore) { optimalMoves.clear(); optimalMoves.add(m); bestScore = score; } else if (score == bestScore) { optimalMoves.add(m); } else { // Ignore move } } } for (Move m : unknownMoves) optimalMoves.add(m); return (optimalMoves.size() < moveList.size()) ? optimalMoves : null; } /** For a given position and from square, return EGTB information * about all legal destination squares. Return null if no information available. */ public final ArrayList<Pair<Integer,ProbeResult>> movePieceProbe(Position pos, int fromSq) { int p = pos.getPiece(fromSq); if ((p == Piece.EMPTY) || (pos.whiteMove != Piece.isWhite(p))) return null; ArrayList<Pair<Integer,ProbeResult>> ret = new ArrayList<Pair<Integer,ProbeResult>>(); ArrayList<Move> moveList = new MoveGen().legalMoves(pos); UndoInfo ui = new UndoInfo(); for (Move m : moveList) { if (m.from != fromSq) continue; pos.makeMove(m, ui); boolean isZeroing = pos.halfMoveClock == 0; ProbeResult res = probe(pos); pos.unMakeMove(m, ui); if (res.type == ProbeResult.Type.NONE) continue; res.wdl = -res.wdl; if (isZeroing && (res.type == ProbeResult.Type.DTZ)) { res.score = 1; } else if (res.type != ProbeResult.Type.WDL) { res.score++; } ret.add(new Pair<Integer,ProbeResult>(m.to, res)); } return ret; } /** For a given position and from square, return EGTB information * about all legal alternative positions for the piece on from square. * Return null if no information is available. */ public final ArrayList<Pair<Integer,ProbeResult>> relocatePieceProbe(Position pos, int fromSq) { int p = pos.getPiece(fromSq); if (p == Piece.EMPTY) return null; boolean isPawn = (Piece.makeWhite(p) == Piece.WPAWN); ArrayList<Pair<Integer,ProbeResult>> ret = new ArrayList<Pair<Integer,ProbeResult>>(); for (int sq = 0; sq < 64; sq++) { if ((sq != fromSq) && (pos.getPiece(sq) != Piece.EMPTY)) continue; if (isPawn && ((sq < 8) || (sq >= 56))) continue; pos.setPiece(fromSq, Piece.EMPTY); pos.setPiece(sq, p); ProbeResult res = probe(pos); pos.setPiece(sq, Piece.EMPTY); pos.setPiece(fromSq, p); if (res.type == ProbeResult.Type.NONE) continue; if (!pos.whiteMove) res.wdl = -res.wdl; ret.add(new Pair<Integer,ProbeResult>(sq, res)); } return ret; } }