/* $Id$ Copyright (C) 2006-2007 by David Cotton 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package fr.free.jchecs.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static fr.free.jchecs.core.BoardFactory.Type.FASTEST; import static fr.free.jchecs.core.Piece.BLACK_BISHOP; import static fr.free.jchecs.core.Piece.BLACK_KING; import static fr.free.jchecs.core.Piece.BLACK_KNIGHT; import static fr.free.jchecs.core.Piece.BLACK_PAWN; import static fr.free.jchecs.core.Piece.BLACK_QUEEN; import static fr.free.jchecs.core.Piece.BLACK_ROOK; import static fr.free.jchecs.core.Piece.WHITE_BISHOP; import static fr.free.jchecs.core.Piece.WHITE_KING; import static fr.free.jchecs.core.Piece.WHITE_KNIGHT; import static fr.free.jchecs.core.Piece.WHITE_PAWN; import static fr.free.jchecs.core.Piece.WHITE_QUEEN; import static fr.free.jchecs.core.Piece.WHITE_ROOK; import java.util.Random; import org.junit.Test; /** * Tests unitaires des classes générant des états de la partie. * * @author David Cotton */ public final class MoveGeneratorTest { /** * Pour que JUnit puisse instancier les tests. */ public MoveGeneratorTest() { // Rien de spécifique... } /** * Vérifie si deux listes de mouvements sont identiques. * * @param pNom Nom de la classe testée. * @param pListe1 Première liste de mouvements. * @param pListe2 Deuxième liste de mouvements. */ private static void sameMoves(final String pNom, final Move [] pListe1, final Move [] pListe2) { assert pNom != null; assert pListe1 != null; assert pListe2 != null; if (pListe1.length != pListe2.length) { for (final Move m : pListe1) { System.out.println("1:" + m); } for (final Move m : pListe2) { System.out.println("2:" + m); } } assertTrue(pNom + " (" + pListe1.length + "!=" + pListe2.length + ')', pListe1.length == pListe2.length); for (final Move src : pListe1) { boolean trouve = false; for (final Move dst : pListe2) { if (dst.equals(src)) { trouve = true; break; } } assertTrue(pNom, trouve); } } /** * Vérifie si deux listes de cases sont identiques (même ensemble de cases). * * @param pNom Nom de la classe testée. * @param pListe1 Première liste de cases. * @param pListe2 Deuxième liste de cases. */ private static void sameSquares(final String pNom, final Square [] pListe1, final Square [] pListe2) { assert pNom != null; assert pListe1 != null; assert pListe2 != null; if (pListe1.length != pListe2.length) { for (final Square s : pListe1) { System.out.println("1:" + s); } for (final Square s : pListe2) { System.out.println("2:" + s); } } assertTrue(pNom + " (" + pListe1.length + "!=" + pListe2.length + ')', pListe1.length == pListe2.length); for (final Square src : pListe1) { boolean trouve = false; for (final Square dst : pListe2) { if (dst == src) { trouve = true; break; } } if (!trouve) { for (final Square s : pListe1) { System.out.println("1:" + s); } for (final Square s : pListe2) { System.out.println("2:" + s); } } assertTrue(pNom, trouve); } } /** * Teste la méthode de dérivation de plateau. */ @Test public void testDerive() { final Piece [] dispositionFinale = { WHITE_ROOK, WHITE_KNIGHT, WHITE_BISHOP, null, WHITE_KING, null, WHITE_KNIGHT, WHITE_ROOK, WHITE_PAWN, WHITE_PAWN, WHITE_PAWN, WHITE_PAWN, null, WHITE_PAWN, WHITE_PAWN, WHITE_PAWN, null, null, null, null, WHITE_PAWN, null, null, null, null, null, WHITE_BISHOP, null, null, null, null, null, null, null, null, null, null, null, null, null, null, BLACK_PAWN, null, null, null, null, null, BLACK_KNIGHT, BLACK_PAWN, BLACK_BISHOP, BLACK_PAWN, BLACK_PAWN, BLACK_PAWN, WHITE_QUEEN, BLACK_PAWN, BLACK_PAWN, BLACK_ROOK, BLACK_KNIGHT, null, BLACK_QUEEN, BLACK_KING, BLACK_BISHOP, null, BLACK_ROOK, }; for (final BoardFactory.Type t : BoardFactory.Type.values()) { MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); final String nomClasse = etat.getClass().getSimpleName(); etat = etat.derive(new Move(WHITE_PAWN, Square.valueOf("e2"), Square.valueOf("e3")), true); etat = etat.derive(new Move(BLACK_PAWN, Square.valueOf("b7"), Square.valueOf("b6")), true); etat = etat.derive(new Move(WHITE_BISHOP, Square.valueOf("f1"), Square.valueOf("c4")), true); etat = etat.derive(new Move(BLACK_KNIGHT, Square.valueOf("g8"), Square.valueOf("h6")), true); etat = etat.derive(new Move(WHITE_QUEEN, Square.valueOf("d1"), Square.valueOf("f3")), true); etat = etat.derive(new Move(BLACK_BISHOP, Square.valueOf("c8"), Square.valueOf("b7")), true); assertTrue(nomClasse, etat.isWhiteActive()); assertTrue(nomClasse, etat.getHalfmoveCount() == 4); etat = etat.derive( new Move(WHITE_QUEEN, Square.valueOf("f3"), Square.valueOf("f7"), BLACK_PAWN), true); assertFalse(nomClasse, etat.isWhiteActive()); assertTrue(nomClasse, etat.canCastleLong(true)); assertTrue(nomClasse, etat.canCastleLong(false)); assertTrue(nomClasse, etat.canCastleShort(true)); assertTrue(nomClasse, etat.canCastleShort(false)); assertTrue(nomClasse, etat.getFullmoveNumber() == 4); assertTrue(nomClasse, etat.getHalfmoveCount() == 0); for (final Square s : Square.values()) { assertSame(nomClasse, etat.getPieceAt(s), dispositionFinale[s.getIndex()]); } } } /** * Teste l'équivalence des méthodes de recherche des cases cibles d'une position. */ @Test public void testGetAllTargets() { MoveGenerator etatPrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (etatPrec != null) { final String nomClasse = etat.getClass().getSimpleName(); for (final Square s : Square.values()) { sameSquares(nomClasse, etatPrec.getAllTargets(s), etat.getAllTargets(s)); } } etatPrec = etat; } } /** * Teste l'équivalence des méthodes de recherche des cases cibles de fou. */ @Test public void testGetBishopTargets() { MoveGenerator ePrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (ePrec != null) { final String nomClasse = etat.getClass().getSimpleName(); for (final Square s : Square.values()) { sameSquares(nomClasse, ePrec.getBishopTargets(s, true), etat.getBishopTargets(s, true)); sameSquares(nomClasse, ePrec.getBishopTargets(s, false), etat.getBishopTargets(s, false)); } } ePrec = etat; } } /** * Teste l'équivalence des méthodes de recherche de la case d'un roi. */ @Test public void testGetKingSquare() { MoveGenerator etatPrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (etatPrec != null) { final String nomClasse = etat.getClass().getSimpleName(); assertSame(nomClasse, etatPrec.getKingSquare(true), etat.getKingSquare(true)); assertSame(nomClasse, etatPrec.getKingSquare(false), etat.getKingSquare(false)); } etatPrec = etat; } } /** * Teste l'équivalence des méthodes de recherche des cases cibles de roi. */ @Test public void testGetKingTargets() { MoveGenerator etatPrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (etatPrec != null) { final String nomClasse = etat.getClass().getSimpleName(); for (final Square s : Square.values()) { sameSquares(nomClasse, etatPrec.getKingTargets(s, true), etat.getKingTargets(s, true)); sameSquares(nomClasse, etatPrec.getKingTargets(s, false), etat.getKingTargets(s, false)); } } etatPrec = etat; } } /** * Teste l'équivalence des méthodes de recherche des cases cibles de cavalier. */ @Test public void testGetKnightTargets() { MoveGenerator ePrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (ePrec != null) { final String nomClasse = etat.getClass().getSimpleName(); for (final Square s : Square.values()) { sameSquares(nomClasse, ePrec.getKnightTargets(s, true), etat.getKnightTargets(s, true)); sameSquares(nomClasse, ePrec.getKnightTargets(s, false), etat.getKnightTargets(s, false)); } } ePrec = etat; } } /** * Teste l'équivalence des méthodes de recherche des cases cibles de pion. */ @Test public void testGetPawnTargets() { MoveGenerator etatPrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (etatPrec != null) { final String nomClasse = etat.getClass().getSimpleName(); for (final Square s : Square.values()) { sameSquares(nomClasse, etatPrec.getPawnTargets(s, true), etat.getPawnTargets(s, true)); sameSquares(nomClasse, etatPrec.getPawnTargets(s, false), etat.getPawnTargets(s, false)); } } etatPrec = etat; } } /** * Teste l'équivalence des méthodes de recherche des cases cibles de dame. */ @Test public void testGetQueenTargets() { MoveGenerator ePrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (ePrec != null) { final String nomClasse = etat.getClass().getSimpleName(); for (final Square s : Square.values()) { sameSquares(nomClasse, ePrec.getQueenTargets(s, true), etat.getQueenTargets(s, true)); sameSquares(nomClasse, ePrec.getQueenTargets(s, false), etat.getQueenTargets(s, false)); } } ePrec = etat; } } /** * Teste l'équivalence des méthodes de recherche des cases cibles de tour. */ @Test public void testGetRookTargets() { MoveGenerator etatPrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (etatPrec != null) { final String nomClasse = etat.getClass().getSimpleName(); for (final Square s : Square.values()) { sameSquares(nomClasse, etatPrec.getRookTargets(s, true), etat.getRookTargets(s, true)); sameSquares(nomClasse, etatPrec.getRookTargets(s, false), etat.getRookTargets(s, false)); } } etatPrec = etat; } } /** * Teste l'équivalence des méthodes de recherche des cases mouvements valides. */ @Test public void testGetValidMoves() { final Move [] attendu = { new Move(WHITE_PAWN, Square.valueOf("a2"), Square.valueOf("a3")), new Move(WHITE_PAWN, Square.valueOf("a2"), Square.valueOf("a4")), new Move(WHITE_PAWN, Square.valueOf("b2"), Square.valueOf("b3")), new Move(WHITE_PAWN, Square.valueOf("b2"), Square.valueOf("b4")), new Move(WHITE_PAWN, Square.valueOf("c2"), Square.valueOf("c3")), new Move(WHITE_PAWN, Square.valueOf("c2"), Square.valueOf("c4")), new Move(WHITE_PAWN, Square.valueOf("d2"), Square.valueOf("d3")), new Move(WHITE_PAWN, Square.valueOf("d2"), Square.valueOf("d4")), new Move(WHITE_PAWN, Square.valueOf("e2"), Square.valueOf("e3")), new Move(WHITE_PAWN, Square.valueOf("e2"), Square.valueOf("e4")), new Move(WHITE_PAWN, Square.valueOf("f2"), Square.valueOf("f3")), new Move(WHITE_PAWN, Square.valueOf("f2"), Square.valueOf("f4")), new Move(WHITE_PAWN, Square.valueOf("g2"), Square.valueOf("g3")), new Move(WHITE_PAWN, Square.valueOf("g2"), Square.valueOf("g4")), new Move(WHITE_PAWN, Square.valueOf("h2"), Square.valueOf("h3")), new Move(WHITE_PAWN, Square.valueOf("h2"), Square.valueOf("h4")), new Move(WHITE_KNIGHT, Square.valueOf("b1"), Square.valueOf("a3")), new Move(WHITE_KNIGHT, Square.valueOf("b1"), Square.valueOf("c3")), new Move(WHITE_KNIGHT, Square.valueOf("g1"), Square.valueOf("f3")), new Move(WHITE_KNIGHT, Square.valueOf("g1"), Square.valueOf("h3")), }; MoveGenerator etatPrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (etatPrec != null) { final String nomClasse = etat.getClass().getSimpleName(); sameMoves(nomClasse, etatPrec.getValidMoves(true), etat.getValidMoves(true)); sameMoves(nomClasse, etatPrec.getValidMoves(false), etat.getValidMoves(false)); sameMoves(nomClasse, etatPrec.getValidMoves(true), attendu); sameMoves(nomClasse, etat.getValidMoves(true), attendu); } etatPrec = etat; } } /** * Teste l'équivalence des méthodes de recherche des cases cibles valides à partir d'une position. */ @Test public void testGetValidTargets() { MoveGenerator etatPrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (etatPrec != null) { final String nomClasse = etat.getClass().getSimpleName(); for (final Square s : Square.values()) { sameSquares(nomClasse, etatPrec.getValidTargets(s), etat.getValidTargets(s)); } assertTrue(nomClasse, etatPrec.getValidTargets(Square.valueOf("a1")).length == 0); assertTrue(nomClasse, etatPrec.getValidTargets(Square.valueOf("b1")).length == 2); assertTrue(nomClasse, etatPrec.getValidTargets(Square.valueOf("b2")).length == 2); } etatPrec = etat; } } /** * Teste l'équivalence des méthodes de détection d'une attaque sur une case. */ @Test public void testIsAttacked() { MoveGenerator etatPrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (etatPrec != null) { final String nomClasse = etat.getClass().getSimpleName(); for (final Square s : Square.values()) { assertTrue(nomClasse, etatPrec.isAttacked(s, true) == etat.isAttacked(s, true)); assertTrue(nomClasse, etatPrec.isAttacked(s, false) == etat.isAttacked(s, false)); } } etatPrec = etat; } } /** * Teste l'équivalence des méthodes de recherche indiqunt un roque effectué. */ @Test public void testIsCastled() { MoveGenerator etatPrec = null; for (final BoardFactory.Type t : BoardFactory.Type.values()) { final MoveGenerator etat = BoardFactory.valueOf(t, BoardFactory.State.STARTING); if (etatPrec != null) { final String nomClasse = etat.getClass().getSimpleName(); assertTrue(nomClasse, etatPrec.isCastled(true) == etat.isCastled(true)); assertTrue(nomClasse, etatPrec.isCastled(false) == etat.isCastled(false)); } etatPrec = etat; } } /** * Teste l'équivalence des résultats lors du déroulement des parties. */ @Test public void testPlaying() { final Random randomizer = new Random(1000); final int eLength = BoardFactory.Type.values().length; final MoveGenerator [] etats = new MoveGenerator [ eLength ]; final Move [][] mvts = new Move [ eLength ] []; for (int p = 100; p >= 0; p--) { for (final BoardFactory.Type t : BoardFactory.Type.values()) { etats[t.ordinal()] = BoardFactory.valueOf(t, BoardFactory.State.STARTING); } for (int cps = 100; cps >= 0; cps--) { for (int i = 0; i < eLength; i++) { mvts[i] = etats[i].getValidMoves(etats[i].isWhiteActive()); if (i > 0) { sameMoves(etats[i].getClass().getSimpleName(), mvts[i - 1], mvts[i]); } } final int mLength = mvts[0].length; if (mLength == 0) { break; } final Move mvt = mvts[0][randomizer.nextInt(mLength)]; for (int i = 0; i < eLength; i++) { etats[i] = etats[i].derive(mvt, true); if (etats[i].isCastled(true)) { assertFalse(etats[i].getClass().getSimpleName(), etats[i].canCastleLong(true)); assertFalse(etats[i].getClass().getSimpleName(), etats[i].canCastleShort(true)); } if (etats[i].isCastled(false)) { assertFalse(etats[i].getClass().getSimpleName(), etats[i].canCastleLong(false)); assertFalse(etats[i].getClass().getSimpleName(), etats[i].canCastleShort(false)); } if (i > 0) { assertEquals(etats[i].getClass().getSimpleName(), etats[i - 1], etats[i]); assertTrue(etats[i].getClass().getSimpleName(), etats[i - 1].isCastled(true) == etats[i].isCastled(true)); assertTrue(etats[i].getClass().getSimpleName(), etats[i - 1].isCastled(false) == etats[i].isCastled(false)); } } } } } /** * Teste la résistance des représentations face au multithread. */ @Test public void testThreadSafety() { for (final BoardFactory.Type t : BoardFactory.Type.values()) { if (t == FASTEST) { continue; } final PlayingThread [] process = new PlayingThread [ 20 ]; for (int i = 0; i < 50; i++) { for (int j = 0; j < process.length; j++) { process[j] = new PlayingThread(t); process[j].start(); } for (final PlayingThread p : process) { try { p.join(); if (!p.isOk()) { fail(p.toString() + " failed"); } } catch (final InterruptedException e) { fail(e.toString()); } } } } } /** * Thread exécutant une partie. */ private static final class PlayingThread extends Thread { /** Type de la représentation à utiliser. */ private final BoardFactory.Type _type; /** Indicateur de bon fonctionnement. */ private boolean _ok; /** * Instancie un nouveau processus de jeu. * * @param pType Type de représentation de l'état. */ PlayingThread(final BoardFactory.Type pType) { assert pType != null; _type = pType; _ok = false; } /** * Renvoi l'état de l'indicateur de bon fonctionnement. * * @return Vrai si le thread s'est bien déroulé. */ boolean isOk() { return _ok; } /** * Joue une partie. */ @Override public void run() { final Random randomizer = new Random(1000); MoveGenerator etat = BoardFactory.valueOf(_type, BoardFactory.State.STARTING); for (int cps = 50; cps >= 0; cps--) { final Move [] mvts = etat.getValidMoves(etat.isWhiteActive()); final int nbMvts = mvts.length; if (nbMvts == 0) { break; } etat = etat.derive(mvts[randomizer.nextInt(nbMvts)], true); } _ok = true; } } }