/*
$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.ai.AbstractEngine.MATE_VALUE;
import static fr.free.jchecs.core.Constants.FILE_COUNT;
import static fr.free.jchecs.core.Constants.RANK_COUNT;
import static fr.free.jchecs.core.PieceType.KING;
import static fr.free.jchecs.core.PieceType.PAWN;
import fr.free.jchecs.core.MoveGenerator;
import fr.free.jchecs.core.Piece;
import fr.free.jchecs.core.PieceType;
import fr.free.jchecs.core.Square;
/**
* Fonction d'évaluation basée sur le matériel, la position des pièces présentes sur le plateau et
* leur mobilité.
* <p>
* Classe sûre vis-à-vis des threads.
* </p>
*
* @author David Cotton
*/
final class MobilityHeuristic implements Heuristic
{
/** Nombre de pièces à partir duquel on considère être en fin de partie. */
private static final int END_GAME = 8;
/** Nombre de pièces à partir duquel on considère être en milieu de partie. */
private static final int MIDDLE_GAME = 16;
/** Identifiant de la classe pour la sérialisation. */
private static final long serialVersionUID = 8752973612245818678L;
/**
* Bonus/Malus d'un fou (blanc par défaut) en fonction de sa position.
*/
private static final int [] BISHOP_POSITIONS = { -5, -5, -5, -5, -5, -5, -5, -5, // a1 ... h1
-5, 10, 5, 10, 10, 5, 10, -5, // a2 ... h2
-5, 5, 3, 12, 12, 3, 5, -5, // a3 ... h3
-5, 3, 12, 3, 3, 12, 3, -5, // a4 ... h4
-5, 3, 12, 3, 3, 12, 3, -5, // a5 ... h5
-5, 5, 3, 12, 12, 3, 5, -5, // a6 ... h6
-5, 10, 5, 10, 10, 5, 10, -5, // a7 ... h7
-5, -5, -5, -5, -5, -5, -5, -5, // a8 ... h8
};
static
{
assert BISHOP_POSITIONS.length == 64;
}
/**
* Bonus/Malus d'un roi (blanc par défaut) en fonction de sa position.
*/
private static final int [] KING_POSITIONS = { 2, 3, 5, -5, 0, -4, 6, 4, // a1 ... h1
-3, -3, -5, -5, -5, -5, -3, -3, // a2 ... h2
-5, -5, -8, -8, -8, -8, -5, -5, // a3 ... h3
-8, -8, -13, -13, -13, -13, -8, -8, // a4 ... h4
-13, -13, -21, -21, -21, -21, -13, -13, // a5 ... h5
-21, -21, -34, -34, -34, -34, -21, -21, // a6 ... h6
-34, -34, -55, -55, -55, -55, -34, -34, // a7 ... h7
-55, -55, -89, -89, -89, -89, -55, -55, // a8 ... h8
};
static
{
assert KING_POSITIONS.length == 64;
}
/**
* Bonus/Malus d'un roi (blanc par défaut) en fonction de sa position, en fin de partie.
*/
private static final int [] KING_END_POSITIONS = { -5, -3, -1, 0, 0, -1, -3, -5, // a1 ... h1
-3, 5, 5, 5, 5, 5, 5, -3, // a2 ... h2
-1, 5, 10, 10, 10, 10, 5, -1, // a3 ... h3
0, 5, 10, 15, 15, 10, 5, 0, // a4 ... h4
0, 5, 10, 15, 15, 10, 5, 0, // a5 ... h5
-1, 5, 10, 10, 10, 10, 5, -1, // a6 ... h6
-3, 5, 5, 5, 5, 5, 5, -3, // a7 ... h7
-5, -3, -1, 0, 0, -1, -3, -5, // a8 ... h8
};
static
{
assert KING_END_POSITIONS.length == 64;
}
/**
* Bonus/Malus d'un cavalier (blanc par défaut) en fonction de sa position.
*/
private static final int [] KNIGHT_POSITIONS = { -10, -5, -3, -1, -1, -3, -5, -10, // a1 ... h1
-5, 0, 0, 3, 3, 0, 0, -5, // a2 ... h2
-3, 0, 5, 5, 5, 5, 0, -3, // a3 ... h3
-1, 1, 5, 10, 10, 5, 1, -1, // a4 ... h4
-1, 1, 7, 12, 12, 7, 1, -1, // a5 ... h5
-3, 0, 5, 7, 7, 5, 0, -3, // a6 ... h6
-5, 0, 0, 3, 3, 0, 0, -5, // a7 ... h7
-10, -5, -3, -1, -1, -3, -5, -10, // a8 ... h8
};
static
{
assert KNIGHT_POSITIONS.length == 64;
}
/**
* Bonus/Malus d'un pion (blanc par défaut) en fonction de sa position.
*/
private static final int [] PAWN_POSITIONS = { 0, 0, 0, 0, 0, 0, 0, 0, // a1 ... h1
0, 0, 0, -5, -5, 0, 0, 0, // a2 ... h2
1, 2, 4, 4, 4, 3, 2, 1, // a3 ... h3
2, 4, 7, 8, 8, 6, 4, 2, // a4 ... h4
3, 6, 11, 12, 12, 9, 6, 3, // a5 ... h5
4, 8, 12, 16, 16, 12, 8, 4, // a6 ... h6
5, 10, 15, 20, 20, 15, 10, 5, // a7 ... h7
100, 100, 100, 100, 100, 100, 100, 100, // a8 ... h9
};
static
{
assert PAWN_POSITIONS.length == 64;
}
/**
* Bonus/Malus d'une reine (blanche par défaut) en fonction de sa position.
*/
private static final int [] QUEEN_POSITIONS = { -5, -5, -5, 0, 0, -5, -5, -5, // a1 ... h1
0, 0, 3, 3, 3, 0, 0, 0, // a2 ... h2
0, 3, 3, 3, 3, 0, 0, 0, // a3 ... h3
0, 0, 0, 5, 5, 0, 0, 0, // a4 ... h4
0, 0, 0, 5, 5, 0, 0, 0, // a5 ... h5
-5, -5, 0, 0, 0, 0, 0, 0, // a6 ... h6
-5, -5, 0, 0, 0, 0, 0, 0, // a7 ... h7
-5, -5, 0, 0, 0, 0, 0, 0, // a8 ... h8
};
static
{
assert QUEEN_POSITIONS.length == 64;
}
/**
* Bonus/Malus d'une tour (blanche par défaut) en fonction de sa position.
*/
private static final int [] ROOK_POSITIONS = { 0, 0, 0, 5, 5, 0, 0, 0, // a1 ... h1
-2, 0, 0, 0, 0, 0, 0, -2, // a2 ... h2
-2, 0, 0, 0, 0, 0, 0, -2, // a3 ... h3
-2, 0, 0, 0, 0, 0, 0, -2, // a4 ... h4
-2, 0, 0, 0, 0, 0, 0, -2, // a5 ... h5
-2, 0, 0, 0, 0, 0, 0, -2, // a6 ... h6
10, 10, 10, 10, 10, 10, 10, 10, // a7 ... h7
0, 0, 0, 0, 0, 0, 0, 0, // a8 ... h8
};
static
{
assert ROOK_POSITIONS.length == 64;
}
/**
* Crée une nouvelle instance.
*/
MobilityHeuristic()
{
// Rien de spécifique...
}
/**
* Renvoi la valeur estimée d'un état du jeu, pour les fonctions de recherche du meilleur coup.
*
* @param pEtat Etat du jeu.
* @param pTrait Positionné à "true" si l'on veut une évaluation du point de vue des blancs.
* @return Valeur estimée.
* @see Heuristic#evaluate(MoveGenerator,boolean)
*/
public int evaluate(final MoveGenerator pEtat, final boolean pTrait)
{
assert pEtat != null;
final int [] pionsTrait = new int [ FILE_COUNT ];
final int [] pionsAdversaire = new int [ FILE_COUNT ];
int res = -pEtat.getHalfmoveCount() * 2;
int nbPieces = 0;
for (final Square s : Square.values())
{
final Piece piece = pEtat.getPieceAt(s);
if (piece != null)
{
nbPieces++;
if (piece.getType() == PAWN)
{
final int idx = s.getFile();
if (piece.isWhite() == pTrait)
{
pionsTrait[idx]++;
if (pionsTrait[idx] > 1)
{
res -= 5;
}
}
else
{
pionsAdversaire[idx]++;
if (pionsAdversaire[idx] > 1)
{
res += 5;
}
}
}
}
}
for (final Square s : Square.values())
{
final Piece piece = pEtat.getPieceAt(s);
if (piece != null)
{
final boolean traitPiece = piece.isWhite();
final PieceType typePiece = piece.getType();
final int mat = typePiece.getValue();
final int pos;
final int mob;
switch (typePiece)
{
case BISHOP :
if (traitPiece)
{
pos = BISHOP_POSITIONS[s.getIndex()];
}
else
{
pos = BISHOP_POSITIONS[((RANK_COUNT - 1) - s.getRank()) * FILE_COUNT + s.getFile()];
}
if (nbPieces >= MIDDLE_GAME)
{
mob = pEtat.getBishopTargets(s, traitPiece).length * 4;
}
else
{
mob = 0;
}
break;
case KING :
if ((pEtat.getFullmoveNumber() > 5) && pEtat.isInCheck(traitPiece))
{
pos = 0;
if (pEtat.getValidMoves(traitPiece).length == 0)
{
// Mat : inutile d'aller plus loin...
if (traitPiece == pTrait)
{
return MATE_VALUE;
}
return -MATE_VALUE;
}
// Malus pour un échec...
mob = -250;
}
else
{
final int idx;
if (traitPiece)
{
idx = s.getIndex();
}
else
{
idx = ((RANK_COUNT - 1) - s.getRank()) * FILE_COUNT + s.getFile();
}
if (nbPieces >= END_GAME)
{
pos = KING_POSITIONS[idx];
}
else
{
pos = KING_END_POSITIONS[idx];
}
if ((pEtat.getFullmoveNumber() <= 32) && pEtat.isCastled(traitPiece))
{
// Pour favoriser le roque en début de partie...
mob = 25;
}
else
{
mob = 0;
}
}
break;
case KNIGHT :
if (traitPiece)
{
pos = KNIGHT_POSITIONS[s.getIndex()];
}
else
{
pos = KNIGHT_POSITIONS[((RANK_COUNT - 1) - s.getRank()) * FILE_COUNT + s.getFile()];
}
if (nbPieces >= MIDDLE_GAME)
{
mob = pEtat.getKnightTargets(s, traitPiece).length * 4;
}
else
{
mob = 0;
}
break;
case PAWN :
if (traitPiece)
{
pos = PAWN_POSITIONS[s.getIndex()];
}
else
{
pos = PAWN_POSITIONS[((RANK_COUNT - 1) - s.getRank()) * FILE_COUNT + s.getFile()];
}
if (nbPieces > END_GAME)
{
mob = 0;
}
else
{
mob = pos;
}
break;
case QUEEN :
int posReine = 0;
if (traitPiece)
{
posReine = QUEEN_POSITIONS[s.getIndex()];
}
else
{
posReine =
QUEEN_POSITIONS[((RANK_COUNT - 1) - s.getRank()) * FILE_COUNT + s.getFile()];
}
if (pEtat.getFullmoveNumber() < 12)
{
// Essayer d'éviter de déplacer la reine trop tôt...
if ((traitPiece && (s.getIndex() != 3)) || ((!traitPiece) && (s.getIndex() != 59)))
{
posReine -= 30;
}
}
pos = posReine;
if ((nbPieces >= END_GAME) && (nbPieces <= MIDDLE_GAME))
{
mob = pEtat.getQueenTargets(s, traitPiece).length;
}
else
{
mob = 0;
}
break;
case ROOK :
if (traitPiece)
{
pos = ROOK_POSITIONS[s.getIndex()];
}
else
{
pos = ROOK_POSITIONS[((RANK_COUNT - 1) - s.getRank()) * FILE_COUNT + s.getFile()];
}
if (nbPieces >= END_GAME)
{
final int nbPions;
if (traitPiece == pTrait)
{
nbPions = pionsTrait[s.getFile()];
}
else
{
nbPions = pionsAdversaire[s.getFile()];
}
if (nbPions == 0)
{
mob = 10;
}
else
{
mob = 0;
}
}
else
{
mob = pEtat.getRookTargets(s, traitPiece).length * 2;
}
break;
default :
assert false;
pos = 0;
mob = 0;
}
int att = 0;
if (typePiece != KING)
{
if (pEtat.isAttacked(s, traitPiece))
{
att += mat / 20;
}
if (pEtat.isAttacked(s, !traitPiece))
{
att -= mat / 10;
}
}
final int score = mat + pos + mob + att;
if (traitPiece == pTrait)
{
res += score;
}
else
{
res -= score;
}
}
}
return res;
}
}