/* 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.engine.cuckoochess; import chess.ChessParseError; import chess.ComputerPlayer; import chess.Move; import chess.Position; import chess.TextIO; import java.util.ArrayList; import org.petero.droidfish.EngineOptions; import org.petero.droidfish.engine.LocalPipe; import org.petero.droidfish.engine.UCIEngineBase; /** * UCI interface to cuckoochess engine. * @author petero */ public class CuckooChessEngine extends UCIEngineBase { // Data set by the "position" command. private Position pos; private ArrayList<Move> moves; // Engine data private DroidEngineControl engine; // Set to true to break out of main loop private boolean quit; private LocalPipe guiToEngine; private LocalPipe engineToGui; private Thread engineThread; public CuckooChessEngine(Report report) { pos = null; moves = new ArrayList<Move>(); quit = false; guiToEngine = new LocalPipe(); engineToGui = new LocalPipe(); } /** @inheritDoc */ @Override protected final void startProcess() { engineThread = new Thread(new Runnable() { public void run() { mainLoop(guiToEngine, engineToGui); } }); int pMin = Thread.MIN_PRIORITY; int pNorm = Thread.NORM_PRIORITY; int prio = pMin + (pNorm - pMin) / 2; engineThread.setPriority(prio); engineThread.start(); } /** @inheritDoc */ @Override public final void initOptions(EngineOptions engineOptions) { super.initOptions(engineOptions); } /** @inheritDoc */ @Override public boolean optionsOk(EngineOptions engineOptions) { return true; } /** @inheritDoc */ @Override public final void setStrength(int strength) { setOption("strength", strength); } private final void mainLoop(LocalPipe is, LocalPipe os) { String line; while ((line = is.readLine()) != null) { handleCommand(line, os); if (quit) { break; } } } @Override public void shutDown() { super.shutDown(); guiToEngine.close(); } /** @inheritDoc */ @Override public final String readLineFromEngine(int timeoutMillis) { if ((engineThread != null) && !engineThread.isAlive()) return null; String ret = engineToGui.readLine(timeoutMillis); if (ret == null) return null; if (ret.length() > 0) { // System.out.printf("Engine -> GUI: %s\n", ret); } return ret; } /** @inheritDoc */ @Override public final synchronized void writeLineToEngine(String data) { // System.out.printf("GUI -> Engine: %s\n", data); guiToEngine.addLine(data); } private final void handleCommand(String cmdLine, LocalPipe os) { String[] tokens = tokenize(cmdLine); try { String cmd = tokens[0]; if (cmd.equals("uci")) { os.printLine("id name %s", ComputerPlayer.engineName); os.printLine("id author Peter Osterlund"); DroidEngineControl.printOptions(os); os.printLine("uciok"); } else if (cmd.equals("isready")) { initEngine(os); os.printLine("readyok"); } else if (cmd.equals("setoption")) { initEngine(os); StringBuilder optionName = new StringBuilder(); StringBuilder optionValue = new StringBuilder(); if (tokens[1].endsWith("name")) { int idx = 2; while ((idx < tokens.length) && !tokens[idx].equals("value")) { optionName.append(tokens[idx++].toLowerCase()); optionName.append(' '); } if ((idx < tokens.length) && tokens[idx++].equals("value")) { while ((idx < tokens.length)) { optionValue.append(tokens[idx++].toLowerCase()); optionValue.append(' '); } } engine.setOption(optionName.toString().trim(), optionValue.toString().trim()); } } else if (cmd.equals("ucinewgame")) { if (engine != null) { engine.newGame(); } } else if (cmd.equals("position")) { String fen = null; int idx = 1; if (tokens[idx].equals("startpos")) { idx++; fen = TextIO.startPosFEN; } else if (tokens[idx].equals("fen")) { idx++; StringBuilder sb = new StringBuilder(); while ((idx < tokens.length) && !tokens[idx].equals("moves")) { sb.append(tokens[idx++]); sb.append(' '); } fen = sb.toString().trim(); } if (fen != null) { pos = TextIO.readFEN(fen); moves.clear(); if ((idx < tokens.length) && tokens[idx++].equals("moves")) { for (int i = idx; i < tokens.length; i++) { Move m = TextIO.uciStringToMove(tokens[i]); if (m != null) { moves.add(m); } else { break; } } } } } else if (cmd.equals("go")) { initEngine(os); int idx = 1; SearchParams sPar = new SearchParams(); boolean ponder = false; while (idx < tokens.length) { String subCmd = tokens[idx++]; if (subCmd.equals("searchmoves")) { while (idx < tokens.length) { Move m = TextIO.uciStringToMove(tokens[idx]); if (m != null) { sPar.searchMoves.add(m); idx++; } else { break; } } } else if (subCmd.equals("ponder")) { ponder = true; } else if (subCmd.equals("wtime")) { sPar.wTime = Integer.parseInt(tokens[idx++]); } else if (subCmd.equals("btime")) { sPar.bTime = Integer.parseInt(tokens[idx++]); } else if (subCmd.equals("winc")) { sPar.wInc = Integer.parseInt(tokens[idx++]); } else if (subCmd.equals("binc")) { sPar.bInc = Integer.parseInt(tokens[idx++]); } else if (subCmd.equals("movestogo")) { sPar.movesToGo = Integer.parseInt(tokens[idx++]); } else if (subCmd.equals("depth")) { sPar.depth = Integer.parseInt(tokens[idx++]); } else if (subCmd.equals("nodes")) { sPar.nodes = Integer.parseInt(tokens[idx++]); } else if (subCmd.equals("mate")) { sPar.mate = Integer.parseInt(tokens[idx++]); } else if (subCmd.equals("movetime")) { sPar.moveTime = Integer.parseInt(tokens[idx++]); } else if (subCmd.equals("infinite")) { sPar.infinite = true; } } if (pos == null) { try { pos = TextIO.readFEN(TextIO.startPosFEN); } catch (ChessParseError ex) { throw new RuntimeException(); } } if (ponder) { engine.startPonder(pos, moves, sPar); } else { engine.startSearch(pos, moves, sPar); } } else if (cmd.equals("stop")) { engine.stopSearch(); } else if (cmd.equals("ponderhit")) { engine.ponderHit(); } else if (cmd.equals("quit")) { if (engine != null) { engine.stopSearch(); } quit = true; } } catch (ChessParseError ex) { } catch (ArrayIndexOutOfBoundsException e) { } catch (NumberFormatException nfe) { } } private final void initEngine(LocalPipe os) { if (engine == null) { engine = new DroidEngineControl(os); } } /** Convert a string to tokens by splitting at whitespace characters. */ private final String[] tokenize(String cmdLine) { cmdLine = cmdLine.trim(); return cmdLine.split("\\s+"); } }