/* 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.gamelogic; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import junit.framework.TestCase; import org.petero.droidfish.PGNOptions; import org.petero.droidfish.gamelogic.Game.GameState; import org.petero.droidfish.gamelogic.GameTree.Node; import org.petero.droidfish.gamelogic.GameTree.PgnScanner; import org.petero.droidfish.gamelogic.TimeControlData.TimeControlField; public class GameTreeTest extends TestCase { /** Add a move to the game tree, with no comments or annotations. */ private final int addStdMove(GameTree gt, String moveStr) { return gt.addMove(moveStr, "", 0, "", ""); } public final void testGameTree() throws ChessParseError, IOException { GameTree gt = new GameTree(null); Position expectedPos = TextIO.readFEN(TextIO.startPosFEN); assertEquals(expectedPos, gt.currentPos); List<Move> varList = gt.variations(); assertEquals(0, varList.size()); int varNo = addStdMove(gt, "e4"); assertEquals(0, varNo); assertEquals(expectedPos, gt.currentPos); gt.goForward(varNo); Move move = TextIO.UCIstringToMove("e2e4"); UndoInfo ui = new UndoInfo(); expectedPos.makeMove(move, ui); assertEquals(expectedPos, gt.currentPos); gt.goBack(); expectedPos.unMakeMove(move, ui); assertEquals(expectedPos, gt.currentPos); varNo = addStdMove(gt, "d4"); assertEquals(1, varNo); assertEquals(expectedPos, gt.currentPos); varList = gt.variations(); assertEquals(2, varList.size()); gt.goForward(varNo); move = TextIO.UCIstringToMove("d2d4"); expectedPos.makeMove(move, ui); assertEquals(expectedPos, gt.currentPos); varNo = addStdMove(gt, "g8f6"); assertEquals(0, varNo); assertEquals(expectedPos, gt.currentPos); varList = gt.variations(); assertEquals(1, varList.size()); gt.goForward(-1); Move move2 = TextIO.UCIstringToMove("g8f6"); UndoInfo ui2 = new UndoInfo(); expectedPos.makeMove(move2, ui2); assertEquals(expectedPos, gt.currentPos); assertEquals("Nf6", gt.currentNode.moveStr); gt.goBack(); assertEquals("d4", gt.currentNode.moveStr); gt.goBack(); expectedPos.unMakeMove(move2, ui2); expectedPos.unMakeMove(move, ui); assertEquals(expectedPos, gt.currentPos); assertEquals("", gt.currentNode.moveStr); gt.goForward(-1); // Should remember that d2d4 was last visited branch expectedPos.makeMove(move, ui); assertEquals(expectedPos, gt.currentPos); byte[] serialState = null; { ByteArrayOutputStream baos = new ByteArrayOutputStream(32768); DataOutputStream dos = new DataOutputStream(baos); gt.writeToStream(dos); dos.flush(); serialState = baos.toByteArray(); dos.close(); baos.close(); } gt = new GameTree(null); { ByteArrayInputStream bais = new ByteArrayInputStream(serialState); DataInputStream dis = new DataInputStream(bais); gt.readFromStream(dis, 3); dis.close(); bais.close(); } assertEquals(expectedPos, gt.currentPos); gt.goBack(); expectedPos.unMakeMove(move, ui); assertEquals(expectedPos, gt.currentPos); varList = gt.variations(); assertEquals(2, varList.size()); } private final String getMoveListAsString(GameTree gt) { StringBuilder ret = new StringBuilder(); Pair<List<Node>, Integer> ml = gt.getMoveList(); List<Node> lst = ml.first; final int numMovesPlayed = ml.second; for (int i = 0; i < lst.size(); i++) { if (i == numMovesPlayed) ret.append('*'); if (i > 0) ret.append(' '); ret.append(lst.get(i).moveStr); } if (lst.size() == numMovesPlayed) ret.append('*'); return ret.toString(); } public final void testGetMoveList() throws ChessParseError { GameTree gt = new GameTree(null); addStdMove(gt, "e4"); addStdMove(gt, "d4"); assertEquals("*e4", getMoveListAsString(gt)); gt.goForward(0); assertEquals("e4*", getMoveListAsString(gt)); addStdMove(gt, "e5"); addStdMove(gt, "c5"); assertEquals("e4* e5", getMoveListAsString(gt)); gt.goForward(1); assertEquals("e4 c5*", getMoveListAsString(gt)); addStdMove(gt, "Nf3"); addStdMove(gt, "d4"); assertEquals("e4 c5* Nf3", getMoveListAsString(gt)); gt.goForward(1); assertEquals("e4 c5 d4*", getMoveListAsString(gt)); gt.goBack(); assertEquals("e4 c5* d4", getMoveListAsString(gt)); gt.goBack(); assertEquals("e4* c5 d4", getMoveListAsString(gt)); gt.goBack(); assertEquals("*e4 c5 d4", getMoveListAsString(gt)); gt.goForward(1); assertEquals("d4*", getMoveListAsString(gt)); gt.goBack(); assertEquals("*d4", getMoveListAsString(gt)); gt.goForward(0); assertEquals("e4* c5 d4", getMoveListAsString(gt)); } public final void testReorderVariation() throws ChessParseError { GameTree gt = new GameTree(null); addStdMove(gt, "e4"); addStdMove(gt, "d4"); addStdMove(gt, "c4"); assertEquals("e4 d4 c4", getVariationsAsString(gt)); assertEquals(0, gt.currentNode.defaultChild); gt.reorderVariation(1, 0); assertEquals("d4 e4 c4", getVariationsAsString(gt)); assertEquals(1, gt.currentNode.defaultChild); gt.reorderVariation(0, 2); assertEquals("e4 c4 d4", getVariationsAsString(gt)); assertEquals(0, gt.currentNode.defaultChild); gt.reorderVariation(1, 2); assertEquals("e4 d4 c4", getVariationsAsString(gt)); assertEquals(0, gt.currentNode.defaultChild); gt.reorderVariation(0, 1); assertEquals("d4 e4 c4", getVariationsAsString(gt)); assertEquals(1, gt.currentNode.defaultChild); } public final void testDeleteVariation() throws ChessParseError { GameTree gt = new GameTree(null); addStdMove(gt, "e4"); addStdMove(gt, "d4"); addStdMove(gt, "c4"); addStdMove(gt, "f4"); gt.deleteVariation(0); assertEquals("d4 c4 f4", getVariationsAsString(gt)); assertEquals(0, gt.currentNode.defaultChild); gt.reorderVariation(0, 2); assertEquals("c4 f4 d4", getVariationsAsString(gt)); assertEquals(2, gt.currentNode.defaultChild); gt.deleteVariation(1); assertEquals("c4 d4", getVariationsAsString(gt)); assertEquals(1, gt.currentNode.defaultChild); addStdMove(gt, "g4"); addStdMove(gt, "h4"); assertEquals("c4 d4 g4 h4", getVariationsAsString(gt)); assertEquals(1, gt.currentNode.defaultChild); gt.reorderVariation(1, 2); assertEquals("c4 g4 d4 h4", getVariationsAsString(gt)); assertEquals(2, gt.currentNode.defaultChild); gt.deleteVariation(2); assertEquals("c4 g4 h4", getVariationsAsString(gt)); assertEquals(0, gt.currentNode.defaultChild); } final static String getVariationsAsString(GameTree gt) { StringBuilder ret = new StringBuilder(); List<Move> vars = gt.variations(); for (int i = 0; i < vars.size(); i++) { if (i > 0) ret.append(' '); String moveStr = TextIO.moveToString(gt.currentPos, vars.get(i), false, false); ret.append(moveStr); } return ret.toString(); } public final void testGetRemainingTime() throws ChessParseError { GameTree gt = new GameTree(null); int initialTime = 60000; assertEquals(initialTime, gt.getRemainingTime(true, initialTime)); assertEquals(initialTime, gt.getRemainingTime(false, initialTime)); addStdMove(gt, "e4"); gt.goForward(-1); assertEquals(initialTime, gt.getRemainingTime(true, initialTime)); assertEquals(initialTime, gt.getRemainingTime(false, initialTime)); gt.setRemainingTime(45000); assertEquals(45000, gt.getRemainingTime(true, initialTime)); assertEquals(initialTime, gt.getRemainingTime(false, initialTime)); addStdMove(gt, "e5"); assertEquals(45000, gt.getRemainingTime(true, initialTime)); assertEquals(initialTime, gt.getRemainingTime(false, initialTime)); gt.goForward(-1); assertEquals(45000, gt.getRemainingTime(true, initialTime)); assertEquals(initialTime, gt.getRemainingTime(false, initialTime)); addStdMove(gt, "Nf3"); gt.goForward(-1); addStdMove(gt, "Nc6"); gt.goForward(-1); assertEquals(45000, gt.getRemainingTime(true, initialTime)); assertEquals(initialTime, gt.getRemainingTime(false, initialTime)); gt.setRemainingTime(30000); assertEquals(45000, gt.getRemainingTime(true, initialTime)); assertEquals(30000, gt.getRemainingTime(false, initialTime)); addStdMove(gt, "Bb5"); gt.goForward(-1); gt.setRemainingTime(20000); assertEquals(20000, gt.getRemainingTime(true, initialTime)); assertEquals(30000, gt.getRemainingTime(false, initialTime)); addStdMove(gt, "a6"); gt.goForward(-1); gt.setRemainingTime(15000); assertEquals(20000, gt.getRemainingTime(true, initialTime)); assertEquals(15000, gt.getRemainingTime(false, initialTime)); gt.goBack(); assertEquals(20000, gt.getRemainingTime(true, initialTime)); assertEquals(30000, gt.getRemainingTime(false, initialTime)); gt.goBack(); assertEquals(45000, gt.getRemainingTime(true, initialTime)); assertEquals(30000, gt.getRemainingTime(false, initialTime)); gt.goBack(); assertEquals(45000, gt.getRemainingTime(true, initialTime)); assertEquals(initialTime, gt.getRemainingTime(false, initialTime)); gt.goBack(); gt.goBack(); gt.goBack(); assertEquals(initialTime, gt.getRemainingTime(true, initialTime)); assertEquals(initialTime, gt.getRemainingTime(false, initialTime)); } private final List<PgnToken> getAllTokens(String s) { PgnScanner sc = new PgnScanner(s); List<PgnToken> ret = new ArrayList<PgnToken>(); while (true) { PgnToken tok = sc.nextToken(); if (tok.type == PgnToken.EOF) break; ret.add(tok); } return ret; } public final void testPgnScanner() throws ChessParseError { List<PgnToken> lst = getAllTokens("a\nb\n%junk\nc3"); // a b c3 assertEquals(3, lst.size()); assertEquals(PgnToken.SYMBOL, lst.get(0).type); assertEquals("a", lst.get(0).token); assertEquals(PgnToken.SYMBOL, lst.get(1).type); assertEquals("b", lst.get(1).token); assertEquals(PgnToken.SYMBOL, lst.get(2).type); assertEquals("c3", lst.get(2).token); lst = getAllTokens("e2 ; e5\nc5"); // e2 comment c5 assertEquals(3, lst.size()); assertEquals("e2", lst.get(0).token); assertEquals(PgnToken.COMMENT, lst.get(1).type); assertEquals(" e5", lst.get(1).token); assertEquals("c5", lst.get(2).token); lst = getAllTokens("e4?? { comment ; } e5!?"); // e4?? comment e5!? assertEquals(3, lst.size()); assertEquals("e4??", lst.get(0).token); assertEquals(" comment ; ", lst.get(1).token); assertEquals("e5!?", lst.get(2).token); lst = getAllTokens("e4! { comment { } e5?"); // e4! comment e5? assertEquals(3, lst.size()); assertEquals("e4!", lst.get(0).token); assertEquals(" comment { ", lst.get(1).token); assertEquals("e5?", lst.get(2).token); lst = getAllTokens("e4(c4 {(()\\} c5 ( e5))Nf6"); // e4 ( c4 comment c5 ( e5 ) ) Nf6 assertEquals(10, lst.size()); assertEquals("e4", lst.get(0).token); assertEquals(PgnToken.LEFT_PAREN, lst.get(1).type); assertEquals("c4", lst.get(2).token); assertEquals("(()\\", lst.get(3).token); assertEquals("c5", lst.get(4).token); assertEquals(PgnToken.LEFT_PAREN, lst.get(5).type); assertEquals("e5", lst.get(6).token); assertEquals(PgnToken.RIGHT_PAREN, lst.get(7).type); assertEquals(PgnToken.RIGHT_PAREN, lst.get(8).type); assertEquals("Nf6", lst.get(9).token); lst = getAllTokens("[a \"string\"]"); // [ a string ] assertEquals(4, lst.size()); assertEquals(PgnToken.LEFT_BRACKET, lst.get(0).type); assertEquals("a", lst.get(1).token); assertEquals(PgnToken.STRING, lst.get(2).type); assertEquals("string", lst.get(2).token); assertEquals(PgnToken.RIGHT_BRACKET, lst.get(3).type); lst = getAllTokens("[a \"str\\\"in\\\\g\"]"); // [ a str"in\g ] assertEquals(4, lst.size()); assertEquals(PgnToken.LEFT_BRACKET, lst.get(0).type); assertEquals("a", lst.get(1).token); assertEquals(PgnToken.STRING, lst.get(2).type); assertEquals("str\"in\\g", lst.get(2).token); assertEquals(PgnToken.RIGHT_BRACKET, lst.get(3).type); lst = getAllTokens("1...Nf6$23Nf3 12 e4_+#=:-*"); // 1 . . . Nf6 $23 Nf3 12 e4_+#=:- * assertEquals(10, lst.size()); assertEquals(PgnToken.INTEGER, lst.get(0).type); assertEquals("1", lst.get(0).token); assertEquals(PgnToken.PERIOD, lst.get(1).type); assertEquals(PgnToken.PERIOD, lst.get(2).type); assertEquals(PgnToken.PERIOD, lst.get(3).type); assertEquals("Nf6", lst.get(4).token); assertEquals(PgnToken.NAG, lst.get(5).type); assertEquals("23", lst.get(5).token); assertEquals("Nf3", lst.get(6).token); assertEquals(PgnToken.INTEGER, lst.get(7).type); assertEquals("12", lst.get(7).token); assertEquals("e4_+#=:-", lst.get(8).token); assertEquals(PgnToken.ASTERISK, lst.get(9).type); lst = getAllTokens("1/2-1/2 1-0 0-1"); assertEquals(3, lst.size()); assertEquals(PgnToken.SYMBOL, lst.get(0).type); assertEquals("1/2-1/2", lst.get(0).token); assertEquals(PgnToken.SYMBOL, lst.get(1).type); assertEquals("1-0", lst.get(1).token); assertEquals(PgnToken.SYMBOL, lst.get(2).type); assertEquals("0-1", lst.get(2).token); // Test invalid data, unterminated tokens lst = getAllTokens("e4 e5 ; ( )"); // e4 e5 comment assertEquals(3, lst.size()); assertEquals(PgnToken.SYMBOL, lst.get(0).type); assertEquals("e4", lst.get(0).token); assertEquals(PgnToken.SYMBOL, lst.get(1).type); assertEquals("e5", lst.get(1).token); assertEquals(PgnToken.COMMENT, lst.get(2).type); assertEquals(" ( )", lst.get(2).token); lst = getAllTokens("e4 e5 {"); // e4 e5 ? assertTrue(lst.size() >= 2); assertEquals(PgnToken.SYMBOL, lst.get(0).type); assertEquals("e4", lst.get(0).token); assertEquals(PgnToken.SYMBOL, lst.get(1).type); assertEquals("e5", lst.get(1).token); lst = getAllTokens("e4 e5 \""); // e4 e5 ? assertTrue(lst.size() >= 2); assertEquals(PgnToken.SYMBOL, lst.get(0).type); assertEquals("e4", lst.get(0).token); assertEquals(PgnToken.SYMBOL, lst.get(1).type); assertEquals("e5", lst.get(1).token); // Test that reading beyond EOF produces more EOF tokens PgnScanner sc = new PgnScanner("e4 e5"); assertEquals(PgnToken.SYMBOL, sc.nextToken().type); assertEquals(PgnToken.SYMBOL, sc.nextToken().type); assertEquals(PgnToken.EOF, sc.nextToken().type); assertEquals(PgnToken.EOF, sc.nextToken().type); assertEquals(PgnToken.EOF, sc.nextToken().type); } public final void testReadPGN() throws ChessParseError { GameTree gt = new GameTree(null); PGNOptions options = new PGNOptions(); options.imp.variations = true; options.imp.comments = true; options.imp.nag = true; boolean res = gt.readPGN("", options); assertEquals(false, res); res = gt.readPGN("[White \"a\"][Black \"b\"] {comment} e4 {x}", options); assertEquals(true, res); assertEquals("a", gt.white); assertEquals("b", gt.black); assertEquals("e4", getVariationsAsString(gt)); gt.goForward(0); assertEquals("comment", gt.currentNode.preComment); assertEquals("x", gt.currentNode.postComment); res = gt.readPGN("e4 e5 Nf3", options); assertEquals(true, res); assertEquals("e4", getVariationsAsString(gt)); gt.goForward(0); assertEquals("e5", getVariationsAsString(gt)); gt.goForward(0); assertEquals("Nf3", getVariationsAsString(gt)); res = gt.readPGN("e4 e5 (c5 (c6) d4) (d5) Nf3", options); assertEquals(true, res); assertEquals("e4", getVariationsAsString(gt)); gt.goForward(0); assertEquals("e5 c5 c6 d5", getVariationsAsString(gt)); gt.goForward(0); assertEquals("Nf3", getVariationsAsString(gt)); res = gt.readPGN("e4 e5 (c5 (c3) d4 (Nc3)) (d5) Nf3", options); // c3 invalid, should be removed assertEquals(true, res); assertEquals("e4", getVariationsAsString(gt)); gt.goForward(0); assertEquals("e5 c5 d5", getVariationsAsString(gt)); gt.goForward(1); assertEquals("d4 Nc3", getVariationsAsString(gt)); res = gt.readPGN("e4 + e5", options); // Extra + should be ignored assertEquals(true, res); assertEquals("e4", getVariationsAsString(gt)); gt.goForward(0); assertEquals("e5", getVariationsAsString(gt)); // Test for broken PGN headers: [White "A "good" player"] res = gt.readPGN("[White \"A \"good\" player\"]\ne4", options); assertEquals(true, res); assertEquals("A \"good\" player", gt.white); assertEquals("e4", getVariationsAsString(gt)); // 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); assertEquals("e4", getVariationsAsString(gt)); } public final void testStringEscape() throws ChessParseError { GameTree gt = new GameTree(null); PGNOptions options = new PGNOptions(); gt.white = "test \"x\""; String pgn = gt.toPGN(options); gt.white = ""; boolean res = gt.readPGN(pgn, options); assertEquals(true, res); assertEquals("test \"x\"", gt.white); } public final void testNAG() throws ChessParseError { GameTree gt = new GameTree(null); PGNOptions options = new PGNOptions(); options.imp.variations = true; options.imp.comments = true; options.imp.nag = true; boolean res = gt.readPGN("e4! e5 ? Nf3?! Nc6 !? Bb5!! a6?? Ba4 $14", options); assertEquals(true, res); assertEquals("e4", getVariationsAsString(gt)); gt.goForward(0); assertEquals(1, gt.currentNode.nag); assertEquals("e5", getVariationsAsString(gt)); gt.goForward(0); assertEquals(2, gt.currentNode.nag); assertEquals("Nf3", getVariationsAsString(gt)); gt.goForward(0); assertEquals(6, gt.currentNode.nag); assertEquals("Nc6", getVariationsAsString(gt)); gt.goForward(0); assertEquals(5, gt.currentNode.nag); assertEquals("Bb5", getVariationsAsString(gt)); gt.goForward(0); assertEquals(3, gt.currentNode.nag); assertEquals("a6", getVariationsAsString(gt)); gt.goForward(0); assertEquals(4, gt.currentNode.nag); assertEquals("Ba4", getVariationsAsString(gt)); gt.goForward(0); assertEquals(14, gt.currentNode.nag); } public final void testTime() throws ChessParseError { GameTree gt = new GameTree(null); PGNOptions options = new PGNOptions(); options.imp.variations = true; options.imp.comments = true; options.imp.nag = true; boolean res = gt.readPGN("e4 { x [%clk 0:0:43] y} e5 {[%clk\n1:2:3]} Nf3 Nc6 {[%clk -1:2 ]}", options); assertEquals(true, res); assertEquals("e4", getVariationsAsString(gt)); gt.goForward(0); assertEquals(43000, gt.currentNode.remainingTime); assertEquals(" x y", gt.currentNode.postComment); assertEquals("e5", getVariationsAsString(gt)); gt.goForward(0); assertEquals(((1*60+2)*60+3)*1000, gt.currentNode.remainingTime); assertEquals("", gt.currentNode.postComment); assertEquals("Nf3", getVariationsAsString(gt)); gt.goForward(0); assertEquals(Integer.MIN_VALUE, gt.currentNode.remainingTime); assertEquals("", gt.currentNode.postComment); assertEquals("Nc6", getVariationsAsString(gt)); gt.goForward(0); assertEquals(-(1*60+2)*1000, gt.currentNode.remainingTime); assertEquals("", gt.currentNode.postComment); } public final void testPlayerAction() throws ChessParseError { GameTree gt = new GameTree(null); int varNo = gt.addMove("--", "resign", 0, "", ""); assertEquals(0, varNo); PGNOptions options = new PGNOptions(); String pgn = gt.toPGN(options); assertEquals(-1, pgn.indexOf("--")); options.exp.playerAction = true; pgn = gt.toPGN(options); assertTrue(pgn.indexOf("--") >= 0); assertTrue(pgn.indexOf("1. -- {[%playeraction resign]} 0-1") >= 0); gt = new GameTree(null); gt.readPGN(pgn, options); assertEquals("--", getVariationsAsString(gt)); gt.goForward(0); assertEquals(GameState.RESIGN_WHITE, gt.getGameState()); gt = new GameTree(null); gt.readPGN("1. -- {[%playeraction resign]}", options); assertEquals("--", getVariationsAsString(gt)); gt.goForward(0); assertEquals(GameState.RESIGN_WHITE, gt.getGameState()); gt.readPGN("1. e4 -- {[%playeraction resign]}", options); assertEquals("e4", getVariationsAsString(gt)); gt.goForward(0); assertEquals("--", getVariationsAsString(gt)); gt.goForward(0); assertEquals(GameState.RESIGN_BLACK, gt.getGameState()); // Even if playerActions are not exported, moves corresponding // to draw offers must still be exported gt = new GameTree(null); varNo = gt.addMove("e4", "draw offer", 0, "", ""); assertEquals(0, varNo); assertEquals("e4", getVariationsAsString(gt)); gt.goForward(0); varNo = addStdMove(gt, "e5"); assertEquals(0, varNo); assertEquals("e5", getVariationsAsString(gt)); gt.goForward(0); options.exp.playerAction = false; pgn = gt.toPGN(options); assertTrue(pgn.indexOf("e4") >= 0); } public final void testGameResult() throws ChessParseError { GameTree gt = new GameTree(null); int varNo = addStdMove(gt, "e4"); gt.goForward(varNo); varNo = addStdMove(gt, "e5"); gt.goForward(varNo); varNo = gt.addMove("--", "resign", 0, "", ""); gt.goBack(); gt.goBack(); varNo = addStdMove(gt, "d4"); gt.goForward(varNo); varNo = gt.addMove("--", "resign", 0, "", ""); gt.goForward(varNo); // Black has resigned in a variation, but white resigned in the mainline, // so the PGN result should be "0-1"; PGNOptions options = new PGNOptions(); options.exp.variations = true; // options.exp.playerAction = true; String pgn = gt.toPGN(options); assertTrue(pgn.indexOf("1-0") < 0); assertTrue(pgn.indexOf("0-1") >= 0); } public final void testPGNPromotion() throws ChessParseError { // PGN standard specifies that promotion moves shall have an = sign // before the promotion piece GameTree gt = new GameTree(null); gt.setStartPos(TextIO.readFEN("rnbqkbnr/ppPppppp/8/8/8/8/PP1PPPPP/RNBQKBNR w KQkq - 0 1")); int varNo = addStdMove(gt, "cxb8N"); assertEquals(0, varNo); varNo = addStdMove(gt, "cxd8R+"); assertEquals(1, varNo); assertEquals("cxb8N cxd8R+", getVariationsAsString(gt)); // Normal short alg notation does not have = PGNOptions options = new PGNOptions(); options.exp.variations = true; String pgn = gt.toPGN(options); assertTrue(pgn.indexOf("cxb8=N") >= 0); // ... but PGN promotions do have the = sign assertTrue(pgn.indexOf("cxd8=R+") >= 0); } public final void testGoNode() throws ChessParseError { GameTree gt = new GameTree(null); PGNOptions options = new PGNOptions(); options.imp.variations = true; options.imp.comments = false; options.imp.nag = false; boolean res = gt.readPGN("e4 (d4 d5 c4) e5 Nf3 Nc6 Bb5 (Bc4 Bc5 (Nf6)) a6", options); assertEquals(true, res); assertEquals("*e4 e5 Nf3 Nc6 Bb5 a6", getMoveListAsString(gt)); gt.goForward(-1); Node ne4 = gt.currentNode; for (int i = 0; i < 5; i++) gt.goForward(-1); assertEquals("e4 e5 Nf3 Nc6 Bb5 a6*", getMoveListAsString(gt)); Node na6 = gt.currentNode; assertTrue(gt.goNode(gt.rootNode)); assertFalse(gt.goNode(gt.rootNode)); assertEquals("*e4 e5 Nf3 Nc6 Bb5 a6", getMoveListAsString(gt)); gt.goNode(na6); assertEquals("e4 e5 Nf3 Nc6 Bb5 a6*", getMoveListAsString(gt)); gt.goBack(); gt.goBack(); gt.goForward(1); gt.goForward(-1); Node nBc5 = gt.currentNode; assertEquals("e4 e5 Nf3 Nc6 Bc4 Bc5*", getMoveListAsString(gt)); gt.goNode(na6); assertEquals("e4 e5 Nf3 Nc6 Bb5 a6*", getMoveListAsString(gt)); gt.goNode(nBc5); gt.goBack(); gt.goForward(1); assertEquals("e4 e5 Nf3 Nc6 Bc4 Nf6*", getMoveListAsString(gt)); gt.goNode(gt.rootNode); gt.goForward(1); assertEquals("d4* d5 c4", getMoveListAsString(gt)); gt.goNode(ne4); assertEquals("e4* e5 Nf3 Nc6 Bc4 Nf6", getMoveListAsString(gt)); gt.goNode(na6); gt.goNode(ne4); assertEquals("e4* e5 Nf3 Nc6 Bb5 a6", getMoveListAsString(gt)); } public final void testTimeControl() throws ChessParseError { GameTree gt = new GameTree(null); PGNOptions options = new PGNOptions(); TimeControlData tcData = new TimeControlData(); tcData.setTimeControl(180*1000, 35, 0); gt.setTimeControlData(tcData); TimeControlData tcData2 = gt.getTimeControlData(); assertTrue(tcData2.isSymmetric()); assertTrue(tcData.equals(tcData2)); Map<String,String> headers = new TreeMap<String,String>(); gt.getHeaders(headers); assertEquals("35/180", headers.get("TimeControl")); assertEquals(null, headers.get("WhiteTimeControl")); assertEquals(null, headers.get("BlackTimeControl")); String pgn = gt.toPGN(options); boolean res = gt.readPGN(pgn, options); assertTrue(res); tcData2 = gt.getTimeControlData(); assertTrue(tcData2.isSymmetric()); assertEquals(tcData, tcData2); headers = new TreeMap<String,String>(); gt.getHeaders(headers); assertEquals("35/180", headers.get("TimeControl")); assertEquals(null, headers.get("WhiteTimeControl")); assertEquals(null, headers.get("BlackTimeControl")); tcData = new TimeControlData(); tcData.tcW.clear(); tcData.tcW.add(new TimeControlField(15*60*1000,40,0)); tcData.tcW.add(new TimeControlField(5*60*1000+345,20,0)); tcData.tcW.add(new TimeControlField(0,10,1000)); tcData.tcW.add(new TimeControlField(0,0,5000)); tcData.tcB.clear(); tcData.tcB.add(new TimeControlField(60*1000,20,3004)); tcData.tcB.add(new TimeControlField(30*1000,0,0)); gt.setTimeControlData(tcData); headers = new TreeMap<String,String>(); gt.getHeaders(headers); assertEquals(null, headers.get("TimeControl")); assertEquals("40/900:20/300.345:10/0+1:0+5", headers.get("WhiteTimeControl")); assertEquals("20/60+3.004:30", headers.get("BlackTimeControl")); tcData2 = gt.getTimeControlData(); assertTrue(!tcData2.isSymmetric()); assertEquals(tcData, tcData2); pgn = gt.toPGN(options); res = gt.readPGN(pgn, options); assertTrue(res); tcData2 = gt.getTimeControlData(); assertTrue(!tcData2.isSymmetric()); assertEquals(tcData, tcData2); headers = new TreeMap<String,String>(); gt.getHeaders(headers); assertEquals(null, headers.get("TimeControl")); assertEquals("40/900:20/300.345:10/0+1:0+5", headers.get("WhiteTimeControl")); assertEquals("20/60+3.004:30", headers.get("BlackTimeControl")); tcData = new TimeControlData(); tcData.setTimeControl(2*60*1000, 0, 12000); gt.setTimeControlData(tcData); headers = new TreeMap<String,String>(); gt.getHeaders(headers); assertEquals("120+12", headers.get("TimeControl")); assertEquals(null, headers.get("WhiteTimeControl")); assertEquals(null, headers.get("BlackTimeControl")); // Test pgn data with extra white space res = gt.readPGN("[TimeControl \" 40 / 5400 + 60 : 3.14 + 2.718 \"]", options); assertTrue(res); tcData = gt.getTimeControlData(); assertTrue(tcData.isSymmetric()); assertEquals(2, tcData.tcW.size()); TimeControlField tf = tcData.tcW.get(0); assertEquals(40, tf.movesPerSession); assertEquals(5400*1000, tf.timeControl); assertEquals(60*1000, tf.increment); tf = tcData.tcW.get(1); assertEquals(0, tf.movesPerSession); assertEquals(3140, tf.timeControl); assertEquals(2718, tf.increment); } public final void testHeaders() throws Throwable { PGNOptions options = new PGNOptions(); options.imp.variations = true; options.imp.comments = true; options.imp.nag = true; { GameTree gt = new GameTree(null); boolean res = gt.readPGN("[White \"a\"][Black \"b\"] f4 *", options); assertEquals(true, res); TreeMap<String,String> headers = new TreeMap<String,String>(); headers.put("Event", "test"); gt.setHeaders(headers); TreeMap<String,String> newHeaders = new TreeMap<String,String>(); gt.getHeaders(newHeaders); assertEquals("test", newHeaders.get("Event")); for (int i = 0; i < 2; i++) { for (String r : new String[]{"1-0", "1/2-1/2", "0-1", "*"}) { headers.clear(); headers.put("Result", r); gt.setHeaders(headers); newHeaders.clear(); gt.getHeaders(newHeaders); assertEquals(r, newHeaders.get("Result")); } } } { GameTree gt = new GameTree(null); gt.readPGN("f3 e5 g4 Qh4", options); TreeMap<String,String> headers = new TreeMap<String,String>(); gt.getHeaders(headers); assertEquals("0-1", headers.get("Result")); for (String r : new String[]{"1-0", "1/2-1/2", "0-1", "*"}) { headers.clear(); headers.put("Result", r); gt.setHeaders(headers); headers.clear(); gt.getHeaders(headers); assertEquals("0-1", headers.get("Result")); } } } }