From 5c161d21014fe4c243ac3fc8bbf8ec32bfc47148 Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Sun, 19 Aug 2012 10:05:42 +0000 Subject: [PATCH] DroidFish: Display chess moves using localized piece names. --- CuckooChessEngine/src/chess/MoveGen.java | 2 +- DroidFish/res/values-de/strings.xml | 3 +- DroidFish/res/values-es/strings.xml | 1 + DroidFish/res/values-pt/strings.xml | 5 +- DroidFish/res/values-ru/strings.xml | 1 + DroidFish/res/values/strings.xml | 22 ++++- DroidFish/res/xml/preferences.xml | 8 ++ .../src/org/petero/droidfish/DroidFish.java | 14 +-- .../src/org/petero/droidfish/PGNOptions.java | 6 ++ .../org/petero/droidfish/book/DroidBook.java | 5 +- .../droidfish/engine/DroidComputerPlayer.java | 10 +- .../gamelogic/DroidChessController.java | 57 ++++++++---- .../org/petero/droidfish/gamelogic/Game.java | 6 +- .../petero/droidfish/gamelogic/GameTree.java | 92 ++++++++++++++++--- .../petero/droidfish/gamelogic/MoveGen.java | 9 +- .../droidfish/gamelogic/SearchListener.java | 15 --- .../petero/droidfish/gamelogic/TextIO.java | 68 ++++++++++---- .../org/petero/droidfish/book/BookTest.java | 2 +- .../petero/droidfish/gamelogic/GameTest.java | 2 +- .../droidfish/gamelogic/GameTreeTest.java | 4 +- .../droidfish/gamelogic/MoveGenTest.java | 2 +- .../droidfish/gamelogic/TextIOTest.java | 48 +++++----- 22 files changed, 271 insertions(+), 111 deletions(-) diff --git a/CuckooChessEngine/src/chess/MoveGen.java b/CuckooChessEngine/src/chess/MoveGen.java index 28cf3c8..91e7b65 100644 --- a/CuckooChessEngine/src/chess/MoveGen.java +++ b/CuckooChessEngine/src/chess/MoveGen.java @@ -48,7 +48,7 @@ public final class MoveGen { /** * Generate and return a list of pseudo-legal moves. - * Pseudo-legal means that the moves doesn't necessarily defend from check threats. + * Pseudo-legal means that the moves don't necessarily defend from check threats. */ public final MoveList pseudoLegalMoves(Position pos) { MoveList moveList = getMoveListObj(); diff --git a/DroidFish/res/values-de/strings.xml b/DroidFish/res/values-de/strings.xml index 37de4d9..3c92bb9 100644 --- a/DroidFish/res/values-de/strings.xml +++ b/DroidFish/res/values-de/strings.xml @@ -108,7 +108,7 @@ wenn Sie es nicht aktiv nutzen.\ Dateiname: Halbzug: Zugnummer: - Filtertext + Suche… Wert (%): Ereignis: Ort: @@ -171,6 +171,7 @@ wenn Sie es nicht aktiv nutzen.\ UCI-Protokollfehler Neue Partie starten? Nutzen Sie die CuckooChess-Engine für eine noch geringere Spielstärke. + B S L T D K Zuwenig Felder Unzulässige Figur Unzulässige Seite diff --git a/DroidFish/res/values-es/strings.xml b/DroidFish/res/values-es/strings.xml index ccde774..54e7de3 100644 --- a/DroidFish/res/values-es/strings.xml +++ b/DroidFish/res/values-es/strings.xml @@ -169,6 +169,7 @@ Si está usted utilizando la batería, se recomienda que cambie los ajustes para Fallo en el protocolo UCI ¿Empezar nueva partida? Utilice el motor CuckooChess para rebajar aún más el nivel de juego. + P C A T D R Pocos espacios Pieza incorrecta Lado incorrecto diff --git a/DroidFish/res/values-pt/strings.xml b/DroidFish/res/values-pt/strings.xml index a86439a..a8823e9 100644 --- a/DroidFish/res/values-pt/strings.xml +++ b/DroidFish/res/values-pt/strings.xml @@ -107,7 +107,7 @@ não estiver usando o programa diretamente.\ Arquivo: Relógio para meio-lance: Contador de lances completos: - Pesquisar... + Pesquisar… Valor(%): Evento: Site: @@ -170,6 +170,7 @@ não estiver usando o programa diretamente.\ Erro de protocolo UCI Iniciar nova partida? Use o CuckooChess para um nível ainda menor. + P C B T D R Poucos espaços Peça inválida Lado inválido @@ -238,7 +239,7 @@ não estiver usando o programa diretamente.\ Modo tela cheia oculta a barra de notificações Desabilita apagar a tela automaticamente Rótulos nas casas - Mostrar rótulos nas casas: a-h e 1-8 + Mostrar rótulos nas casas: a–h e 1–8 Velocidade do movimento Velocidade do movimento para navegação em partida Inverter direção do movimento diff --git a/DroidFish/res/values-ru/strings.xml b/DroidFish/res/values-ru/strings.xml index ef5e92d..9288c77 100644 --- a/DroidFish/res/values-ru/strings.xml +++ b/DroidFish/res/values-ru/strings.xml @@ -159,6 +159,7 @@ Ошибка UCI протокола Начать новую партию? Использовать движок CuckooChess для наименьшего уровня сложности. + П К С Л Ф Кр Слишком малое пространство Неверная фигура Неверное местоположение diff --git a/DroidFish/res/values/strings.xml b/DroidFish/res/values/strings.xml index 4626916..1dda1de 100644 --- a/DroidFish/res/values/strings.xml +++ b/DroidFish/res/values/strings.xml @@ -10,6 +10,7 @@ 2 2 1000000 + 1 \ CPU Usage\n\ If you leave DroidFish running in the background and GameMode is set to \ @@ -185,6 +186,7 @@ you are not actively using the program.\ Invalid network port Start New Game? Use the CuckooChess engine for even lower strength. + P N B R Q K Too few spaces Invalid piece Invalid side @@ -323,6 +325,8 @@ you are not actively using the program.\ Include numeric annotation glyphs (NAGs), such as ! and ? Headers Show PGN header lines + Pieces + Control how chess pieces are displayed PGN import Variations Include non-mainline moves @@ -366,7 +370,7 @@ you are not actively using the program.\ 4 6 8 - + 0 1 @@ -375,7 +379,15 @@ you are not actively using the program.\ 4 6 8 - + + + English letters + Local language letters + + + 0 + 1 + 16 MB 32 MB @@ -385,7 +397,7 @@ you are not actively using the program.\ 512 MB 1024 MB 2048 MB - + 16 32 @@ -395,7 +407,7 @@ you are not actively using the program.\ 512 1024 2048 - + Whole Game 1 move @@ -527,7 +539,7 @@ you are not actively using the program.\ 50 moves Unlimited - + 5 10 15 diff --git a/DroidFish/res/xml/preferences.xml b/DroidFish/res/xml/preferences.xml index c9550cc..a157c9b 100644 --- a/DroidFish/res/xml/preferences.xml +++ b/DroidFish/res/xml/preferences.xml @@ -513,6 +513,14 @@ android:summary="@string/prefs_viewHeaders_summary" android:defaultValue="false"> + + diff --git a/DroidFish/src/org/petero/droidfish/DroidFish.java b/DroidFish/src/org/petero/droidfish/DroidFish.java index a4bf14a..9e1eff2 100644 --- a/DroidFish/src/org/petero/droidfish/DroidFish.java +++ b/DroidFish/src/org/petero/droidfish/DroidFish.java @@ -126,7 +126,6 @@ public class DroidFish extends Activity implements GUIInterface { // FIXME!!! PGN view option: game continuation (for training) // FIXME!!! Remove invalid playerActions in PGN import (should be done in verifyChildren) // FIXME!!! Implement bookmark mechanism for positions in pgn files - // FIXME!!! Display chess notation in local language // FIXME!!! Add support for "Chess Leipzig" font // FIXME!!! Implement figurine notation @@ -253,7 +252,7 @@ public class DroidFish extends Activity implements GUIInterface { public void run() { pgnOptions.view.variations = toggleBooleanPref("viewVariations"); gameTextListener.clear(); - ctrl.prefsChanged(); + ctrl.prefsChanged(false); } }); addAction(new UIAction() { @@ -264,7 +263,7 @@ public class DroidFish extends Activity implements GUIInterface { public void run() { pgnOptions.view.comments = toggleBooleanPref("viewComments"); gameTextListener.clear(); - ctrl.prefsChanged(); + ctrl.prefsChanged(false); } }); addAction(new UIAction() { @@ -275,7 +274,7 @@ public class DroidFish extends Activity implements GUIInterface { public void run() { pgnOptions.view.headers = toggleBooleanPref("viewHeaders"); gameTextListener.clear(); - ctrl.prefsChanged(); + ctrl.prefsChanged(false); } }); addAction(new UIAction() { @@ -352,6 +351,7 @@ public class DroidFish extends Activity implements GUIInterface { custom3ButtonActions = new ButtonActions("custom3", CUSTOM3_BUTTON_DIALOG, R.string.select_action); + TextIO.setPieceNames(getString(R.string.piece_names)); initUI(true); gameTextListener = new PgnScreenText(pgnOptions); @@ -808,6 +808,8 @@ public class DroidFish extends Activity implements GUIInterface { pgnOptions.view.comments = settings.getBoolean("viewComments", true); pgnOptions.view.nag = settings.getBoolean("viewNAG", true); pgnOptions.view.headers = settings.getBoolean("viewHeaders", false); + final int oldViewPieceType = pgnOptions.view.pieceType; + pgnOptions.view.pieceType = getIntSetting("viewPieceType", PGNOptions.PT_LOCAL); pgnOptions.imp.variations = settings.getBoolean("importVariations", true); pgnOptions.imp.comments = settings.getBoolean("importComments", true); pgnOptions.imp.nag = settings.getBoolean("importNAG", true); @@ -821,7 +823,7 @@ public class DroidFish extends Activity implements GUIInterface { cb.setColors(); gameTextListener.clear(); - ctrl.prefsChanged(); + ctrl.prefsChanged(oldViewPieceType != pgnOptions.view.pieceType); } private void updateButtons() { @@ -1829,7 +1831,7 @@ public class DroidFish extends Activity implements GUIInterface { ColorTheme.instance().setTheme(settings, item); cb.setColors(); gameTextListener.clear(); - ctrl.prefsChanged(); + ctrl.prefsChanged(false); dialog.dismiss(); } }); diff --git a/DroidFish/src/org/petero/droidfish/PGNOptions.java b/DroidFish/src/org/petero/droidfish/PGNOptions.java index 8cd02d9..5577103 100644 --- a/DroidFish/src/org/petero/droidfish/PGNOptions.java +++ b/DroidFish/src/org/petero/droidfish/PGNOptions.java @@ -20,11 +20,15 @@ package org.petero.droidfish; /** Settings controlling PGN import/export */ public class PGNOptions { + public static final int PT_ENGLISH = 0; // Piece type english letters + public static final int PT_LOCAL = 1; // Piece type local language letters + public static class Viewer { public boolean variations; public boolean comments; public boolean nag; public boolean headers; + public int pieceType; } public static class Import { public boolean variations; @@ -39,6 +43,7 @@ public class PGNOptions { public boolean clockInfo; public boolean pgnPromotions; public boolean moveNrAfterNag; + public int pieceType; } public Viewer view; @@ -50,5 +55,6 @@ public class PGNOptions { imp = new Import(); exp = new Export(); exp.moveNrAfterNag = true; + exp.pieceType = PT_ENGLISH; } } diff --git a/DroidFish/src/org/petero/droidfish/book/DroidBook.java b/DroidFish/src/org/petero/droidfish/book/DroidBook.java index 9e710b9..575c9c5 100644 --- a/DroidFish/src/org/petero/droidfish/book/DroidBook.java +++ b/DroidFish/src/org/petero/droidfish/book/DroidBook.java @@ -114,7 +114,8 @@ 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(Position pos, + boolean localized) { StringBuilder ret = new StringBuilder(); ArrayList bookMoveList = new ArrayList(); List bookMoves = getBook().getBookEntries(pos); @@ -150,7 +151,7 @@ public final class DroidBook { for (BookEntry be : bookMoves) { Move m = be.move; bookMoveList.add(m); - String moveStr = TextIO.moveToString(pos, m, false); + String moveStr = TextIO.moveToString(pos, m, false, localized); if (first) first = false; else diff --git a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java index 2d2b078..70c500b 100644 --- a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java +++ b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java @@ -275,8 +275,8 @@ public class DroidComputerPlayer { } /** Return all book moves, both as a formatted string and as a list of moves. */ - public final Pair> getBookHints(Position pos) { - return book.getAllBookMoves(pos); + public final Pair> getBookHints(Position pos, boolean localized) { + return book.getAllBookMoves(pos, localized); } /** Get engine reported name. */ @@ -371,7 +371,9 @@ public class DroidComputerPlayer { Move bookMove = book.getBookMove(sr.currPos); if (bookMove != null) { if (canClaimDraw(sr.currPos, posHashList, posHashListSize, bookMove) == "") { - listener.notifySearchResult(sr.searchId, TextIO.moveToString(sr.currPos, bookMove, false), null); + listener.notifySearchResult(sr.searchId, + TextIO.moveToString(sr.currPos, bookMove, false, false), + null); return; } } @@ -803,7 +805,7 @@ public class DroidComputerPlayer { } else if (canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) { drawStr = "draw rep"; } else if (move != null) { - String strMove = TextIO.moveToString(pos, move, false); + String strMove = TextIO.moveToString(pos, move, false, false); posHashList[posHashListSize++] = pos.zobristHash(); UndoInfo ui = new UndoInfo(); pos.makeMove(move, ui); diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java index 8d2b93e..264ca6a 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java @@ -184,15 +184,22 @@ public class DroidChessController { } /** Notify controller that preferences has changed. */ - public final synchronized void prefsChanged() { + public final synchronized void prefsChanged(boolean translateMoves) { + if (game == null) + translateMoves = false; + if (translateMoves) + game.tree.translateMoves(); updateBookHints(); updateMoveList(); - listener.prefsChanged(searchId); + listener.prefsChanged(searchId, translateMoves); + if (translateMoves) + updateGUI(); } /** De-serialize from byte array. */ public final synchronized void fromByteArray(byte[] data) { game.fromByteArray(data); + game.tree.translateMoves(); } /** Serialize to byte array. */ @@ -221,6 +228,7 @@ public class DroidChessController { // Try read as PGN instead if (!newGame.readPGN(fenPgn, pgnOptions)) throw e; + newGame.tree.translateMoves(); } searchId++; game = newGame; @@ -554,7 +562,7 @@ public class DroidChessController { public final synchronized CommentInfo getComments() { Node cur = game.tree.currentNode; CommentInfo ret = new CommentInfo(); - ret.move = cur.moveStr; + ret.move = cur.moveStrLocal; ret.preComment = cur.preComment; ret.postComment = cur.postComment; ret.nag = cur.nag; @@ -571,11 +579,17 @@ public class DroidChessController { updateGUI(); } + /** Return true if localized piece names should be used. */ + private final boolean localPt() { + return pgnOptions.view.pieceType == PGNOptions.PT_LOCAL; + } + /** Engine search information receiver. */ private final class SearchListener implements org.petero.droidfish.gamelogic.SearchListener { private int currDepth = 0; private int currMoveNr = 0; - private String currMove = ""; + private Move currMove = null; + private String currMoveStr = ""; private int currNodes = 0; private int currNps = 0; private int currTime = 0; @@ -586,8 +600,10 @@ public class DroidChessController { private Move ponderMove = null; private ArrayList pvInfoV = new ArrayList(); + private int pvInfoSearchId = -1; // Search ID corresponding to pvInfoV public final void clearSearchInfo(int id) { + pvInfoSearchId = -1; ponderMove = null; pvInfoV.clear(); currDepth = 0; @@ -620,7 +636,7 @@ public class DroidChessController { buf.append(pvi.pvStr); } final String statStr = (currDepth > 0) ? - String.format("d:%d %d:%s t:%.2f n:%d nps:%d", currDepth, currMoveNr, currMove, + String.format("d:%d %d:%s t:%.2f n:%d nps:%d", currDepth, currMoveNr, currMoveStr, currTime / 1000.0, currNodes, currNps) : ""; final String newPV = buf.toString(); @@ -652,7 +668,8 @@ public class DroidChessController { @Override public void notifyCurrMove(int id, Position pos, Move m, int moveNr) { - currMove = TextIO.moveToString(pos, m, false); + currMove = m; + currMoveStr = TextIO.moveToString(pos, m, false, localPt()); currMoveNr = moveNr; setSearchInfo(id); } @@ -661,6 +678,7 @@ public class DroidChessController { @Override public void notifyPV(int id, Position pos, ArrayList pvInfo, Move ponderMove) { this.ponderMove = ponderMove; + this.pvInfoSearchId = id; pvInfoV = (ArrayList) pvInfo.clone(); for (PvInfo pv : pvInfo) { currTime = pv.time; @@ -671,7 +689,7 @@ public class DroidChessController { Position tmpPos = new Position(pos); UndoInfo ui = new UndoInfo(); if (ponderMove != null) { - String moveStr = TextIO.moveToString(tmpPos, ponderMove, false); + String moveStr = TextIO.moveToString(tmpPos, ponderMove, false, localPt()); buf.append(String.format(" [%s]", moveStr)); tmpPos.makeMove(ponderMove, ui); } @@ -680,7 +698,7 @@ public class DroidChessController { break; if (!TextIO.isValid(tmpPos, m)) break; - String moveStr = TextIO.moveToString(tmpPos, m, false); + String moveStr = TextIO.moveToString(tmpPos, m, false, localPt()); buf.append(String.format(" %s", moveStr)); tmpPos.makeMove(m, ui); } @@ -706,8 +724,15 @@ public class DroidChessController { setSearchInfo(id); } - public void prefsChanged(int id) { - setSearchInfo(id); + public void prefsChanged(int id, boolean translateMoves) { + if (translateMoves && (id == pvInfoSearchId)) { + Position pos = game.currPos(); + if (currMove != null) + notifyCurrMove(id, pos, currMove, currMoveNr); + notifyPV(id, pos, pvInfoV, ponderMove); + } else { + setSearchInfo(id); + } } @Override @@ -758,7 +783,7 @@ public class DroidChessController { private final void updateBookHints() { if (humansTurn()) { - Pair> bi = computerPlayer.getBookHints(game.currPos()); + Pair> bi = computerPlayer.getBookHints(game.currPos(), localPt()); listener.notifyBookInfo(searchId, bi.first, bi.second); } } @@ -906,8 +931,7 @@ public class DroidChessController { */ private final boolean doMove(Move move) { Position pos = game.currPos(); - ArrayList moves = new MoveGen().pseudoLegalMoves(pos); - moves = MoveGen.removeIllegal(pos, moves); + ArrayList moves = new MoveGen().legalMoves(pos); int promoteTo = move.promoteTo; for (Move m : moves) { if ((m.from == move.from) && (m.to == move.to)) { @@ -917,7 +941,7 @@ public class DroidChessController { return false; } if (m.promoteTo == promoteTo) { - String strMove = TextIO.moveToString(pos, m, false); + String strMove = TextIO.moveToString(pos, m, false, false, moves); game.processString(strMove); return true; } @@ -943,7 +967,7 @@ public class DroidChessController { } } else { if ((s.state == GameState.DRAW_REP) || (s.state == GameState.DRAW_50)) - s.drawInfo = game.getDrawInfo(); + s.drawInfo = game.getDrawInfo(localPt()); } gui.setStatus(s); updateMoveList(); @@ -957,7 +981,7 @@ public class DroidChessController { if (i > 0) sb.append(' '); if (i == game.tree.currentNode.defaultChild) sb.append(Util.boldStart); - sb.append(TextIO.moveToString(pos, prevVarList.get(i), false)); + sb.append(TextIO.moveToString(pos, prevVarList.get(i), false, localPt())); if (i == game.tree.currentNode.defaultChild) sb.append(Util.boldStop); } @@ -985,6 +1009,7 @@ public class DroidChessController { tmpOptions.exp.playerAction = false; tmpOptions.exp.clockInfo = false; tmpOptions.exp.moveNrAfterNag = false; + tmpOptions.exp.pieceType = pgnOptions.view.pieceType; gameTextListener.clear(); game.tree.pgnTreeWalker(tmpOptions, gameTextListener); } diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Game.java b/DroidFish/src/org/petero/droidfish/gamelogic/Game.java index ca216af..197085e 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/Game.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/Game.java @@ -185,8 +185,8 @@ public class Game { } } - public final String getDrawInfo() { - return tree.getGameStateInfo(); + public final String getDrawInfo(boolean localized) { + return tree.getGameStateInfo(localized); } /** @@ -404,7 +404,7 @@ public class Game { if (valid) { String playerAction = rep ? "draw rep" : "draw 50"; if (m != null) - playerAction += " " + TextIO.moveToString(pos, m, false); + playerAction += " " + TextIO.moveToString(pos, m, false, false); addToGameTree(new Move(0, 0, 0), playerAction); } else { pendingDrawOffer = true; diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java b/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java index 66d8f8b..2ad6a5e 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java @@ -218,10 +218,38 @@ public class GameTree { } } + /** Update moveStrLocal in all game nodes. */ + public final void translateMoves() { + List currPath = new ArrayList(); + while (currentNode != rootNode) { + Node child = currentNode; + goBack(); + int childNum = currentNode.children.indexOf(child); + currPath.add(childNum); + } + translateMovesHelper(); + for (int i = currPath.size() - 1; i >= 0; i--) + goForward(currPath.get(i), false); + } + + private final void translateMovesHelper() { + ArrayList moves = MoveGen.instance.legalMoves(currentPos); + currentNode.verifyChildren(currentPos, moves); + int nc = currentNode.children.size(); + for (int i = 0; i < nc; i++) { + Node child = currentNode.children.get(i); + child.moveStrLocal = TextIO.moveToString(currentPos, child.move, false, true, moves); + goForward(i, false); + translateMovesHelper(); + goBack(); + } + } + /** Export game tree in PGN format. */ public final String toPGN(PGNOptions options) { PgnText pgnText = new PgnText(); options.exp.pgnPromotions = true; + options.exp.pieceType = PGNOptions.PT_ENGLISH; pgnTreeWalker(options, pgnText); return pgnText.getPgnString(); } @@ -235,7 +263,7 @@ public class GameTree { while (currentNode != rootNode) { Node child = currentNode; goBack(); - int childNum = variations().indexOf(child); + int childNum = currentNode.children.indexOf(child); currPath.add(childNum); } while (variations().size() > 0) @@ -692,11 +720,17 @@ public class GameTree { int idx = currentNode.children.size(); Node node = new Node(currentNode, moveStr, playerAction, Integer.MIN_VALUE, nag, preComment, postComment); Move move = TextIO.UCIstringToMove(moveStr); - if (move == null) - move = TextIO.stringToMove(currentPos, moveStr); + ArrayList moves = null; + if (move == null) { + moves = MoveGen.instance.legalMoves(currentPos); + move = TextIO.stringToMove(currentPos, moveStr, moves); + } if (move == null) return -1; - node.moveStr = TextIO.moveToString(currentPos, move, false); + if (moves == null) + moves = MoveGen.instance.legalMoves(currentPos); + node.moveStr = TextIO.moveToString(currentPos, move, false, false, moves); + node.moveStrLocal = TextIO.moveToString(currentPos, move, false, true, moves); node.move = move; node.ui = new UndoInfo(); currentNode.children.add(node); @@ -831,7 +865,7 @@ public class GameTree { } /** Get additional info affecting gameState. A player "draw" or "resign" command. */ - final String getGameStateInfo() { + final String getGameStateInfo(boolean localized) { String ret = ""; String action = currentNode.playerAction; if (action.startsWith("draw rep ")) { @@ -840,6 +874,25 @@ public class GameTree { if (action.startsWith("draw 50 ")) { ret = action.substring(8).trim(); } + if (localized) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < ret.length(); i++) { + int p = Piece.EMPTY; + switch (ret.charAt(i)) { + case 'Q': p = Piece.WQUEEN; break; + case 'R': p = Piece.WROOK; break; + case 'B': p = Piece.WBISHOP; break; + case 'N': p = Piece.WKNIGHT; break; + case 'K': p = Piece.WKING; break; + case 'P': p = Piece.WPAWN; break; + } + if (p == Piece.EMPTY) + sb.append(ret.charAt(i)); + else + sb.append(TextIO.pieceToCharLocalized(p)); + } + ret = sb.toString(); + } return ret; } @@ -930,7 +983,8 @@ public class GameTree { * The root node is special in that it doesn't have a move. */ public static class Node { - String moveStr; // String representation of move leading to this node. Empty string root node. + String moveStr; // String representation of move leading to this node. Empty string in root node. + String moveStrLocal; // Localized version of moveStr Move move; // Computed on demand for better PGN parsing performance. // Subtrees of invalid moves will be dropped when detected. // Always valid for current node. @@ -948,6 +1002,7 @@ public class GameTree { public Node() { this.moveStr = ""; + this.moveStrLocal = ""; this.move = null; this.ui = null; this.playerAction = ""; @@ -963,6 +1018,7 @@ public class GameTree { public Node(Node parent, String moveStr, String playerAction, int remainingTime, int nag, String preComment, String postComment) { this.moveStr = moveStr; + this.moveStrLocal = moveStr; this.move = null; this.ui = null; this.playerAction = playerAction; @@ -981,12 +1037,18 @@ public class GameTree { /** nodePos must represent the same position as this Node object. */ private final boolean verifyChildren(Position nodePos) { + return verifyChildren(nodePos, null); + } + private final boolean verifyChildren(Position nodePos, ArrayList moves) { boolean anyToRemove = false; for (Node child : children) { if (child.move == null) { - Move move = TextIO.stringToMove(nodePos, child.moveStr); + if (moves == null) + moves = MoveGen.instance.legalMoves(nodePos); + Move move = TextIO.stringToMove(nodePos, child.moveStr, moves); if (move != null) { - child.moveStr = TextIO.moveToString(nodePos, move, false); + child.moveStr = TextIO.moveToString(nodePos, move, false, false, moves); + child.moveStrLocal = TextIO.moveToString(nodePos, move, false, true, moves); child.move = move; child.ui = new UndoInfo(); } else { @@ -1053,6 +1115,7 @@ public class GameTree { static final void readFromStream(DataInputStream dis, Node node) throws IOException { while (true) { node.moveStr = dis.readUTF(); + node.moveStrLocal = node.moveStr; int from = dis.readByte(); if (from >= 0) { int to = dis.readByte(); @@ -1105,7 +1168,7 @@ public class GameTree { } } - /** Export this node in PGN format. */ + /** Export this node in PGN (or display text) format. */ private final boolean addPgnDataOneNode(PgnToken.PgnTokenReceiver out, MoveNumber mn, boolean needMoveNr, PGNOptions options) { if ((preComment.length() > 0) && options.exp.comments) { @@ -1125,9 +1188,13 @@ public class GameTree { out.processToken(this, PgnToken.PERIOD, null); } } - String str = moveStr; - if (options.exp.pgnPromotions && (move != null) && (move.promoteTo != Piece.EMPTY)) { - str = TextIO.pgnPromotion(str); + String str; + if (options.exp.pieceType == PGNOptions.PT_LOCAL) { + str = moveStrLocal; + } else { + str = moveStr; + if (options.exp.pgnPromotions && (move != null) && (move.promoteTo != Piece.EMPTY)) + str = TextIO.pgnPromotion(str); } out.processToken(this, PgnToken.SYMBOL, str); needMoveNr = false; @@ -1261,6 +1328,7 @@ public class GameTree { moveAdded = false; } nodeToAdd.moveStr = tok.token; + nodeToAdd.moveStrLocal = tok.token; moveAdded = true; } break; diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java b/DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java index f09e229..ffec23a 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java @@ -31,9 +31,16 @@ public class MoveGen { instance = new MoveGen(); } + /** Generate and return a list of legal moves. */ + public final ArrayList legalMoves(Position pos) { + ArrayList moveList = pseudoLegalMoves(pos); + moveList = MoveGen.removeIllegal(pos, moveList); + return moveList; + } + /** * Generate and return a list of pseudo-legal moves. - * Pseudo-legal means that the moves doesn't necessarily defend from check threats. + * Pseudo-legal means that the moves don't necessarily defend from check threats. */ public final ArrayList pseudoLegalMoves(Position pos) { ArrayList moveList = getMoveListObj(); diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java b/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java index 3d6afba..ae44af5 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java @@ -37,21 +37,6 @@ public interface SearchListener { ArrayList pv; String pvStr = ""; - public PvInfo(PvInfo pvi) { - depth = pvi.depth; - score = pvi.score; - time = pvi.time; - nodes = pvi.nodes; - nps = pvi.nps; - isMate = pvi.isMate; - upperBound = pvi.upperBound; - lowerBound = pvi.lowerBound; - pv = new ArrayList(pvi.pv.size()); - for (int i = 0; i < pvi.pv.size(); i++) - pv.add(pvi.pv.get(i)); - pvStr = pvi.pvStr; - } - public PvInfo(int depth, int score, int time, int nodes, int nps, boolean isMate, boolean upperBound, boolean lowerBound, ArrayList pv) { this.depth = depth; diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java b/DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java index 6db2ffc..05baa7d 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java @@ -25,12 +25,22 @@ import org.petero.droidfish.R; /** - * + * Handle conversion of positions and moves to/from text format. * @author petero */ public class TextIO { static public final String startPosFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + /** Localized version of "P N B R Q K". */ + private static String[] pieceNames = null; + + /** Set localized piece names. */ + public static final void setPieceNames(String pieceNames) { + String[] pn = pieceNames.split(" "); + if (pn.length == 6) + TextIO.pieceNames = pn; + } + /** Parse a FEN string and return a chess Position object. */ public static final Position readFEN(String fen) throws ChessParseError { Position pos = new Position(); @@ -309,18 +319,18 @@ public class TextIO { /** * Convert a chess move to human readable form. - * @param pos The chess position. - * @param move The executed move. - * @param longForm If true, use long notation, eg Ng1-f3. - * Otherwise, use short notation, eg Nf3 + * @param pos The chess position. + * @param move The executed move. + * @param longForm If true, use long notation, eg Ng1-f3. + * Otherwise, use short notation, eg Nf3. + * @param localized If true, use localized piece names. */ - public static final String moveToString(Position pos, Move move, boolean longForm) { - ArrayList moves = MoveGen.instance.pseudoLegalMoves(pos); - moves = MoveGen.removeIllegal(pos, moves); - return moveToString(pos, move, longForm, moves); + public static final String moveToString(Position pos, Move move, boolean longForm, + boolean localized) { + return moveToString(pos, move, longForm, localized, null); } - private static final String moveToString(Position pos, Move move, boolean longForm, - List moves) { + public static final String moveToString(Position pos, Move move, boolean longForm, + boolean localized, List moves) { if ((move == null) || move.equals(new Move(0, 0, 0))) return "--"; StringBuilder ret = new StringBuilder(); @@ -342,8 +352,13 @@ public class TextIO { } } if (ret.length() == 0) { + if (pieceNames == null) + localized = false; int p = pos.getPiece(move.from); - ret.append(pieceToChar(p)); + if (localized) + ret.append(pieceToCharLocalized(p)); + else + ret.append(pieceToChar(p)); int x1 = Position.getX(move.from); int y1 = Position.getY(move.from); int x2 = Position.getX(move.to); @@ -361,6 +376,8 @@ public class TextIO { int numSameTarget = 0; int numSameFile = 0; int numSameRow = 0; + if (moves == null) + moves = MoveGen.instance.legalMoves(pos); int mSize = moves.size(); for (int mi = 0; mi < mSize; mi++) { Move m = moves.get(mi); @@ -389,8 +406,12 @@ public class TextIO { } ret.append((char) (x2 + 'a')); ret.append((char) (y2 + '1')); - if (move.promoteTo != Piece.EMPTY) - ret.append(pieceToChar(move.promoteTo)); + if (move.promoteTo != Piece.EMPTY) { + if (localized) + ret.append(pieceToCharLocalized(move.promoteTo)); + else + ret.append(pieceToChar(move.promoteTo)); + } } UndoInfo ui = new UndoInfo(); pos.makeMove(move, ui); @@ -453,6 +474,10 @@ public class TextIO { * information as long as it matches exactly one valid move. */ public static final Move stringToMove(Position pos, String strMove) { + return stringToMove(pos, strMove, null); + } + public static final Move stringToMove(Position pos, String strMove, + ArrayList moves) { if (strMove.equals("--")) return new Move(0, 0, 0); @@ -530,8 +555,8 @@ public class TextIO { info.promPiece = Piece.EMPTY; } - ArrayList moves = MoveGen.instance.pseudoLegalMoves(pos); - moves = MoveGen.removeIllegal(pos, moves); + if (moves == null) + moves = MoveGen.instance.legalMoves(pos); ArrayList matches = new ArrayList(2); for (int i = 0; i < moves.size(); i++) { @@ -717,6 +742,17 @@ public class TextIO { return ""; } + public final static String pieceToCharLocalized(int p) { + switch (p) { + case Piece.WQUEEN: case Piece.BQUEEN: return pieceNames[4]; + case Piece.WROOK: case Piece.BROOK: return pieceNames[3]; + case Piece.WBISHOP: case Piece.BBISHOP: return pieceNames[2]; + case Piece.WKNIGHT: case Piece.BKNIGHT: return pieceNames[1]; + case Piece.WKING: case Piece.BKING: return pieceNames[5]; + } + return ""; + } + private final static int charToPiece(boolean white, char c) { switch (c) { case 'Q': case 'q': return white ? Piece.WQUEEN : Piece.BQUEEN; diff --git a/DroidFishTest/src/org/petero/droidfish/book/BookTest.java b/DroidFishTest/src/org/petero/droidfish/book/BookTest.java index b76a5f1..75adb3e 100644 --- a/DroidFishTest/src/org/petero/droidfish/book/BookTest.java +++ b/DroidFishTest/src/org/petero/droidfish/book/BookTest.java @@ -60,7 +60,7 @@ public class BookTest extends TestCase { public void testGetAllBookMoves() throws ChessParseError { Position pos = TextIO.readFEN(TextIO.startPosFEN); DroidBook book = DroidBook.getInstance(); - String moveListString = book.getAllBookMoves(pos).first; + String moveListString = book.getAllBookMoves(pos, false).first; String[] strMoves = moveListString.split(":[0-9]* "); assertTrue(strMoves.length > 1); for (String strMove : strMoves) { diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTest.java index 120160b..dfe5fcb 100644 --- a/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTest.java +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTest.java @@ -140,7 +140,7 @@ public class GameTest extends TestCase { game.setPos(TextIO.readFEN(fen)); game.processString("draw 50 Nc3"); assertEquals(Game.GameState.DRAW_50, game.getGameState()); // Draw claim valid - assertEquals("Nc3", game.getDrawInfo()); + assertEquals("Nc3", game.getDrawInfo(false)); game.setPos(TextIO.readFEN(fen)); game.processString("draw 50 a6"); diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java index 9fc1b63..27d920e 100644 --- a/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java @@ -225,7 +225,7 @@ public class GameTreeTest extends TestCase { for (int i = 0; i < vars.size(); i++) { if (i > 0) ret.append(' '); - String moveStr = TextIO.moveToString(gt.currentPos, vars.get(i), false); + String moveStr = TextIO.moveToString(gt.currentPos, vars.get(i), false, false); ret.append(moveStr); } return ret.toString(); @@ -477,7 +477,7 @@ public class GameTreeTest extends TestCase { assertEquals("A \"good\" player", gt.white); assertEquals("e4", getVariationsAsString(gt)); - // Test for broken PGN headers: [White "A "good" player"] + // Test for broken PGN headers: [White "A "good old" player"] res = gt.readPGN("[White \"A \"good old\" player\"]\ne4", options); assertEquals(true, res); assertEquals("A \"good old\" player", gt.white); diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveGenTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveGenTest.java index 9f1db3c..d811ff4 100644 --- a/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveGenTest.java +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveGenTest.java @@ -195,7 +195,7 @@ public class MoveGenTest extends TestCase { } ArrayList strMoves = new ArrayList(); for (Move m : moves) { - String mStr = TextIO.moveToString(pos, m, true); + String mStr = TextIO.moveToString(pos, m, true, false); strMoves.add(mStr); // System.out.println(mStr); } diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/TextIOTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/TextIOTest.java index 77622b5..19429fa 100644 --- a/DroidFishTest/src/org/petero/droidfish/gamelogic/TextIOTest.java +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/TextIOTest.java @@ -123,6 +123,10 @@ public class TextIOTest extends TestCase { return wasError; } + private final static String moveToString(Position pos, Move move, boolean longForm) { + return TextIO.moveToString(pos, move, longForm, false); + } + /** * Test of moveToString method, of class TextIO. */ @@ -132,39 +136,39 @@ public class TextIOTest extends TestCase { Move move = new Move(Position.getSquare(4, 1), Position.getSquare(4, 3), Piece.EMPTY); boolean longForm = true; - String result = TextIO.moveToString(pos, move, longForm); + String result = moveToString(pos, move, longForm); assertEquals("e2-e4", result); move = new Move(Position.getSquare(6, 0), Position.getSquare(5, 2), Piece.EMPTY); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("Ng1-f3", result); move = new Move(Position.getSquare(4, 7), Position.getSquare(2, 7), Piece.EMPTY); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("O-O-O", result); String fen = "1r3k2/2P5/8/8/8/4K3/8/8 w - - 0 1"; pos = TextIO.readFEN(fen); assertEquals(fen, TextIO.toFEN(pos)); move = new Move(Position.getSquare(2,6), Position.getSquare(1,7), Piece.WROOK); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("c7xb8R+", result); move = new Move(Position.getSquare(2,6), Position.getSquare(2,7), Piece.WKNIGHT); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("c7-c8N", result); move = new Move(Position.getSquare(2,6), Position.getSquare(2,7), Piece.WQUEEN); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("c7-c8Q+", result); // Test null move pos = TextIO.readFEN(TextIO.startPosFEN); Move nullMove = new Move(0, 0, 0); - result = TextIO.moveToString(pos, nullMove, false); + result = moveToString(pos, nullMove, false); assertEquals("--", result); - result = TextIO.moveToString(pos, nullMove, true); + result = moveToString(pos, nullMove, true); assertEquals("--", result); } @@ -176,19 +180,19 @@ public class TextIOTest extends TestCase { boolean longForm = true; Move move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WROOK); - String result = TextIO.moveToString(pos, move, longForm); + String result = moveToString(pos, move, longForm); assertEquals("b7-b8R+", result); // check move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WQUEEN); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("b7-b8Q#", result); // check mate move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WKNIGHT); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("b7-b8N", result); move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WBISHOP); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("b7-b8B", result); // stalemate } @@ -202,43 +206,43 @@ public class TextIOTest extends TestCase { boolean longForm = false; Move move = new Move(Position.getSquare(4,5), Position.getSquare(4,3), Piece.EMPTY); - String result = TextIO.moveToString(pos, move, longForm); + String result = moveToString(pos, move, longForm); assertEquals("Qee4", result); // File disambiguation needed move = new Move(Position.getSquare(2,5), Position.getSquare(4,3), Piece.EMPTY); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("Qc6e4", result); // Full disambiguation needed move = new Move(Position.getSquare(2,3), Position.getSquare(4,3), Piece.EMPTY); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("Q4e4", result); // Row disambiguation needed move = new Move(Position.getSquare(2,3), Position.getSquare(2,0), Piece.EMPTY); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("Qc1+", result); // No disambiguation needed move = new Move(Position.getSquare(0,1), Position.getSquare(0,0), Piece.BQUEEN); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("a1Q", result); // Normal promotion move = new Move(Position.getSquare(0,1), Position.getSquare(1,0), Piece.BQUEEN); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("axb1Q#", result); // Capture promotion and check mate move = new Move(Position.getSquare(0,1), Position.getSquare(1,0), Piece.BKNIGHT); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("axb1N", result); // Capture promotion move = new Move(Position.getSquare(3,6), Position.getSquare(4,4), Piece.EMPTY); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("Ne5", result); // Other knight pinned, no disambiguation needed move = new Move(Position.getSquare(7,6), Position.getSquare(7,4), Piece.EMPTY); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("h5", result); // Regular pawn move move = new Move(Position.getSquare(5,7), Position.getSquare(3,7), Piece.EMPTY); - result = TextIO.moveToString(pos, move, longForm); + result = moveToString(pos, move, longForm); assertEquals("Rfd8", result); // File disambiguation needed }