/* 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 guibase; import chess.ChessParseError; import chess.ComputerPlayer; import chess.Game; import chess.HumanPlayer; import chess.Move; import chess.MoveGen; import chess.Piece; import chess.Player; import chess.Position; import chess.Search; import chess.TextIO; import chess.UndoInfo; import chess.Game.GameState; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.List; import java.util.Scanner; /** * The glue between the chess engine and the GUI. * @author petero */ public class ChessController { Player humanPlayer; ComputerPlayer computerPlayer; Game game; GUIInterface gui; boolean humanIsWhite; Thread computerThread; int threadStack; // Thread stack size, or zero to use OS default // Search statistics String thinkingPV; class SearchListener implements Search.Listener { int currDepth = 0; int currMoveNr = 0; String currMove = ""; long currNodes = 0; int currNps = 0; int currTime = 0; int pvDepth = 0; int pvScore = 0; boolean pvIsMate = false; boolean pvUpperBound = false; boolean pvLowerBound = false; String pvStr = ""; private void setSearchInfo() { StringBuilder buf = new StringBuilder(); buf.append(String.format("%n[%d] ", pvDepth)); if (pvUpperBound) { buf.append("<="); } else if (pvLowerBound) { buf.append(">="); } if (pvIsMate) { buf.append(String.format("m%d", pvScore)); } else { buf.append(String.format("%.2f", pvScore / 100.0)); } buf.append(pvStr); buf.append(String.format("%n")); buf.append(String.format("d:%d %d:%s t:%.2f n:%d nps:%d", currDepth, currMoveNr, currMove, currTime / 1000.0, currNodes, currNps)); final String newPV = buf.toString(); gui.runOnUIThread(new Runnable() { public void run() { thinkingPV = newPV; setThinkingPV(); } }); } public void notifyDepth(int depth) { currDepth = depth; setSearchInfo(); } public void notifyCurrMove(Move m, int moveNr) { currMove = TextIO.moveToString(new Position(game.pos), m, false); currMoveNr = moveNr; setSearchInfo(); } public void notifyPV(int depth, int score, int time, long nodes, int nps, boolean isMate, boolean upperBound, boolean lowerBound, ArrayList<Move> pv) { pvDepth = depth; pvScore = score; currTime = time; currNodes = nodes; currNps = nps; pvIsMate = isMate; pvUpperBound = upperBound; pvLowerBound = lowerBound; StringBuilder buf = new StringBuilder(); Position pos = new Position(game.pos); UndoInfo ui = new UndoInfo(); for (Move m : pv) { buf.append(String.format(" %s", TextIO.moveToString(pos, m, false))); pos.makeMove(m, ui); } pvStr = buf.toString(); setSearchInfo(); } public void notifyStats(long nodes, int nps, int time) { currNodes = nodes; currNps = nps; currTime = time; setSearchInfo(); } } SearchListener listener; public ChessController(GUIInterface gui) { this.gui = gui; listener = new SearchListener(); thinkingPV = ""; threadStack = 0; } public void setThreadStackSize(int size) { threadStack = size; } public final void newGame(boolean humanIsWhite, int ttLogSize, boolean verbose) { stopComputerThinking(); this.humanIsWhite = humanIsWhite; humanPlayer = new HumanPlayer(); computerPlayer = new ComputerPlayer(); computerPlayer.verbose = verbose; computerPlayer.setTTLogSize(ttLogSize); computerPlayer.setListener(listener); if (humanIsWhite) { game = new Game(humanPlayer, computerPlayer); } else { game = new Game(computerPlayer, humanPlayer); } } public final void startGame() { gui.setSelection(-1); updateGUI(); startComputerThinking(); } public final void setPosHistory(List<String> posHistStr) { try { String fen = posHistStr.get(0); Position pos = TextIO.readFEN(fen); game.processString("new"); game.pos = pos; String[] strArr = posHistStr.get(1).split(" "); final int arrLen = strArr.length; for (int i = 0; i < arrLen; i++) { game.processString(strArr[i]); } int numUndo = Integer.parseInt(posHistStr.get(2)); for (int i = 0; i < numUndo; i++) { game.processString("undo"); } } catch (ChessParseError e) { // Just ignore invalid positions } } public final List<String> getPosHistory() { return game.getPosHistory(); } public String getFEN() { return TextIO.toFEN(game.pos); } /** Convert current game to PGN format. */ public String getPGN() { StringBuilder pgn = new StringBuilder(); List<String> posHist = getPosHistory(); String fen = posHist.get(0); String moves = game.getMoveListString(true); if (game.getGameState() == GameState.ALIVE) moves += " *"; int year, month, day; { Calendar now = GregorianCalendar.getInstance(); year = now.get(Calendar.YEAR); month = now.get(Calendar.MONTH) + 1; day = now.get(Calendar.DAY_OF_MONTH); } pgn.append(String.format("[Date \"%04d.%02d.%02d\"]%n", year, month, day)); String white = "Player"; String black = ComputerPlayer.engineName; if (!humanIsWhite) { String tmp = white; white = black; black = tmp; } pgn.append(String.format("[White \"%s\"]%n", white)); pgn.append(String.format("[Black \"%s\"]%n", black)); pgn.append(String.format("[Result \"%s\"]%n", game.getPGNResultString())); if (!fen.equals(TextIO.startPosFEN)) { pgn.append(String.format("[FEN \"%s\"]%n", fen)); pgn.append("[SetUp \"1\"]\n"); } pgn.append("\n"); String[] strArr = moves.split(" "); int currLineLength = 0; final int arrLen = strArr.length; for (int i = 0; i < arrLen; i++) { String move = strArr[i].trim(); int moveLen = move.length(); if (moveLen > 0) { if (currLineLength + 1 + moveLen >= 80) { pgn.append("\n"); pgn.append(move); currLineLength = moveLen; } else { if (currLineLength > 0) { pgn.append(" "); currLineLength++; } pgn.append(move); currLineLength += moveLen; } } } pgn.append("\n\n"); return pgn.toString(); } public void setPGN(String pgn) throws ChessParseError { // First pass, remove comments { StringBuilder out = new StringBuilder(); Scanner sc = new Scanner(pgn); sc.useDelimiter(""); while (sc.hasNext()) { String c = sc.next(); if (c.equals("{")) { sc.skip("[^}]*}"); } else if (c.equals(";")) { sc.skip("[^\n]*\n"); } else { out.append(c); } } pgn = out.toString(); } // Parse tag section Position pos = TextIO.readFEN(TextIO.startPosFEN); Scanner sc = new Scanner(pgn); sc.useDelimiter("\\s+"); while (sc.hasNext("\\[.*")) { String tagName = sc.next(); if (tagName.length() > 1) { tagName = tagName.substring(1); } else { tagName = sc.next(); } String tagValue = sc.findWithinHorizon(".*\\]", 0); tagValue = tagValue.trim(); if (tagValue.charAt(0) == '"') tagValue = tagValue.substring(1); if (tagValue.charAt(tagValue.length()-1) == ']') tagValue = tagValue.substring(0, tagValue.length() - 1); if (tagValue.charAt(tagValue.length()-1) == '"') tagValue = tagValue.substring(0, tagValue.length() - 1); if (tagName.equals("FEN")) { pos = TextIO.readFEN(tagValue); } } game.processString("new"); game.pos = pos; // Handle (ignore) recursive annotation variations { StringBuilder out = new StringBuilder(); sc.useDelimiter(""); int level = 0; while (sc.hasNext()) { String c = sc.next(); if (c.equals("(")) { level++; } else if (c.equals(")")) { level--; } else if (level == 0) { out.append(c); } } pgn = out.toString(); } // Parse move text section sc = new Scanner(pgn); sc.useDelimiter("\\s+"); while (sc.hasNext()) { String strMove = sc.next(); strMove = strMove.replaceFirst("\\$?[0-9]*\\.*([^?!]*)[?!]*", "$1"); if (strMove.length() == 0) continue; Move m = TextIO.stringToMove(game.pos, strMove); if (m == null) break; game.processString(strMove); } } public void setFENOrPGN(String fenPgn) throws ChessParseError { try { Position pos = TextIO.readFEN(fenPgn); game.processString("new"); game.pos = pos; } catch (ChessParseError e) { // Try read as PGN instead setPGN(fenPgn); } gui.setSelection(-1); updateGUI(); startComputerThinking(); } /** Set color for human player. Doesn't work when computer is thinking. */ public final void setHumanWhite(final boolean humanIsWhite) { if (computerThread != null) return; if (this.humanIsWhite != humanIsWhite) { this.humanIsWhite = humanIsWhite; game.processString("swap"); startComputerThinking(); } } public final boolean humansTurn() { return game.pos.whiteMove == humanIsWhite; } public final boolean computerThinking() { return computerThread != null; } public final void takeBackMove() { if (humansTurn()) { if (game.getLastMove() != null) { game.processString("undo"); if (game.getLastMove() != null) { game.processString("undo"); } else { game.processString("redo"); } updateGUI(); setSelection(); } } else if (game.getGameState() != Game.GameState.ALIVE) { if (game.getLastMove() != null) { game.processString("undo"); if (!humansTurn()) { if (game.getLastMove() != null) { game.processString("undo"); } else { game.processString("redo"); } } updateGUI(); setSelection(); } } } public final void redoMove() { if (humansTurn()) { game.processString("redo"); game.processString("redo"); updateGUI(); setSelection(); } } public final void humanMove(Move m) { if (humansTurn()) { if (doMove(m)) { updateGUI(); startComputerThinking(); } else { gui.setSelection(-1); } } } Move promoteMove; public final void reportPromotePiece(int choice) { final boolean white = game.pos.whiteMove; int promoteTo; switch (choice) { case 1: promoteTo = white ? Piece.WROOK : Piece.BROOK; break; case 2: promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP; break; case 3: promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT; break; default: promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN; break; } promoteMove.promoteTo = promoteTo; Move m = promoteMove; promoteMove = null; humanMove(m); } /** * Move a piece from one square to another. * @return True if the move was legal, false otherwise. */ final private boolean doMove(Move move) { Position pos = game.pos; MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos); MoveGen.removeIllegal(pos, moves); int promoteTo = move.promoteTo; for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; if ((m.from == move.from) && (m.to == move.to)) { if ((m.promoteTo != Piece.EMPTY) && (promoteTo == Piece.EMPTY)) { promoteMove = m; gui.requestPromotePiece(); return false; } if (m.promoteTo == promoteTo) { String strMove = TextIO.moveToString(pos, m, false); game.processString(strMove); return true; } } } gui.reportInvalidMove(move); return false; } final private void updateGUI() { setStatusString(); setMoveList(); setThinkingPV(); gui.setPosition(game.pos); } final private void setStatusString() { String str = game.pos.whiteMove ? "White's move" : "Black's move"; if (computerThread != null) str += " (thinking)"; if (game.getGameState() != Game.GameState.ALIVE) { str = game.getGameStateString(); } gui.setStatusString(str); } public final void setMoveList() { String str = game.getMoveListString(true); gui.setMoveListString(str); } public final void setThinkingPV() { String str = ""; if (gui.showThinking()) { str = thinkingPV; } gui.setThinkingString(str); } final private void setSelection() { Move m = game.getLastMove(); int sq = (m != null) ? m.to : -1; gui.setSelection(sq); } private void startComputerThinking() { if (game.pos.whiteMove != humanIsWhite) { if (computerThread == null) { Runnable run = new Runnable() { public void run() { computerPlayer.timeLimit(gui.timeLimit(), gui.timeLimit(), gui.randomMode()); final String cmd = computerPlayer.getCommand(new Position(game.pos), game.haveDrawOffer(), game.getHistory()); gui.runOnUIThread(new Runnable() { public void run() { game.processString(cmd); thinkingPV = ""; updateGUI(); setSelection(); stopComputerThinking(); } }); } }; if (threadStack > 0) { ThreadGroup tg = new ThreadGroup("searcher"); computerThread = new Thread(tg, run, "searcher", threadStack); } else { computerThread = new Thread(run); } thinkingPV = ""; updateGUI(); computerThread.start(); } } } public synchronized void stopComputerThinking() { if (computerThread != null) { computerPlayer.timeLimit(0, 0, false); try { computerThread.join(); } catch (InterruptedException ex) { System.out.printf("Could not stop thread%n"); } computerThread = null; updateGUI(); } } public synchronized void setTimeLimit() { if (computerThread != null) { computerPlayer.timeLimit(gui.timeLimit(), gui.timeLimit(), gui.randomMode()); } } }