/*
$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.ai;
import static fr.free.jchecs.core.BoardFactory.State.EMPTY;
import static fr.free.jchecs.core.BoardFactory.State.STARTING;
import static fr.free.jchecs.core.BoardFactory.Type.FASTEST;
import static fr.free.jchecs.core.Constants.APPLICATION_NAME;
import static fr.free.jchecs.core.Constants.APPLICATION_VERSION;
import static fr.free.jchecs.core.FENUtils.toBoard;
import static fr.free.jchecs.core.PieceType.PAWN;
import static fr.free.jchecs.core.SANUtils.toMove;
import static fr.free.jchecs.core.SANUtils.toSAN;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Logger;
import fr.free.jchecs.core.Board;
import fr.free.jchecs.core.BoardFactory;
import fr.free.jchecs.core.FENException;
import fr.free.jchecs.core.Game;
import fr.free.jchecs.core.Move;
import fr.free.jchecs.core.MoveGenerator;
import fr.free.jchecs.core.SANException;
import fr.free.jchecs.core.Square;
import fr.free.jchecs.core.Game.State;
/**
* Classe utilitaire assurant l'interface entre l'I.A. et une I.H.M. utilisant le protocole
* XBoard/WinBoard.
*
* @author David Cotton
*/
public final class XBoardAdapter
{
/** Chaine identifiant l'application. */
private static final String APPLICATION_STRING = APPLICATION_NAME + " " + APPLICATION_VERSION;
/** Chaine listant les capacités du moteur. */
private static final String FEATURES_STRING =
"feature analyze=0 colors=0 myname=\"" + APPLICATION_STRING
+ "\" pause=0 ping=1 playother=0 san=1 setboard=1 sigint=0 sigterm=0 "
+ "time=0 usermove=1 variants=\"normal\" done=1";
/** Log de la classe. */
private static final Logger LOGGER = Logger.getLogger(XBoardAdapter.class.getName());
/** Moteur d'I.A. utilisé. */
private static final Engine ENGINE = EngineFactory.newInstance();
/** Etat de la partie. */
private static Game S_game = new Game();
/** Etat du mode "force". */
private static boolean S_forceMode;
/** Etat du mode de jeu faible/fort. */
private static boolean S_hardMode;
/** Indicateur de position illegale. */
private static boolean S_illegalPosition;
/**
* Classe utilitaire : ne pas instancier.
*/
private XBoardAdapter()
{
// Rien de particulier...
}
/**
* Lance l'interface.
*
* @param pArgs Arguments de la ligne de commande : ignorés.
*/
public static void main(final String [] pArgs)
{
assert pArgs != null;
final BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true)
{
String commande = null;
try
{
commande = in.readLine();
}
catch (final IOException e)
{
LOGGER.severe(e.toString());
}
if (commande == null)
{
LOGGER.severe("Communication error.");
System.exit(-1);
}
else
{
parseCommand(commande.trim());
}
}
}
/**
* Interprète les commandes reçues.
*
* @param pCommande Commande reçue.
*/
private static void parseCommand(final String pCommande)
{
assert pCommande != null;
if (pCommande.startsWith("accepted "))
{
// Tant mieux, mais il n'y a rien à faire.
}
else if (pCommande.startsWith("level "))
{
// Non impléméntée...
}
else if (pCommande.startsWith("ping "))
{
System.out.println("pong" + pCommande.substring(4));
}
else if (pCommande.startsWith("protover "))
{
System.out.println(FEATURES_STRING);
}
else if (pCommande.startsWith("rejected "))
{
System.out.println("tellusererror Missing feature " + pCommande.substring(9));
}
else if (pCommande.startsWith("result "))
{
// Non impléméntée...
}
else if (pCommande.startsWith("setboard "))
{
final String fen = pCommande.substring(9);
Board etat = null;
try
{
etat = toBoard(fen);
}
catch (final FENException e)
{
// Géré par la suite...
}
if (etat == null)
{
S_illegalPosition = true;
System.out.println("tellusererror Illegal position");
}
else
{
S_illegalPosition = false;
S_game.resetTo(BoardFactory.valueOf(FASTEST, EMPTY).derive(etat));
}
}
else if (pCommande.startsWith("usermove "))
{
final String xbSAN = pCommande.substring(9);
// Filtre les '=' que certains programmes peuvent en cas de promotion de pion...
// ... et transforme les "o" majuscules utilisés par XBoard pour les roques en zéros.
final String san = xbSAN.replace("=", "").replace('O', '0');
Move mvt = null;
try
{
mvt = toMove(S_game.getBoard(), san);
}
catch (final SANException e)
{
// géré par la suite...
}
if ((mvt == null) || S_illegalPosition)
{
System.out.println("Illegal move: " + xbSAN);
}
else
{
S_game.moveFromCurrent(mvt);
if (!S_forceMode)
{
think();
}
}
}
else if (pCommande.equals("computer"))
{
// Non impléméntée...
}
else if (pCommande.equals("easy"))
{
S_hardMode = false;
}
else if (pCommande.equals("force"))
{
S_forceMode = true;
}
else if (pCommande.equals("go"))
{
if (S_illegalPosition)
{
System.out.println("Error (illegal position): go");
}
else
{
S_forceMode = false;
think();
}
}
else if (pCommande.equals("hard"))
{
S_hardMode = true;
}
else if (pCommande.equals("new"))
{
S_game.resetTo(BoardFactory.valueOf(FASTEST, STARTING));
S_forceMode = false;
S_illegalPosition = false;
}
else if (pCommande.equals("nopost"))
{
// Non impléméntée...
}
else if (pCommande.equals("post"))
{
// Non impléméntée...
}
else if (pCommande.equals("quit"))
{
System.exit(0);
}
else if (pCommande.equals("random"))
{
// Rien à faire.
}
else if (pCommande.equals("xboard"))
{
System.out.println(APPLICATION_STRING + " started in xboard mode.");
}
else
{
System.out.println("Error (unknown command): " + pCommande);
}
}
/**
* Teste la fin de partie.
*
* @param pAbandon Positionné à vrai si l'on souhaite tester l'abandon.
* @return Vrai si la partie n'est pas terminée.
*/
private static boolean testResult(final boolean pAbandon)
{
String rep = null;
final State etat = S_game.getState();
switch (etat)
{
case BLACK_MATES :
rep = "0-1 {Black mates}";
break;
case DRAWN_BY_50_MOVE_RULE :
if (pAbandon)
{
// Ne chercher l'abandon que si l'on n'a pas un avantage suffisant (un pion)...
final MoveGenerator dispo = S_game.getBoard();
if (ENGINE.getHeuristic().evaluate(dispo, dispo.isWhiteActive()) < PAWN.getValue())
{
rep = "1/2-1/2 {Drawn by 50 moves rule}";
}
}
break;
case DRAWN_BY_TRIPLE_REPETITION :
if (pAbandon)
{
// Ne chercher l'abandon que si l'on est en difficulté (unpion de retard)...
final MoveGenerator dispo = S_game.getBoard();
if (ENGINE.getHeuristic().evaluate(dispo, dispo.isWhiteActive()) < -PAWN.getValue())
{
rep = "1/2-1/2 {Drawn by triple repetition}";
}
}
break;
case IN_PROGRESS :
break;
case STALEMATE :
rep = "1/2-1/2 {Stalemate}";
break;
case WHITE_MATES :
rep = "1-0 {White mates}";
break;
default :
assert false;
}
if (rep != null)
{
System.out.println(rep);
}
return rep == null;
}
/**
* Recherche et renvoi le meilleur coup à partir de la position en cours.
*/
private static void think()
{
if (testResult(true))
{
final MoveGenerator etat = BoardFactory.valueOf(FASTEST, EMPTY).derive(S_game.getBoard());
int profondeur = 5;
if (S_hardMode)
{
int nbPieces = 0;
for (final Square s : Square.values())
{
if (etat.getPieceAt(s) != null)
{
nbPieces++;
}
}
if (nbPieces <= 6)
{
profondeur++;
}
}
ENGINE.setSearchDepthLimit(profondeur);
final Move mvt = ENGINE.getMoveFor(etat);
final String san = toSAN(etat, mvt);
S_game.moveFromCurrent(mvt);
// Transforme les zéros en "o" majuscules utilisés par XBoard pour les roques...
// ... et supprime les marques de prise "en passant" inconnues de XBoard.
final String xbSAN = san.replace(" e.p.", "").replace("O", "0");
System.out.println("move " + xbSAN);
testResult(false);
}
}
}