diff --git a/DroidFishTest/.classpath b/DroidFishTest/.classpath new file mode 100644 index 0000000..2604b8a --- /dev/null +++ b/DroidFishTest/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/DroidFishTest/.project b/DroidFishTest/.project new file mode 100644 index 0000000..57d4422 --- /dev/null +++ b/DroidFishTest/.project @@ -0,0 +1,34 @@ + + + DroidFishTest + + + DroidFish + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/DroidFishTest/AndroidManifest.xml b/DroidFishTest/AndroidManifest.xml new file mode 100644 index 0000000..b87b05e --- /dev/null +++ b/DroidFishTest/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/DroidFishTest/bin/res/drawable-hdpi/ic_launcher.png b/DroidFishTest/bin/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..882eb14 Binary files /dev/null and b/DroidFishTest/bin/res/drawable-hdpi/ic_launcher.png differ diff --git a/DroidFishTest/bin/res/drawable-ldpi/ic_launcher.png b/DroidFishTest/bin/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 0000000..18689f6 Binary files /dev/null and b/DroidFishTest/bin/res/drawable-ldpi/ic_launcher.png differ diff --git a/DroidFishTest/bin/res/drawable-mdpi/ic_launcher.png b/DroidFishTest/bin/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..02e96b9 Binary files /dev/null and b/DroidFishTest/bin/res/drawable-mdpi/ic_launcher.png differ diff --git a/DroidFishTest/proguard.cfg b/DroidFishTest/proguard.cfg new file mode 100644 index 0000000..b1cdf17 --- /dev/null +++ b/DroidFishTest/proguard.cfg @@ -0,0 +1,40 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/DroidFishTest/project.properties b/DroidFishTest/project.properties new file mode 100644 index 0000000..f049142 --- /dev/null +++ b/DroidFishTest/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-10 diff --git a/DroidFishTest/res/drawable-hdpi/ic_launcher.png b/DroidFishTest/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..8074c4c Binary files /dev/null and b/DroidFishTest/res/drawable-hdpi/ic_launcher.png differ diff --git a/DroidFishTest/res/drawable-ldpi/ic_launcher.png b/DroidFishTest/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 0000000..1095584 Binary files /dev/null and b/DroidFishTest/res/drawable-ldpi/ic_launcher.png differ diff --git a/DroidFishTest/res/drawable-mdpi/ic_launcher.png b/DroidFishTest/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..a07c69f Binary files /dev/null and b/DroidFishTest/res/drawable-mdpi/ic_launcher.png differ diff --git a/DroidFishTest/res/layout/main.xml b/DroidFishTest/res/layout/main.xml new file mode 100644 index 0000000..bc12cd8 --- /dev/null +++ b/DroidFishTest/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/DroidFishTest/res/values/strings.xml b/DroidFishTest/res/values/strings.xml new file mode 100644 index 0000000..3a99398 --- /dev/null +++ b/DroidFishTest/res/values/strings.xml @@ -0,0 +1,7 @@ + + + + Hello World! + DroidFishTestTest + + \ No newline at end of file diff --git a/DroidFishTest/src/org/petero/droidfish/engine/BookTest.java b/DroidFishTest/src/org/petero/droidfish/engine/BookTest.java new file mode 100644 index 0000000..e39444d --- /dev/null +++ b/DroidFishTest/src/org/petero/droidfish/engine/BookTest.java @@ -0,0 +1,78 @@ +/* + 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 . +*/ + +package org.petero.droidfish.engine; + + +import java.util.ArrayList; + +import junit.framework.TestCase; + +import org.petero.droidfish.gamelogic.ChessParseError; +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.MoveGen; +import org.petero.droidfish.gamelogic.Position; +import org.petero.droidfish.gamelogic.TextIO; + +/** + * + * @author petero + */ +public class BookTest extends TestCase { + + public BookTest() { + } + + /** + * Test of getBookMove method, of class Book. + */ + public void testGetBookMove() throws ChessParseError { + Position pos = TextIO.readFEN(TextIO.startPosFEN); + DroidBook book = DroidBook.getInstance(); + Move move = book.getBookMove(pos); + checkValid(pos, move); + + // Test "out of book" condition + pos.setCastleMask(0); + move = book.getBookMove(pos); + assertEquals(null, move); + } + + /** + * Test of getAllBookMoves method, of class Book. + */ + public void testGetAllBookMoves() throws ChessParseError { + Position pos = TextIO.readFEN(TextIO.startPosFEN); + DroidBook book = DroidBook.getInstance(); + String moveListString = book.getAllBookMoves(pos).first; + String[] strMoves = moveListString.split(":[0-9]* "); + assertTrue(strMoves.length > 1); + for (String strMove : strMoves) { + Move m = TextIO.stringToMove(pos, strMove); + checkValid(pos, m); + } + } + + /** Check that move is a legal move in position pos. */ + private void checkValid(Position pos, Move move) { + assertTrue(move != null); + ArrayList moveList = new MoveGen().pseudoLegalMoves(pos); + moveList = MoveGen.removeIllegal(pos, moveList); + assertTrue(moveList.contains(move)); + } +} diff --git a/DroidFishTest/src/org/petero/droidfish/engine/PolyglotBookTest.java b/DroidFishTest/src/org/petero/droidfish/engine/PolyglotBookTest.java new file mode 100644 index 0000000..3f7e379 --- /dev/null +++ b/DroidFishTest/src/org/petero/droidfish/engine/PolyglotBookTest.java @@ -0,0 +1,82 @@ +/* + 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 . +*/ + +package org.petero.droidfish.engine; + + +import junit.framework.TestCase; + +import org.petero.droidfish.gamelogic.ChessParseError; +import org.petero.droidfish.gamelogic.Position; +import org.petero.droidfish.gamelogic.TextIO; + + +public class PolyglotBookTest extends TestCase { + public PolyglotBookTest() { + } + + /** + * Test of getBookMove method, of class Book. + */ + public void testGetHashKey() throws ChessParseError { + // starting position + Position pos = TextIO.readFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + long key = 0x463b96181691fc9cL; + assertEquals(key, PolyglotBook.getHashKey(pos)); + + // position after e2e4 + pos = TextIO.readFEN("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"); + key = 0x823c9b50fd114196L; + assertEquals(key, PolyglotBook.getHashKey(pos)); + + // position after e2e4 d75 + pos = TextIO.readFEN("rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2"); + key = 0x0756b94461c50fb0L; + assertEquals(key, PolyglotBook.getHashKey(pos)); + + // position after e2e4 d7d5 e4e5 + pos = TextIO.readFEN("rnbqkbnr/ppp1pppp/8/3pP3/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 2"); + key = 0x662fafb965db29d4L; + assertEquals(key, PolyglotBook.getHashKey(pos)); + + // position after e2e4 d7d5 e4e5 f7f5 + pos = TextIO.readFEN("rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3"); + key = 0x22a48b5a8e47ff78L; + assertEquals(key, PolyglotBook.getHashKey(pos)); + + // position after e2e4 d7d5 e4e5 f7f5 e1e2 + pos = TextIO.readFEN("rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPPKPPP/RNBQ1BNR b kq - 0 3"); + key = 0x652a607ca3f242c1L; + assertEquals(key, PolyglotBook.getHashKey(pos)); + + // position after e2e4 d7d5 e4e5 f7f5 e1e2 e8f7 + pos = TextIO.readFEN("rnbq1bnr/ppp1pkpp/8/3pPp2/8/8/PPPPKPPP/RNBQ1BNR w - - 0 4"); + key = 0x00fdd303c946bdd9L; + assertEquals(key, PolyglotBook.getHashKey(pos)); + + // position after a2a4 b7b5 h2h4 b5b4 c2c4 + pos = TextIO.readFEN("rnbqkbnr/p1pppppp/8/8/PpP4P/8/1P1PPPP1/RNBQKBNR b KQkq c3 0 3"); + key = 0x3c8123ea7b067637L; + assertEquals(key, PolyglotBook.getHashKey(pos)); + + // position after a2a4 b7b5 h2h4 b5b4 c2c4 b4c3 a1a3 + pos = TextIO.readFEN("rnbqkbnr/p1pppppp/8/8/P6P/R1p5/1P1PPPP1/1NBQKBNR b Kkq - 0 4"); + key = 0x5c3f9b829b279560L; + assertEquals(key, PolyglotBook.getHashKey(pos)); + } +} diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTest.java new file mode 100644 index 0000000..40a106d --- /dev/null +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTest.java @@ -0,0 +1,461 @@ +/* + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import java.util.ArrayList; + +import junit.framework.TestCase; + + +/** + * + * @author petero + */ +public class GameTest extends TestCase { + + public GameTest() { + } + + /** + * Test of haveDrawOffer method, of class Game. + */ + public void testHaveDrawOffer() { + Game game = new Game(null, null, 0, 0, 0); + assertEquals(false, game.haveDrawOffer()); + + boolean res = game.processString("e4"); + assertEquals(true, res); + assertEquals(false, game.haveDrawOffer()); + + res = game.processString("draw offer e5"); + assertEquals(true, res); + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw offer does not imply draw + assertEquals(Piece.BPAWN, game.currPos().getPiece(Position.getSquare(4, 4))); // e5 move made + + res = game.processString("draw offer Nf3"); + assertEquals(true, res); + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw offer does not imply draw + assertEquals(Piece.WKNIGHT, game.currPos().getPiece(Position.getSquare(5, 2))); // Nf3 move made + + res = game.processString("Nc6"); + assertEquals(true, res); + assertEquals(false, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + assertEquals(Piece.BKNIGHT, game.currPos().getPiece(Position.getSquare(2, 5))); // Nc6 move made + + res = game.processString("draw offer Bb5"); + assertEquals(true, res); + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + assertEquals(Piece.WBISHOP, game.currPos().getPiece(Position.getSquare(1, 4))); // Bb5 move made + + res = game.processString("draw accept"); + assertEquals(true, res); + assertEquals(Game.GameState.DRAW_AGREE, game.getGameState()); // Draw by agreement + + game.undoMove(); // Undo "draw accept" + assertEquals(Piece.WBISHOP, game.currPos().getPiece(TextIO.getSquare("b5"))); + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.undoMove(); // Undo "Bb5" + assertEquals(Piece.EMPTY, game.currPos().getPiece(Position.getSquare(1, 4))); // Bb5 move undone + assertEquals(false, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.undoMove(); + assertEquals(Piece.EMPTY, game.currPos().getPiece(Position.getSquare(2, 5))); // Nc6 move undone + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + + game.redoMove(); + assertEquals(Piece.BKNIGHT, game.currPos().getPiece(Position.getSquare(2, 5))); // Nc6 move redone + assertEquals(false, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.redoMove(); + assertEquals(Piece.WBISHOP, game.currPos().getPiece(Position.getSquare(1, 4))); // Bb5 move redone + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.redoMove(); + assertEquals(Game.GameState.DRAW_AGREE, game.getGameState()); // Can redo draw accept + + // Test draw offer in connection with invalid move + game.newGame(); + assertEquals(false, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + + res = game.processString("draw offer e5"); + assertEquals(true, res); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.currPos())); // Move invalid, not executed + res = game.processString("e4"); + assertEquals(true, res); + assertEquals(true, game.haveDrawOffer()); // Previous draw offer still valid + assertEquals(Piece.WPAWN, game.currPos().getPiece(Position.getSquare(4, 3))); // e4 move made + + // Undo/redo shall clear "pendingDrawOffer". + game.newGame(); + game.processString("e4"); + game.processString("draw offer e4"); // Invalid black move + assertEquals(true, game.pendingDrawOffer); + game.undoMove(); + game.redoMove(); + game.processString("e5"); + assertEquals(true,game.currPos().whiteMove); + assertEquals(false, game.haveDrawOffer()); + } + + /** + * Test of draw by 50 move rule, of class Game. + */ + public void testDraw50() throws ChessParseError { + Game game = new Game(null, null, 0, 0, 0); + assertEquals(false, game.haveDrawOffer()); + boolean res = game.processString("draw 50"); + assertEquals(true, res); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw claim invalid + res = game.processString("e4"); + assertEquals(true, game.haveDrawOffer()); // Invalid claim converted to draw offer + + String fen = "8/4k3/8/P7/8/8/8/1N2K2R w K - 99 83"; + game.setPos(TextIO.readFEN(fen)); + res = game.processString("draw 50"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw claim invalid + + game.setPos(TextIO.readFEN(fen)); + game.processString("draw 50 Nc3"); + assertEquals(Game.GameState.DRAW_50, game.getGameState()); // Draw claim valid + assertEquals("Game over, draw by 50 move rule! [Nc3]", game.getGameStateString()); + + game.setPos(TextIO.readFEN(fen)); + game.processString("draw 50 a6"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Pawn move resets counter + assertEquals(Piece.WPAWN, game.currPos().getPiece(Position.getSquare(0, 5))); // Move a6 made + + game.setPos(TextIO.readFEN(fen)); + game.processString("draw 50 O-O"); + assertEquals(Game.GameState.DRAW_50, game.getGameState()); // Castling doesn't reset counter + + game.setPos(TextIO.readFEN(fen)); + game.processString("draw 50 Kf2"); + assertEquals(Game.GameState.DRAW_50, game.getGameState()); // Loss of castling right doesn't reset counter + + game.setPos(TextIO.readFEN(fen)); + game.processString("draw 50 Ke3"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Ke3 is invalid + assertEquals(true, game.currPos().whiteMove); + game.processString("a6"); + assertEquals(true, game.haveDrawOffer()); // Previous invalid claim converted to offer + game.processString("draw 50"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // 50 move counter reset. + res = game.processString("draw accept"); + assertEquals(true, res); + assertEquals(Game.GameState.DRAW_AGREE, game.getGameState()); // Can accept previous implicit offer + + fen = "3k4/R7/3K4/8/8/8/8/8 w - - 99 78"; + game.setPos(TextIO.readFEN(fen)); + game.processString("Ra8"); + assertEquals(Game.GameState.WHITE_MATE, game.getGameState()); + game.processString("draw 50"); + assertEquals(Game.GameState.WHITE_MATE, game.getGameState()); // Can't claim draw when game over + } + + /** + * Test of draw by repetition, of class Game. + */ + public void testDrawRep() throws ChessParseError { + Game game = new Game(null, null, 0, 0, 0); + assertEquals(false, game.haveDrawOffer()); + game.processString("Nc3"); + game.processString("Nc6"); + game.processString("Nb1"); + game.processString("Nb8"); + game.processString("Nf3"); + game.processString("Nf6"); + game.processString("Ng1"); + assertEquals(false, game.haveDrawOffer()); + game.processString("draw rep"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Claim not valid, one more move needed + game.processString("draw rep Nc6"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Claim not valid, wrong move claimed + assertEquals(Piece.BKNIGHT, game.currPos().getPiece(Position.getSquare(2, 5))); // Move Nc6 made + assertEquals(true, game.haveDrawOffer()); + game.undoMove(); + assertEquals(false, game.haveDrawOffer()); + assertEquals(Piece.EMPTY, game.currPos().getPiece(Position.getSquare(2, 5))); + game.processString("draw rep Ng8"); + assertEquals(Game.GameState.DRAW_REP, game.getGameState()); + assertEquals(Piece.EMPTY, game.currPos().getPiece(Position.getSquare(6, 7))); // Ng8 not played + + // Test draw by repetition when a "potential ep square but not real ep square" position is present. + game.newGame(); + game.processString("e4"); // e3 is not a real epSquare here + game.processString("Nf6"); + game.processString("Nf3"); + game.processString("Ng8"); + game.processString("Ng1"); + game.processString("Nf6"); + game.processString("Nf3"); + game.processString("Ng8"); + game.processString("draw rep Ng1"); + assertEquals(Game.GameState.DRAW_REP, game.getGameState()); + + // Now check the case when e3 *is* an epSquare + game.newGame(); + game.processString("Nf3"); + game.processString("d5"); + game.processString("Ng1"); + game.processString("d4"); + game.processString("e4"); // Here e3 is a real epSquare + game.processString("Nf6"); + game.processString("Nf3"); + game.processString("Ng8"); + game.processString("Ng1"); + game.processString("Nf6"); + game.processString("Nf3"); + game.processString("Ng8"); + game.processString("draw rep Ng1"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + + // EP capture not valid because it would leave the king in check. Therefore + // the position has been repeated three times at the end of the move sequence. + game.setPos(TextIO.readFEN("4k2n/8/8/8/4p3/8/3P4/3KR2N w - - 0 1")); + game.processString("d4"); + game.processString("Ng6"); + game.processString("Ng3"); + game.processString("Nh8"); + game.processString("Nh1"); + game.processString("Ng6"); + game.processString("Ng3"); + game.processString("Nh8"); + game.processString("draw rep Nh1"); + assertEquals(Game.GameState.DRAW_REP, game.getGameState()); + } + + /** + * Test of draw offer/accept/request command. + */ + public void testDrawBug() throws ChessParseError { + Game game = new Game(null, null, 0, 0, 0); + assertEquals(false, game.haveDrawOffer()); + game.processString("e4"); + game.processString("c5"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("draw accept"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("draw rep"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("draw 50"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + assertEquals(Piece.EMPTY, game.tree.currentPos.getPiece(TextIO.getSquare("e5"))); + } + + /** + * Test of resign command, of class Game. + */ + public void testResign() throws ChessParseError { + Game game = new Game(null, null, 0, 0, 0); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("f3"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("resign"); + assertEquals(Game.GameState.RESIGN_BLACK, game.getGameState()); + game.undoMove(); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("f3"); + game.processString("e5"); + game.processString("resign"); + assertEquals(Game.GameState.RESIGN_WHITE, game.getGameState()); + game.undoMove(); + game.processString("e5"); + game.processString("g4"); + game.processString("Qh4"); + assertEquals(Game.GameState.BLACK_MATE, game.getGameState()); + game.processString("resign"); + assertEquals(Game.GameState.BLACK_MATE, game.getGameState()); // Can't resign after game over + + String fen = "8/1p6/2rp2p1/8/p3Qqk1/6R1/PP4PK/8 b - - 3 42"; + game.setPos(TextIO.readFEN(fen)); + game.processString("resign"); + assertEquals(Game.GameState.RESIGN_BLACK, game.getGameState()); + } + + /** + * Test of processString method, of class Game. + */ + public void testProcessString() throws ChessParseError { + Game game = new Game(null, null, 0, 0, 0); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.currPos())); + boolean res = game.processString("Nf3"); + assertEquals(true, res); + assertEquals(1, game.currPos().halfMoveClock); + assertEquals(1, game.currPos().fullMoveCounter); + res = game.processString("d5"); + assertEquals(true, res); + assertEquals(0, game.currPos().halfMoveClock); + assertEquals(2, game.currPos().fullMoveCounter); + + game.undoMove(); + assertEquals(1, game.currPos().halfMoveClock); + assertEquals(1, game.currPos().fullMoveCounter); + game.undoMove(); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.currPos())); + game.undoMove(); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.currPos())); + + game.redoMove(); + assertEquals(1, game.currPos().halfMoveClock); + assertEquals(1, game.currPos().fullMoveCounter); + game.redoMove(); + assertEquals(0, game.currPos().halfMoveClock); + assertEquals(2, game.currPos().fullMoveCounter); + game.redoMove(); + assertEquals(0, game.currPos().halfMoveClock); + assertEquals(2, game.currPos().fullMoveCounter); + + game.newGame(); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.currPos())); + + String fen = "8/8/8/4k3/8/8/2p5/5K2 b - - 47 68"; + Position pos = TextIO.readFEN(fen); + game.setPos(TextIO.readFEN(fen)); + assertEquals(pos, game.currPos()); + + res = game.processString("junk"); + assertEquals(false, res); + + game.newGame(); + res = game.processString("e7e5"); + assertEquals(false, res); + } + + /** + * Test of getGameState method, of class Game. + */ + public void testGetGameState() throws ChessParseError { + Game game = new Game(null, null, 0, 0, 0); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("f3"); + game.processString("e5"); + game.processString("g4"); + game.processString("Qh4"); + assertEquals(Game.GameState.BLACK_MATE, game.getGameState()); + + game.setPos(TextIO.readFEN("5k2/5P2/5K2/8/8/8/8/8 b - - 0 1")); + assertEquals(Game.GameState.BLACK_STALEMATE, game.getGameState()); + } + + /** + * Test of insufficientMaterial method, of class Game. + */ + public void testInsufficientMaterial() throws ChessParseError { + Game game = new Game(null, null, 0, 0, 0); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.setPos(TextIO.readFEN("4k3/8/8/8/8/8/8/4K3 w - - 0 1")); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + final int a1 = Position.getSquare(0, 0); + Position pos = new Position(game.currPos()); + pos.setPiece(a1, Piece.WROOK); game.setPos(pos); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + pos.setPiece(a1, Piece.BQUEEN); game.setPos(pos); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + pos.setPiece(a1, Piece.WPAWN); game.setPos(pos); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + pos.setPiece(a1, Piece.BKNIGHT); game.setPos(pos); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + pos.setPiece(a1, Piece.WBISHOP); game.setPos(pos); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + + final int c1 = Position.getSquare(2, 0); + pos.setPiece(c1, Piece.WKNIGHT); game.setPos(pos); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + pos.setPiece(c1, Piece.BBISHOP); game.setPos(pos); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + pos.setPiece(c1, Piece.WBISHOP); game.setPos(pos); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + + final int b2 = Position.getSquare(1, 1); + pos.setPiece(b2, Piece.WBISHOP); game.setPos(pos); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + pos.setPiece(b2, Piece.BBISHOP); game.setPos(pos); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + + final int b3 = Position.getSquare(1, 2); + pos.setPiece(b3, Piece.WBISHOP); game.setPos(pos); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + + // Can't force mate with KNNK, but still not an automatic draw. + game.setPos(TextIO.readFEN("8/8/8/8/8/8/8/K3nnk1 w - - 0 1")); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + } + + /** Test that UCI history is not longer than necessary. + * We can't expect engines to handle null moves, for example. */ + public void testUCIHistory() throws ChessParseError { + Game game = new Game(null, null, 0, 0, 0); + + Pair> hist = game.getUCIHistory(); + assertEquals(0, hist.second.size()); + Position expectedPos = new Position(game.currPos()); + assertEquals(expectedPos, hist.first); + + game.processString("Nf3"); + hist = game.getUCIHistory(); + assertEquals(1, hist.second.size()); + assertEquals(TextIO.UCIstringToMove("g1f3"), hist.second.get(0)); + assertEquals(expectedPos, hist.first); + + game.processString("e5"); + hist = game.getUCIHistory(); + expectedPos = new Position(game.currPos()); + assertEquals(0, hist.second.size()); + assertEquals(expectedPos, hist.first); + + game.processString("Nc3"); + hist = game.getUCIHistory(); + assertEquals(1, hist.second.size()); + assertEquals(TextIO.UCIstringToMove("b1c3"), hist.second.get(0)); + assertEquals(expectedPos, hist.first); + + game.processString("Nc6"); + hist = game.getUCIHistory(); + assertEquals(2, hist.second.size()); + assertEquals(TextIO.UCIstringToMove("b1c3"), hist.second.get(0)); + assertEquals(TextIO.UCIstringToMove("b8c6"), hist.second.get(1)); + assertEquals(expectedPos, hist.first); + + game.processString("--"); + hist = game.getUCIHistory(); + expectedPos = new Position(game.currPos()); + assertEquals(0, hist.second.size()); + assertEquals(expectedPos, hist.first); + + game.processString("Nf6"); + hist = game.getUCIHistory(); + assertEquals(1, hist.second.size()); + assertEquals(TextIO.UCIstringToMove("g8f6"), hist.second.get(0)); + assertEquals(expectedPos, hist.first); + + for (int i = 0; i < 6; i++) + game.undoMove(); + hist = game.getUCIHistory(); + assertEquals(0, hist.second.size()); + expectedPos = TextIO.readFEN(TextIO.startPosFEN); + assertEquals(expectedPos, hist.first); + } +} diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java new file mode 100644 index 0000000..becb87c --- /dev/null +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java @@ -0,0 +1,651 @@ +/* + 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 . +*/ + +package org.petero.droidfish.gamelogic; + + +import java.util.ArrayList; +import java.util.List; + +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; + +public class GameTreeTest extends TestCase { + + public final void testGameTree() throws ChessParseError { + GameTree gt = new GameTree(null); + Position expectedPos = TextIO.readFEN(TextIO.startPosFEN); + assertEquals(expectedPos, gt.currentPos); + + List varList = gt.variations(); + assertEquals(0, varList.size()); + + int varNo = gt.addMove("e4", "", 0, "", ""); + 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 = gt.addMove("d4", "", 0, "", ""); + 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 = gt.addMove("g8f6", "", 0, "", ""); + 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 = gt.toByteArray(); + gt = new GameTree(null); + gt.fromByteArray(serialState); + 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, Integer> ml = gt.getMoveList(); + List 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); + gt.addMove("e4", "", 0, "", ""); + gt.addMove("d4", "", 0, "", ""); + assertEquals("*e4", getMoveListAsString(gt)); + + gt.goForward(0); + assertEquals("e4*", getMoveListAsString(gt)); + + gt.addMove("e5", "", 0, "", ""); + gt.addMove("c5", "", 0, "", ""); + assertEquals("e4* e5", getMoveListAsString(gt)); + + gt.goForward(1); + assertEquals("e4 c5*", getMoveListAsString(gt)); + + gt.addMove("Nf3", "", 0, "", ""); + gt.addMove("d4", "", 0, "", ""); + 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); + gt.addMove("e4", "", 0, "", ""); + gt.addMove("d4", "", 0, "", ""); + gt.addMove("c4", "", 0, "", ""); + 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); + gt.addMove("e4", "", 0, "", ""); + gt.addMove("d4", "", 0, "", ""); + gt.addMove("c4", "", 0, "", ""); + gt.addMove("f4", "", 0, "", ""); + 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); + + gt.addMove("g4", "", 0, "", ""); + gt.addMove("h4", "", 0, "", ""); + 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); + } + + private final String getVariationsAsString(GameTree gt) { + StringBuilder ret = new StringBuilder(); + List 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); + 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)); + + gt.addMove("e4", "", 0, "", ""); + 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)); + + gt.addMove("e5", "", 0, "", ""); + 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)); + + gt.addMove("Nf3", "", 0, "", ""); + gt.goForward(-1); + gt.addMove("Nc6", "", 0, "", ""); + 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)); + + gt.addMove("Bb5", "", 0, "", ""); + gt.goForward(-1); + gt.setRemainingTime(20000); + assertEquals(20000, gt.getRemainingTime(true, initialTime)); + assertEquals(30000, gt.getRemainingTime(false, initialTime)); + + gt.addMove("a6", "", 0, "", ""); + 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 getAllTokens(String s) { + PgnScanner sc = new PgnScanner(s); + List ret = new ArrayList(); + while (true) { + PgnToken tok = sc.nextToken(); + if (tok.type == PgnToken.EOF) + break; + ret.add(tok); + } + return ret; + } + + public final void testPgnScanner() throws ChessParseError { + List 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" 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); + + 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 = gt.addMove("e5", "", 0, "", ""); + 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 = gt.addMove("e4", "", 0, "", ""); + gt.goForward(varNo); + varNo = gt.addMove("e5", "", 0, "", ""); + gt.goForward(varNo); + varNo = gt.addMove("--", "resign", 0, "", ""); + gt.goBack(); + gt.goBack(); + varNo = gt.addMove("d4", "", 0, "", ""); + 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 = gt.addMove("cxb8N", "", 0, "", ""); + assertEquals(0, varNo); + varNo = gt.addMove("cxd8R+", "", 0, "", ""); + 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); + + } +} diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveGenTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveGenTest.java new file mode 100644 index 0000000..556698f --- /dev/null +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveGenTest.java @@ -0,0 +1,204 @@ +/* + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + * @author petero + */ +public class MoveGenTest extends TestCase { + + public MoveGenTest() { + } + + /** + * Test of pseudoLegalMoves method, of class MoveGen. + */ + public void testPseudoLegalMoves() throws ChessParseError { + String fen = "8/3k4/8/2n2pP1/1P6/1NB5/2QP4/R3K2R w KQ f6 0 2"; + Position pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + List strMoves = getMoveList(pos, false); + assertTrue(strMoves.contains("Ra1-d1")); + assertTrue(!strMoves.contains("Ra1-e1")); + assertTrue(!strMoves.contains("Ra1-f1")); + assertTrue(strMoves.contains("Ra1-a7+")); + assertTrue(strMoves.contains("Ke1-f2")); + assertTrue(!strMoves.contains("Ke1-g3")); + assertTrue(strMoves.contains("Bc3-f6")); + assertTrue(!strMoves.contains("Nb3xd2")); + + // Test castling + assertTrue(strMoves.contains("O-O")); + assertTrue(strMoves.contains("O-O-O")); + assertEquals(49, strMoves.size()); + + pos.setPiece(Position.getSquare(4,3), Piece.BROOK); + strMoves = getMoveList(pos, false); + assertTrue(!strMoves.contains("O-O")); // In check, not castling possible + assertTrue(!strMoves.contains("O-O-O")); + + pos.setPiece(Position.getSquare(4, 3), Piece.EMPTY); + pos.setPiece(Position.getSquare(5, 3), Piece.BROOK); + strMoves = getMoveList(pos, false); + assertTrue(!strMoves.contains("O-O")); // f1 attacked, short castle not possible + assertTrue(strMoves.contains("O-O-O")); + + pos.setPiece(Position.getSquare(5, 3), Piece.EMPTY); + pos.setPiece(Position.getSquare(6, 3), Piece.BBISHOP); + strMoves = getMoveList(pos, false); + assertTrue(strMoves.contains("O-O")); // d1 attacked, long castle not possible + assertTrue(!strMoves.contains("O-O-O")); + + pos.setPiece(Position.getSquare(6, 3), Piece.EMPTY); + pos.setCastleMask(1 << Position.A1_CASTLE); + strMoves = getMoveList(pos, false); + assertTrue(!strMoves.contains("O-O")); // short castle right has been lost + assertTrue(strMoves.contains("O-O-O")); + } + + /** + * Test of pseudoLegalMoves method, of class MoveGen. Pawn moves. + */ + public void testPawnMoves() throws ChessParseError { + String fen = "1r2k3/P1pppp1p/8/1pP3p1/1nPp2P1/n4p1P/1P2PP2/4KBNR w K b6 0 1"; + Position pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + List strMoves = getMoveList(pos, false); + assertTrue(strMoves.contains("c5xb6")); // En passant capture + assertTrue(strMoves.contains("a7-a8Q")); // promotion + assertTrue(strMoves.contains("a7-a8N")); // under promotion + assertTrue(strMoves.contains("a7xb8R#")); // capture promotion + assertTrue(strMoves.contains("b2-b3")); // pawn single move + assertTrue(strMoves.contains("b2xa3")); // pawn capture to the left + assertTrue(strMoves.contains("e2-e4")); // pawn double move + assertTrue(strMoves.contains("e2xf3")); // pawn capture to the right + assertEquals(22, strMoves.size()); + + pos.setEpSquare(-1); + strMoves = getMoveList(pos, false); + assertEquals(21, strMoves.size()); // No ep, one less move possible + + // Check black pawn moves + pos.setWhiteMove(false); + strMoves = getMoveList(pos, false); + assertTrue(strMoves.contains("f3xe2")); + assertTrue(strMoves.contains("d4-d3")); + assertTrue(strMoves.contains("e7-e6")); + assertTrue(strMoves.contains("e7-e5")); + assertEquals(28, strMoves.size()); + + // Check black pawn promotion + pos.setPiece(Position.getSquare(0,1), Piece.BPAWN); + strMoves = getMoveList(pos, false); + assertTrue(strMoves.contains("a2-a1Q+")); + assertTrue(strMoves.contains("a2-a1R+")); + assertTrue(strMoves.contains("a2-a1N")); + assertTrue(strMoves.contains("a2-a1B")); + } + + /** + * Test of inCheck method, of class MoveGen. + */ + public void testInCheck() { + Position pos = new Position(); + pos.setPiece(Position.getSquare(4,2), Piece.WKING); + pos.setPiece(Position.getSquare(4,7), Piece.BKING); + assertEquals(false, MoveGen.inCheck(pos)); + + pos.setPiece(Position.getSquare(3,3), Piece.BQUEEN); + assertEquals(true, MoveGen.inCheck(pos)); + pos.setPiece(Position.getSquare(3,3), Piece.BROOK); + assertEquals(false, MoveGen.inCheck(pos)); + pos.setPiece(Position.getSquare(3,3), Piece.BPAWN); + assertEquals(true, MoveGen.inCheck(pos)); + + pos.setPiece(Position.getSquare(3,3), Piece.EMPTY); + pos.setPiece(Position.getSquare(5,3), Piece.WQUEEN); + assertEquals(false, MoveGen.inCheck(pos)); + + pos.setPiece(Position.getSquare(4, 6), Piece.BROOK); + assertEquals(true, MoveGen.inCheck(pos)); + pos.setPiece(Position.getSquare(4, 4), Piece.WPAWN); + assertEquals(false, MoveGen.inCheck(pos)); + + pos.setPiece(Position.getSquare(2, 3), Piece.BKNIGHT); + assertEquals(true, MoveGen.inCheck(pos)); + + pos.setPiece(Position.getSquare(2, 3), Piece.EMPTY); + pos.setPiece(Position.getSquare(0, 4), Piece.BKNIGHT); + assertEquals(false, MoveGen.inCheck(pos)); + } + + /** + * Test of removeIllegal method, of class MoveGen. + */ + public void testRemoveIllegal() throws ChessParseError { + Position pos = TextIO.readFEN("8/3k4/8/2n1rpP1/1P6/1NB5/2QP4/R3K2R w KQ f6 0 1"); + List strMoves = getMoveList(pos, true); + assertTrue(strMoves.contains("Qc2-e4")); + assertTrue(strMoves.contains("Bc3xe5")); + assertTrue(strMoves.contains("Ke1-d1")); + assertTrue(strMoves.contains("Ke1-f1")); + assertTrue(strMoves.contains("Ke1-f2")); + assertEquals(5, strMoves.size()); + } + + /** + * Test that if king capture is possible, only a king capture move is returned in the move list. + */ + public void testKingCapture() throws ChessParseError { + Position pos = TextIO.readFEN("8/4k3/8/8/8/8/8/4RK2 b - - 0 1"); + pos.setWhiteMove(true); + List strMoves = getMoveList(pos, false); + assertEquals(1, strMoves.size()); + assertEquals("Re1xe7", strMoves.get(0)); + + pos.setPiece(Position.getSquare(0, 2), Piece.WBISHOP); + pos.setPiece(Position.getSquare(4, 1), Piece.WPAWN); + strMoves = getMoveList(pos, false); + assertEquals(1, strMoves.size()); + assertEquals("Ba3xe7", strMoves.get(0)); + + pos.setPiece(Position.getSquare(1, 3), Piece.WPAWN); + pos.setPiece(Position.getSquare(5, 5), Piece.WPAWN); + strMoves = getMoveList(pos, false); + assertEquals(1, strMoves.size()); + assertEquals("f6xe7", strMoves.get(0)); + } + + private List getMoveList(Position pos, boolean onlyLegal) { + ArrayList moves = new MoveGen().pseudoLegalMoves(pos); + if (onlyLegal) { + moves = MoveGen.removeIllegal(pos, moves); + } + ArrayList strMoves = new ArrayList(); + for (Move m : moves) { + String mStr = TextIO.moveToString(pos, m, true); + strMoves.add(mStr); +// System.out.println(mStr); + } + return strMoves; + } +} diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveTest.java new file mode 100644 index 0000000..22cbafc --- /dev/null +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveTest.java @@ -0,0 +1,59 @@ +/* + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import junit.framework.TestCase; + +/** + * + * @author petero + */ +public class MoveTest extends TestCase { + + public MoveTest() { + } + + /** + * Test of move constructor, of class Move. + */ + public void testMoveConstructor() { + int f = Position.getSquare(4, 1); + int t = Position.getSquare(4, 3); + int p = Piece.WROOK; + Move move = new Move(f, t, p); + assertEquals(move.from, f); + assertEquals(move.to,t); + assertEquals(move.promoteTo, p); + } + + /** + * Test of equals, of class Move. + */ + public void testEquals() { + Move m1 = new Move(Position.getSquare(0, 6), Position.getSquare(1, 7), Piece.WROOK); + Move m2 = new Move(Position.getSquare(0, 6), Position.getSquare(0, 7), Piece.WROOK); + Move m3 = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WROOK); + Move m4 = new Move(Position.getSquare(0, 6), Position.getSquare(1, 7), Piece.WKNIGHT); + Move m5 = new Move(Position.getSquare(0, 6), Position.getSquare(1, 7), Piece.WROOK); + assertTrue(!m1.equals(m2)); + assertTrue(!m1.equals(m3)); + assertTrue(!m1.equals(m4)); + assertTrue(m1.equals(m5)); + } +} diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/PieceTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/PieceTest.java new file mode 100644 index 0000000..380fa34 --- /dev/null +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/PieceTest.java @@ -0,0 +1,42 @@ +/* + 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 . +*/ + +package org.petero.droidfish.gamelogic; + + +import junit.framework.TestCase; + +/** + * + * @author petero + */ +public class PieceTest extends TestCase { + + public PieceTest() { + } + + /** + * Test of isWhite method, of class Piece. + */ + public void testIsWhite() { + assertEquals(false, Piece.isWhite(Piece.BBISHOP)); + assertEquals(true , Piece.isWhite(Piece.WBISHOP)); + assertEquals(true , Piece.isWhite(Piece.WKING)); + assertEquals(false, Piece.isWhite(Piece.BKING)); + } +} diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/PositionTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/PositionTest.java new file mode 100644 index 0000000..f0ef483 --- /dev/null +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/PositionTest.java @@ -0,0 +1,455 @@ +/* + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + * @author petero + */ +public class PositionTest extends TestCase { + + public PositionTest() { + } + + /** + * Test of getPiece method, of class Position. + */ + public void testGetPiece() throws ChessParseError { + Position pos = new Position(); + int result = pos.getPiece(0); + assertEquals(result, Piece.EMPTY); + + pos = TextIO.readFEN(TextIO.startPosFEN); + result = pos.getPiece(0); + assertEquals(result, Piece.WROOK); + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 2; y++) { + int p1 = pos.getPiece(Position.getSquare(x, y)); + int p2 = pos.getPiece(Position.getSquare(x, 7-y)); + int bwDiff = Piece.BPAWN - Piece.WPAWN; + assertEquals(p2, p1 + bwDiff); + } + } + } + + /** + * Test of getIndex method, of class Position. + */ + public void testGetIndex() { + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + int sq = Position.getSquare(x, y); + int x2 = Position.getX(sq); + int y2 = Position.getY(sq); + assertEquals(x, x2); + assertEquals(y, y2); + } + } + } + + /** + * Test of setPiece method, of class Position. + */ + public void testSetPiece() { + Position instance = new Position(); + assertEquals(Piece.EMPTY, instance.getPiece(Position.getSquare(0, 0))); + instance.setPiece(Position.getSquare(3, 4), Piece.WKING); + assertEquals(Piece.WKING, instance.getPiece(Position.getSquare(3, 4))); + } + + /** + * Test of makeMove method, of class Position. + */ + public void testMakeMove() throws ChessParseError { + Position pos = TextIO.readFEN(TextIO.startPosFEN); + Position origPos = new Position(pos); + assertTrue(pos.equals(origPos)); + Move move = new Move(Position.getSquare(4,1), Position.getSquare(4,3), Piece.EMPTY); + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + assertEquals(pos.whiteMove, false); + assertEquals(-1, pos.getEpSquare()); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(4,1))); + assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(4,3))); + assertTrue(!pos.equals(origPos)); + int castleMask = (1 << Position.A1_CASTLE) | + (1 << Position.H1_CASTLE) | + (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask,pos.getCastleMask()); + pos.unMakeMove(move, ui); + assertEquals(pos.whiteMove, true); + assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(4,1))); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(4,3))); + assertTrue(pos.equals(origPos)); + + String fen = "r1bqk2r/2ppbppp/p1n2n2/1pP1p3/B3P3/5N2/PP1P1PPP/RNBQK2R w KQkq b6 0 2"; + pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + origPos = new Position(pos); + assertEquals(Position.getSquare(1,5), pos.getEpSquare()); + + // Test capture + move = new Move(Position.getSquare(0, 3), Position.getSquare(1,4), Piece.EMPTY); + pos.makeMove(move, ui); + assertEquals(-1, pos.getEpSquare()); + assertEquals(Piece.WBISHOP, pos.getPiece(Position.getSquare(1,4))); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(0,3))); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos)); + + // Test castling + move = new Move(Position.getSquare(4, 0), Position.getSquare(6,0), Piece.EMPTY); + pos.makeMove(move, ui); + assertEquals(Piece.WROOK, pos.getPiece(Position.getSquare(5,0))); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(7,0))); + castleMask = (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask,pos.getCastleMask()); + assertEquals(-1, pos.getEpSquare()); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos)); + + // Test castling rights (king move) + move = new Move(Position.getSquare(4, 0), Position.getSquare(4,1), Piece.EMPTY); + pos.makeMove(move, ui); + castleMask = (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask,pos.getCastleMask()); + assertEquals(-1, pos.getEpSquare()); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos)); + + // Test castling rights (rook move) + move = new Move(Position.getSquare(7, 0), Position.getSquare(6,0), Piece.EMPTY); + pos.makeMove(move, ui); + castleMask = (1 << Position.A1_CASTLE) | + (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask,pos.getCastleMask()); + assertEquals(-1, pos.getEpSquare()); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos)); + + // Test en passant + move = new Move(Position.getSquare(2, 4), Position.getSquare(1,5), Piece.EMPTY); + pos.makeMove(move, ui); + assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(1,5))); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(2,4))); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,4))); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos)); + + // Test castling rights loss when rook captured + pos.setPiece(Position.getSquare(6,2), Piece.BKNIGHT); + pos.setWhiteMove(false); + Position origPos2 = new Position(pos); + move = new Move(Position.getSquare(6,2), Position.getSquare(7,0), Piece.EMPTY); + pos.makeMove(move, ui); + castleMask = (1 << Position.A1_CASTLE) | + (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask,pos.getCastleMask()); + assertEquals(-1, pos.getEpSquare()); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos2)); + } + + /** + * Test of makeMove method, of class Position. + */ + public void testPromotion() throws ChessParseError { + String fen = "r1bqk2r/1Pppbppp/p1n2n2/2P1p3/B3P3/5N2/Pp1P1PPP/R1BQK2R w KQkq - 0 1"; + Position pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + Position origPos = new Position(pos); + assertEquals(origPos, pos); + + Move move = new Move(Position.getSquare(1, 6), Position.getSquare(0,7), Piece.WQUEEN); + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,6))); + assertEquals(Piece.WQUEEN, pos.getPiece(Position.getSquare(0,7))); + pos.unMakeMove(move, ui); + assertEquals(origPos, pos); + + move = new Move(Position.getSquare(1, 6), Position.getSquare(1,7), Piece.WKNIGHT); + ui = new UndoInfo(); + pos.makeMove(move, ui); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,6))); + assertEquals(Piece.WKNIGHT, pos.getPiece(Position.getSquare(1,7))); + pos.unMakeMove(move, ui); + assertEquals(origPos, pos); + + pos.setWhiteMove(false); + origPos = new Position(pos); + + move = new Move(Position.getSquare(1, 1), Position.getSquare(2, 0), Piece.BROOK); + ui = new UndoInfo(); + pos.makeMove(move, ui); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,1))); + assertEquals(Piece.BROOK, pos.getPiece(Position.getSquare(2,0))); + pos.unMakeMove(move, ui); + assertEquals(origPos, pos); + } + + /** + * Test move counters, of class Position. + */ + public void testMoveCounters() throws ChessParseError { + String fen = "r1bqk2r/2ppbppp/p1n2n2/1pP1p3/B3P3/5N2/PP1P1PPP/RNBQK2R w KQkq b6 0 7"; + Position pos = TextIO.readFEN(fen); + + Move move = TextIO.stringToMove(pos, "Nc3"); + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + assertEquals(1, pos.halfMoveClock); + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + move = TextIO.stringToMove(pos, "O-O"); + pos.makeMove(move, ui); + assertEquals(1, pos.halfMoveClock); // Castling does not reset 50 move counter + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + move = TextIO.stringToMove(pos, "a3"); + pos.makeMove(move, ui); + assertEquals(0, pos.halfMoveClock); // Pawn move resets 50 move counter + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + move = TextIO.stringToMove(pos, "Nxe5"); + pos.makeMove(move, ui); + assertEquals(0, pos.halfMoveClock); // Capture move resets 50 move counter + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + move = TextIO.stringToMove(pos, "cxb6"); + pos.makeMove(move, ui); + assertEquals(0, pos.halfMoveClock); // EP capture move resets 50 move counter + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + move = TextIO.stringToMove(pos, "Kf1"); + pos.makeMove(move, ui); + assertEquals(1, pos.halfMoveClock); // Loss of castling rights does not reset 50 move counter + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + Move firstMove = TextIO.stringToMove(pos, "Nc3"); + UndoInfo firstUi = new UndoInfo(); + pos.makeMove(move, firstUi); + move = TextIO.stringToMove(pos, "O-O"); + pos.makeMove(move, ui); + assertEquals(2, pos.halfMoveClock); + assertEquals(8, pos.fullMoveCounter); // Black move increases fullMoveCounter + pos.unMakeMove(move, ui); + pos.unMakeMove(firstMove, firstUi); + + fen = "8/8/8/4k3/8/8/2p5/5K2 b - - 47 68"; + pos = TextIO.readFEN(fen); + move = TextIO.stringToMove(pos, "c1Q"); + pos.makeMove(move, ui); + assertEquals(0, pos.halfMoveClock); // Pawn promotion resets 50 move counter + assertEquals(69, pos.fullMoveCounter); + } + + /** + * Test of drawRuleEquals, of class Position. + */ + public void testDrawRuleEquals() throws ChessParseError { + Position pos = TextIO.readFEN(TextIO.startPosFEN); + Position origPos = new Position(pos); + UndoInfo ui = new UndoInfo(); + pos.makeMove(TextIO.stringToMove(pos, "Nf3"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Nf6"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Ng1"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Ng8"), ui); + assertEquals(true, pos.drawRuleEquals(origPos)); + assertEquals(false, pos.equals(origPos)); // Move counters have changed + + String fen = "r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1"; + pos = TextIO.readFEN(fen); + origPos = new Position(pos); + pos.makeMove(TextIO.stringToMove(pos, "Ke2"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Be7"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Ke1"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Bf8"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); // Not equal, castling rights lost + + pos = TextIO.readFEN(TextIO.startPosFEN); + pos.makeMove(TextIO.stringToMove(pos, "c4"), ui); + pos.makeMove(TextIO.stringToMove(pos, "a6"), ui); + pos.makeMove(TextIO.stringToMove(pos, "c5"), ui); + pos.makeMove(TextIO.stringToMove(pos, "b5"), ui); + assertEquals(Position.getSquare(1, 5), pos.getEpSquare()); + origPos = new Position(pos); + pos.makeMove(TextIO.stringToMove(pos, "Nc3"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nc6"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nb1"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nb8"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); // Not equal, en passant rights lost + } + + /** + * Test of hashCode method, of class Position. + */ + public void testHashCode() throws ChessParseError { + Position pos = TextIO.readFEN(TextIO.startPosFEN); + long h1 = pos.zobristHash(); + assertEquals(h1, pos.computeZobristHash()); + UndoInfo ui = new UndoInfo(); + Move move = TextIO.stringToMove(pos, "e4"); + pos.makeMove(move, ui); + assertTrue(h1 != pos.zobristHash()); + pos.unMakeMove(move, ui); + assertTrue(h1 == pos.zobristHash()); + + pos.setWhiteMove(!pos.whiteMove); + long h4 = pos.zobristHash(); + assertEquals(h4, pos.computeZobristHash()); + assertTrue(h1 != pos.zobristHash()); + pos.setWhiteMove(!pos.whiteMove); + assertTrue(h1 == pos.zobristHash()); + + pos.setCastleMask(0); + assertTrue(h1 != pos.zobristHash()); + + pos = TextIO.readFEN("rnbqkbnr/pppp1ppp/8/2P1p3/8/8/PP1PPPPP/RNBQKBNR b KQkq - 0 1"); + h1 = pos.zobristHash(); + assertEquals(h1, pos.computeZobristHash()); + + String[] moves = { + "b5", "Nc3", "Nf6", "Nb1", "Ng8", "Nc3", "Nf6", "Nb1", "Ng8", "Nc3", "d5", + "cxd6", "Qxd6", "h4", "Be6", "h5", "Nc6", "h6", "o-o-o", "hxg7", "Nf6", "gxh8Q", "Be7" + }; + List uiList = new ArrayList(); + List hashList = new ArrayList(); + List moveList = new ArrayList(); + for (int i = 0; i < moves.length; i++) { + uiList.add(new UndoInfo()); + Move m = TextIO.stringToMove(pos, moves[i]); + moveList.add(m); + pos.makeMove(m, uiList.get(i)); + long h = pos.zobristHash(); + assertEquals(h, pos.computeZobristHash()); + hashList.add(h); + } + assertTrue(!hashList.get(0).equals(hashList.get(4))); + assertTrue(hashList.get(4).equals(hashList.get(8))); + for (int i = moves.length - 1; i >= 0; i--) { + pos.unMakeMove(moveList.get(i), uiList.get(i)); + long h = pos.zobristHash(); + assertEquals(h, pos.computeZobristHash()); + assertEquals(h, i > 0 ? hashList.get(i - 1) : h1); + } + } + + /** + * Test of getKingSq method, of class Position. + */ + public void testGetKingSq() throws ChessParseError { + Position pos = TextIO.readFEN(TextIO.startPosFEN); + assertEquals(TextIO.getSquare("e1"), pos.getKingSq(true)); + assertEquals(TextIO.getSquare("e8"), pos.getKingSq(false)); + pos = TextIO.readFEN("r1bq1bnr/ppppkppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQ - 0 4"); + assertEquals(TextIO.getSquare("e1"), pos.getKingSq(true)); + assertEquals(TextIO.getSquare("e7"), pos.getKingSq(false)); + UndoInfo ui = new UndoInfo(); + pos.makeMove(TextIO.stringToMove(pos, "o-o"), ui); + assertEquals(TextIO.getSquare("g1"), pos.getKingSq(true)); + assertEquals(TextIO.getSquare("e7"), pos.getKingSq(false)); + pos.makeMove(TextIO.stringToMove(pos, "Kd6"), ui); + assertEquals(TextIO.getSquare("g1"), pos.getKingSq(true)); + assertEquals(TextIO.getSquare("d6"), pos.getKingSq(false)); + } + + public void testNullMove() throws ChessParseError { + Move nullMove = new Move(0, 0, 0); + UndoInfo ui = new UndoInfo(); + + Position pos = TextIO.readFEN(TextIO.startPosFEN); + Position origPos = new Position(pos); + pos.makeMove(nullMove, ui); + assertEquals(false, pos.whiteMove); + assertEquals(true, pos.a1Castle()); + assertEquals(Piece.WROOK, pos.getPiece(0)); + pos.unMakeMove(nullMove, ui); + assertTrue(pos.equals(origPos)); + + pos = TextIO.readFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Kkq - 5 1"); + origPos = new Position(pos); + pos.makeMove(nullMove, ui); + assertEquals(false, pos.whiteMove); + assertEquals(false, pos.a1Castle()); + assertEquals(Piece.WROOK, pos.getPiece(0)); + assertEquals(0, pos.halfMoveClock); + pos.unMakeMove(nullMove, ui); + assertTrue(pos.equals(origPos)); + + pos = TextIO.readFEN("7k/8/8/8/8/8/4P3/K7 w - - 5 1"); + origPos = new Position(pos); + pos.makeMove(nullMove, ui); + assertEquals(false, pos.whiteMove); + assertEquals(false, pos.a1Castle()); + assertEquals(0, pos.halfMoveClock); + assertEquals(0, pos.getKingSq(true)); + assertEquals(63, pos.getKingSq(false)); + + UndoInfo ui2 = new UndoInfo(); + pos.makeMove(nullMove, ui2); + assertEquals(true, pos.whiteMove); + assertEquals(false, pos.a1Castle()); + assertEquals(0, pos.halfMoveClock); + assertEquals(0, pos.getKingSq(true)); + assertEquals(63, pos.getKingSq(false)); + + pos.unMakeMove(nullMove, ui2); + assertEquals(false, pos.whiteMove); + assertEquals(false, pos.a1Castle()); + assertEquals(0, pos.halfMoveClock); + assertEquals(0, pos.getKingSq(true)); + assertEquals(63, pos.getKingSq(false)); + + pos.unMakeMove(nullMove, ui); + assertTrue(pos.equals(origPos)); + + pos = TextIO.readFEN("5k2/8/4R1K1/8/8/8/8/8 b - - 5 1"); + origPos = new Position(pos); + pos.makeMove(nullMove, ui); + assertEquals(true, pos.whiteMove); + assertEquals(false, pos.a1Castle()); + assertEquals(0, pos.halfMoveClock); + + pos.unMakeMove(nullMove, ui); + assertTrue(pos.equals(origPos)); + } +} diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/TextIOTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/TextIOTest.java new file mode 100644 index 0000000..77622b5 --- /dev/null +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/TextIOTest.java @@ -0,0 +1,382 @@ +/* + 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 . +*/ + +package org.petero.droidfish.gamelogic; + + +import junit.framework.TestCase; + +/** + * + * @author petero + */ +public class TextIOTest extends TestCase { + + public TextIOTest() { + } + + /** + * Test of readFEN method, of class TextIO. + */ + public void testReadFEN() throws ChessParseError { + String fen = "rnbqk2r/1p3ppp/p7/1NpPp3/QPP1P1n1/P4N2/4KbPP/R1B2B1R b kq - 0 1"; + Position pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + assertEquals(pos.getPiece(Position.getSquare(0, 3)), Piece.WQUEEN); + assertEquals(pos.getPiece(Position.getSquare(4, 7)), Piece.BKING); + assertEquals(pos.getPiece(Position.getSquare(4, 1)), Piece.WKING); + assertEquals(pos.whiteMove, false); + assertEquals(pos.a1Castle(), false); + assertEquals(pos.h1Castle(), false); + assertEquals(pos.a8Castle(), true); + assertEquals(pos.h8Castle(), true); + + fen = "8/3k4/8/5pP1/1P6/1NB5/2QP4/R3K2R w KQ f6 1 2"; + pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + assertEquals(1, pos.halfMoveClock); + assertEquals(2, pos.fullMoveCounter); + + // Must have exactly one king + boolean wasError = testFENParseError("8/8/8/8/8/8/8/kk1K4 w - - 0 1"); + assertEquals(true, wasError); + + // Must not be possible to capture the king + wasError = testFENParseError("8/8/8/8/8/8/8/k1RK4 w - - 0 1"); + assertEquals(true, wasError); + + // Make sure bogus en passant square information is removed + fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"; + pos = TextIO.readFEN(fen); + assertEquals(-1, pos.getEpSquare()); + + // Test for too many rows (slashes) + wasError = testFENParseError("8/8/8/8/4k3/8/8/8/KBN5 w - - 0 1"); + assertEquals(true, wasError); + + // Test for too many columns + wasError = testFENParseError("8K/8/8/8/4k3/8/8/8 w - - 0 1"); + assertEquals(true, wasError); + + // Pawns must not be on first/last rank + wasError = testFENParseError("kp6/8/8/8/8/8/8/K7 w - - 0 1"); + assertEquals(true, wasError); + + wasError = testFENParseError("kr/pppp/8/8/8/8/8/KBR w"); + assertEquals(false, wasError); // OK not to specify castling flags and ep square + + wasError = testFENParseError("k/8/8/8/8/8/8/K"); + assertEquals(true, wasError); // Error side to move not specified + + wasError = testFENParseError(""); + assertEquals(true, wasError); + + wasError = testFENParseError(" |"); + assertEquals(true, wasError); + + wasError = testFENParseError("1B1B4/6k1/7r/7P/6q1/r7/q7/7K b - - acn 6; acs 0;"); + assertEquals(false, wasError); // Extra stuff after FEN string is allowed + + pos = TextIO.readFEN("3r2k1/p4p1p/1ppq2p1/8/2PpQ3/1P5P/P4PP1/3R1K2 b - - 1 26\n"); + assertEquals(26, pos.fullMoveCounter); + + // Test that insane move numbers are rejected. Otherwise, could cause problems + // (excessive memory usage) for the clock history class. + pos = TextIO.readFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 10000"); + assertEquals(10000, pos.fullMoveCounter); + } + + /** + * Test that readFEN removes bogus castle flags. + */ + public void testReadFENCastleFlags() throws ChessParseError { + String fenBogus = "rnbqk2r/1p3ppp/p7/1NpPp3/QPP1P1n1/P4N2/4KbPP/R1B2B1R w KQkq - 0 1"; + Position pos = TextIO.readFEN(fenBogus); + String fenCorrect = "rnbqk2r/1p3ppp/p7/1NpPp3/QPP1P1n1/P4N2/4KbPP/R1B2B1R w kq - 0 1"; + assertEquals(fenCorrect, TextIO.toFEN(pos)); + } + + /** Tests if trying to parse a FEN string causes an error. */ + private boolean testFENParseError(String fen) { + boolean wasError; + wasError = false; + try { + TextIO.readFEN(fen); + } catch (ChessParseError err) { + wasError = true; + } + return wasError; + } + + /** + * Test of moveToString method, of class TextIO. + */ + public void testMoveToString() throws ChessParseError { + Position pos = TextIO.readFEN(TextIO.startPosFEN); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(pos)); + Move move = new Move(Position.getSquare(4, 1), Position.getSquare(4, 3), + Piece.EMPTY); + boolean longForm = true; + String result = TextIO.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); + assertEquals("Ng1-f3", result); + + move = new Move(Position.getSquare(4, 7), Position.getSquare(2, 7), + Piece.EMPTY); + result = TextIO.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); + assertEquals("c7xb8R+", result); + + move = new Move(Position.getSquare(2,6), Position.getSquare(2,7), Piece.WKNIGHT); + result = TextIO.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); + 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); + assertEquals("--", result); + result = TextIO.moveToString(pos, nullMove, true); + assertEquals("--", result); + } + + /** + * Test of moveToString method, of class TextIO, mate/stalemate tests. + */ + public void testMoveToStringMate() throws ChessParseError { + Position pos = TextIO.readFEN("3k4/1PR5/3N4/8/4K3/8/8/8 w - - 0 1"); + boolean longForm = true; + + Move move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WROOK); + String result = TextIO.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); + 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); + assertEquals("b7-b8N", result); + + move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WBISHOP); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("b7-b8B", result); // stalemate + } + + /** + * Test of moveToString method, of class TextIO, short form. + */ + public void testMoveToStringShortForm() throws ChessParseError { + String fen = "r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1"; + Position pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + boolean longForm = false; + + Move move = new Move(Position.getSquare(4,5), Position.getSquare(4,3), Piece.EMPTY); + String result = TextIO.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); + 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); + 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); + 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); + assertEquals("a1Q", result); // Normal promotion + + move = new Move(Position.getSquare(0,1), Position.getSquare(1,0), Piece.BQUEEN); + result = TextIO.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); + assertEquals("axb1N", result); // Capture promotion + + move = new Move(Position.getSquare(3,6), Position.getSquare(4,4), Piece.EMPTY); + result = TextIO.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); + 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); + assertEquals("Rfd8", result); // File disambiguation needed + } + + /** + * Test of stringToMove method, of class TextIO. + */ + public void testStringToMove() throws ChessParseError { + Position pos = TextIO.readFEN("r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1"); + + Move mNe5 = new Move(Position.getSquare(3, 6), Position.getSquare(4, 4), Piece.EMPTY); + Move m = TextIO.stringToMove(pos, "Ne5"); + assertEquals(mNe5, m); + m = TextIO.stringToMove(pos, "ne"); + assertEquals(mNe5, m); + m = TextIO.stringToMove(pos, "N"); + assertEquals(null, m); + + Move mQc6e4 = new Move(Position.getSquare(2, 5), Position.getSquare(4, 3), Piece.EMPTY); + m = TextIO.stringToMove(pos, "Qc6-e4"); + assertEquals(mQc6e4, m); + m = TextIO.stringToMove(pos, "Qc6e4"); + assertEquals(mQc6e4, m); + m = TextIO.stringToMove(pos, "Qce4"); + assertEquals(null, m); + m = TextIO.stringToMove(pos, "Q6e4"); + assertEquals(null, m); + + Move maxb1Q = new Move(Position.getSquare(0, 1), Position.getSquare(1, 0), Piece.BQUEEN); + m = TextIO.stringToMove(pos, "axb1Q"); + assertEquals(maxb1Q, m); + m = TextIO.stringToMove(pos, "axb1Q#"); + assertEquals(maxb1Q, m); + m = TextIO.stringToMove(pos, "axb1Q+"); + assertEquals(maxb1Q, m); + + Move mh5= new Move(Position.getSquare(7, 6), Position.getSquare(7, 4), Piece.EMPTY); + m = TextIO.stringToMove(pos, "h5"); + assertEquals(mh5, m); + m = TextIO.stringToMove(pos, "h7-h5"); + assertEquals(mh5, m); + m = TextIO.stringToMove(pos, "h"); + assertEquals(null, m); + + pos = TextIO.readFEN("r1b1k2r/1pqpppbp/p5pn/3BP3/8/2pP4/PPPBQPPP/R3K2R w KQkq - 0 12"); + m = TextIO.stringToMove(pos, "bxc3"); + assertEquals(TextIO.getSquare("b2"), m.from); + m = TextIO.stringToMove(pos, "Bxc3"); + assertEquals(TextIO.getSquare("d2"), m.from); + m = TextIO.stringToMove(pos, "bxc"); + assertEquals(TextIO.getSquare("b2"), m.from); + m = TextIO.stringToMove(pos, "Bxc"); + assertEquals(TextIO.getSquare("d2"), m.from); + + // Test castling. o-o is a substring of o-o-o, which could cause problems. + pos = TextIO.readFEN("5k2/p1pQn3/1p2Bp1r/8/4P1pN/2N5/PPP2PPP/R3K2R w KQ - 0 16"); + Move kCastle = new Move(Position.getSquare(4,0), Position.getSquare(6,0), Piece.EMPTY); + Move qCastle = new Move(Position.getSquare(4,0), Position.getSquare(2,0), Piece.EMPTY); + m = TextIO.stringToMove(pos, "o"); + assertEquals(null, m); + m = TextIO.stringToMove(pos, "o-o"); + assertEquals(kCastle, m); + m = TextIO.stringToMove(pos, "O-O"); + assertEquals(kCastle, m); + m = TextIO.stringToMove(pos, "0-0"); + assertEquals(kCastle, m); + m = TextIO.stringToMove(pos, "O-O-O"); + assertEquals(qCastle, m); + m = TextIO.stringToMove(pos, "o-o-o"); + assertEquals(qCastle, m); + m = TextIO.stringToMove(pos, "0-0-0"); + assertEquals(qCastle, m); + + // Test 'o-o+' + pos.setPiece(Position.getSquare(5,1), Piece.EMPTY); + pos.setPiece(Position.getSquare(5,5), Piece.EMPTY); + m = TextIO.stringToMove(pos, "o"); + assertEquals(null, m); + m = TextIO.stringToMove(pos, "o-o"); + assertEquals(kCastle, m); + m = TextIO.stringToMove(pos, "o-o-o"); + assertEquals(qCastle, m); + m = TextIO.stringToMove(pos, "o-o+"); + assertEquals(kCastle, m); + + // Test d8=Q+ syntax + pos = TextIO.readFEN("1r3r2/2kP2Rp/p1bN1p2/2p5/5P2/2P5/P5PP/3R2K1 w - -"); + m = TextIO.stringToMove(pos, "d8=Q+"); + Move m2 = TextIO.stringToMove(pos, "d8Q"); + assertEquals(m2, m); + + // Test null move + pos = TextIO.readFEN(TextIO.startPosFEN); + Move nullMove = new Move(0, 0, 0); + m = TextIO.stringToMove(pos, "--"); + assertEquals(nullMove, m); + + // Test extra characters + pos = TextIO.readFEN(TextIO.startPosFEN); + Move mNf3 = new Move(TextIO.getSquare("g1"), TextIO.getSquare("f3"), Piece.EMPTY); + assertEquals(mNf3, TextIO.stringToMove(pos, "Ngf3")); + assertEquals(mNf3, TextIO.stringToMove(pos, "Ng1f3")); + assertEquals(mNf3, TextIO.stringToMove(pos, "Ng1-f3")); + assertEquals(mNf3, TextIO.stringToMove(pos, "g1f3")); + assertEquals(mNf3, TextIO.stringToMove(pos, "N1f3")); + assertEquals(mNf3, TextIO.stringToMove(pos, "Ngf")); + assertEquals(mNf3, TextIO.stringToMove(pos, "Nf")); + } + + /** + * Test of getSquare method, of class TextIO. + */ + public void testGetSquare() throws ChessParseError { + assertEquals(Position.getSquare(0, 0), TextIO.getSquare("a1")); + assertEquals(Position.getSquare(1, 7), TextIO.getSquare("b8")); + assertEquals(Position.getSquare(3, 3), TextIO.getSquare("d4")); + assertEquals(Position.getSquare(4, 3), TextIO.getSquare("e4")); + assertEquals(Position.getSquare(3, 1), TextIO.getSquare("d2")); + assertEquals(Position.getSquare(7, 7), TextIO.getSquare("h8")); + } + + /** + * Test of squareToString method, of class TextIO. + */ + public void testSquareToString() { + assertEquals("a1", TextIO.squareToString(Position.getSquare(0, 0))); + assertEquals("h6", TextIO.squareToString(Position.getSquare(7, 5))); + assertEquals("e4", TextIO.squareToString(Position.getSquare(4, 3))); + } + + /** + * Test of asciiBoard method, of class TextIO. + */ + public void testAsciiBoard() throws ChessParseError { + Position pos = TextIO.readFEN("r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1"); + String aBrd = TextIO.asciiBoard(pos); +// System.out.print(aBrd); + assertEquals(12, aBrd.length() - aBrd.replaceAll("\\*", "").length()); // 12 black pieces + assertEquals(3, aBrd.length() - aBrd.replaceAll("\\*Q", " ").length()); // 3 black queens + assertEquals(3, aBrd.length() - aBrd.replaceAll(" P", " ").length()); // 3 white pawns + } +} diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/TimeControlTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/TimeControlTest.java new file mode 100644 index 0000000..fab2c24 --- /dev/null +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/TimeControlTest.java @@ -0,0 +1,136 @@ +/* + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import junit.framework.TestCase; + + +public class TimeControlTest extends TestCase { + public TimeControlTest() { + } + + public void testElapsedTime() { + TimeControl tc = new TimeControl(); + long totTime = 5 * 60 * 1000; + long t0 = 1000; + tc.setTimeControl(totTime, 0, 0); + tc.setCurrentMove(1, true, totTime, totTime); + assertEquals(0, tc.getMovesToTC()); + assertEquals(0, tc.getIncrement()); + assertEquals(totTime, tc.getRemainingTime(true, 0)); + tc.startTimer(t0); + int remain = tc.moveMade(t0 + 1000, true); + assertEquals(totTime - 1000, remain); + + tc.setCurrentMove(2, true, totTime - 1000, totTime); + assertEquals(0, tc.getMovesToTC()); + assertEquals(totTime - 1000, tc.getRemainingTime(true, t0 + 4711)); + assertEquals(totTime, tc.getRemainingTime(false, t0 + 4711)); + + tc.setCurrentMove(1, false, totTime - 1000, totTime); + assertEquals(0, tc.getMovesToTC()); + assertEquals(totTime - 1000, tc.getRemainingTime(true, t0 + 4711)); + assertEquals(totTime, tc.getRemainingTime(false, t0 + 4711)); + + tc.startTimer(t0 + 3000); + assertEquals(totTime - 1000, tc.getRemainingTime(true, t0 + 5000)); + assertEquals(totTime - 2000, tc.getRemainingTime(false, t0 + 5000)); + tc.stopTimer(t0 + 8000); + assertEquals(totTime - 1000, tc.getRemainingTime(true, t0 + 4711)); + assertEquals(totTime - 5000, tc.getRemainingTime(false, t0 + 4711)); + remain = tc.moveMade(t0 + 8000, true); + assertEquals(totTime - 5000, remain); + tc.setCurrentMove(2, true, totTime - 1000, totTime - 5000); + assertEquals(totTime - 1000, tc.getRemainingTime(true, t0 + 4711)); + assertEquals(totTime - 5000, tc.getRemainingTime(false, t0 + 4711)); + } + + /** Test getMovesToTC */ + public void testTimeControl() { + TimeControl tc = new TimeControl(); + tc.setTimeControl(2 * 60 * 1000, 40, 0); + tc.setCurrentMove(1, true, 0, 0); + assertEquals(40, tc.getMovesToTC()); + tc.setCurrentMove(1, false, 0, 0); + assertEquals(40, tc.getMovesToTC()); + + tc.setCurrentMove(2, true, 0, 0); + assertEquals(39, tc.getMovesToTC()); + + tc.setCurrentMove(40, true, 0, 0); + assertEquals(1, tc.getMovesToTC()); + + tc.setCurrentMove(41, true, 0, 0); + assertEquals(40, tc.getMovesToTC()); + + tc.setCurrentMove(80, true, 0, 0); + assertEquals(1, tc.getMovesToTC()); + + tc.setCurrentMove(81, true, 0, 0); + assertEquals(40, tc.getMovesToTC()); + } + + public void testExtraTime() { + TimeControl tc = new TimeControl(); + final long timeCont = 60 * 1000; + int wBaseTime = (int)timeCont; + int bBaseTime = (int)timeCont; + final long inc = 700; + tc.setTimeControl(timeCont, 5, inc); + tc.setCurrentMove(5, true, wBaseTime, bBaseTime); + long t0 = 1342134; + assertEquals(timeCont, tc.getRemainingTime(true, t0 + 4711)); + assertEquals(timeCont, tc.getRemainingTime(false, t0 + 4711)); + + tc.startTimer(t0 + 1000); + wBaseTime = tc.moveMade(t0 + 2000, true); + tc.setCurrentMove(5, false, wBaseTime, bBaseTime); + assertEquals(timeCont - 1000 + timeCont + inc, tc.getRemainingTime(true, t0 + 4711)); + assertEquals(timeCont, tc.getRemainingTime(false, t0 + 4711)); + + tc.startTimer(t0 + 2000); + bBaseTime = tc.moveMade(t0 + 6000, true); + tc.setCurrentMove(6, true, wBaseTime, bBaseTime); + assertEquals(timeCont - 1000 + timeCont + inc, tc.getRemainingTime(true, t0 + 4711)); + assertEquals(timeCont - 4000 + timeCont + inc, tc.getRemainingTime(false, t0 + 4711)); + + tc.startTimer(t0 + 6000); + wBaseTime = tc.moveMade(t0 + 9000, true); + tc.setCurrentMove(6, false, wBaseTime, bBaseTime); + assertEquals(timeCont - 1000 + timeCont + inc - 3000 + inc, tc.getRemainingTime(true, t0 + 4711)); + assertEquals(timeCont - 4000 + timeCont + inc, tc.getRemainingTime(false, t0 + 4711)); + + // No increment when move made int paused mode, ie analysis mode + tc.startTimer(t0 + 9000); + bBaseTime = tc.moveMade(t0 + 10000, false); + tc.setCurrentMove(7, true, wBaseTime, bBaseTime); + assertEquals(timeCont - 1000 + timeCont + inc - 3000 + inc, tc.getRemainingTime(true, t0 + 4711)); + assertEquals(timeCont - 4000 + timeCont + inc - 1000, tc.getRemainingTime(false, t0 + 4711)); + + // No extra time when passing time control in analysis mode + tc.setTimeControl(timeCont, 1, inc); + wBaseTime = bBaseTime = (int)timeCont; + tc.setCurrentMove(1, true, wBaseTime, bBaseTime); + tc.startTimer(t0 + 1000); + wBaseTime = tc.moveMade(t0 + 3000, false); + tc.setCurrentMove(1, false, wBaseTime, bBaseTime); + assertEquals(timeCont - 2000 + (timeCont + inc)*0, tc.getRemainingTime(true, t0 + 4711)); + assertEquals(timeCont, tc.getRemainingTime(false, t0 + 4711)); + } +}