/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Ă–sterlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
import java.util.ArrayList;
/**
*
* @author petero
*/
public class MoveGen {
static MoveGen instance;
static {
instance = new MoveGen();
}
/**
* Generate and return a list of pseudo-legal moves.
* Pseudo-legal means that the moves doesn't necessarily defend from check threats.
*/
public final ArrayList<Move> pseudoLegalMoves(Position pos) {
ArrayList<Move> moveList = getMoveListObj();
final boolean wtm = pos.whiteMove;
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
int sq = Position.getSquare(x, y);
int p = pos.getPiece(sq);
if ((p == Piece.EMPTY) || (Piece.isWhite(p) != wtm)) {
continue;
}
if ((p == Piece.WROOK) || (p == Piece.BROOK) || (p == Piece.WQUEEN) || (p == Piece.BQUEEN)) {
if (addDirection(moveList, pos, sq, 7-x, 1)) return moveList;
if (addDirection(moveList, pos, sq, 7-y, 8)) return moveList;
if (addDirection(moveList, pos, sq, x, -1)) return moveList;
if (addDirection(moveList, pos, sq, y, -8)) return moveList;
}
if ((p == Piece.WBISHOP) || (p == Piece.BBISHOP) || (p == Piece.WQUEEN) || (p == Piece.BQUEEN)) {
if (addDirection(moveList, pos, sq, Math.min(7-x, 7-y), 9)) return moveList;
if (addDirection(moveList, pos, sq, Math.min( x, 7-y), 7)) return moveList;
if (addDirection(moveList, pos, sq, Math.min( x, y), -9)) return moveList;
if (addDirection(moveList, pos, sq, Math.min(7-x, y), -7)) return moveList;
}
if ((p == Piece.WKNIGHT) || (p == Piece.BKNIGHT)) {
if (x < 6 && y < 7 && addDirection(moveList, pos, sq, 1, 10)) return moveList;
if (x < 7 && y < 6 && addDirection(moveList, pos, sq, 1, 17)) return moveList;
if (x > 0 && y < 6 && addDirection(moveList, pos, sq, 1, 15)) return moveList;
if (x > 1 && y < 7 && addDirection(moveList, pos, sq, 1, 6)) return moveList;
if (x > 1 && y > 0 && addDirection(moveList, pos, sq, 1, -10)) return moveList;
if (x > 0 && y > 1 && addDirection(moveList, pos, sq, 1, -17)) return moveList;
if (x < 7 && y > 1 && addDirection(moveList, pos, sq, 1, -15)) return moveList;
if (x < 6 && y > 0 && addDirection(moveList, pos, sq, 1, -6)) return moveList;
}
if ((p == Piece.WKING) || (p == Piece.BKING)) {
if (x < 7 && addDirection(moveList, pos, sq, 1, 1)) return moveList;
if (x < 7 && y < 7 && addDirection(moveList, pos, sq, 1, 9)) return moveList;
if ( y < 7 && addDirection(moveList, pos, sq, 1, 8)) return moveList;
if (x > 0 && y < 7 && addDirection(moveList, pos, sq, 1, 7)) return moveList;
if (x > 0 && addDirection(moveList, pos, sq, 1, -1)) return moveList;
if (x > 0 && y > 0 && addDirection(moveList, pos, sq, 1, -9)) return moveList;
if ( y > 0 && addDirection(moveList, pos, sq, 1, -8)) return moveList;
if (x < 7 && y > 0 && addDirection(moveList, pos, sq, 1, -7)) return moveList;
int k0 = wtm ? Position.getSquare(4,0) : Position.getSquare(4,7);
if (Position.getSquare(x,y) == k0) {
int aCastle = wtm ? Position.A1_CASTLE : Position.A8_CASTLE;
int hCastle = wtm ? Position.H1_CASTLE : Position.H8_CASTLE;
int rook = wtm ? Piece.WROOK : Piece.BROOK;
if (((pos.getCastleMask() & (1 << hCastle)) != 0) &&
(pos.getPiece(k0 + 1) == Piece.EMPTY) &&
(pos.getPiece(k0 + 2) == Piece.EMPTY) &&
(pos.getPiece(k0 + 3) == rook) &&
!sqAttacked(pos, k0) &&
!sqAttacked(pos, k0 + 1)) {
moveList.add(getMoveObj(k0, k0 + 2, Piece.EMPTY));
}
if (((pos.getCastleMask() & (1 << aCastle)) != 0) &&
(pos.getPiece(k0 - 1) == Piece.EMPTY) &&
(pos.getPiece(k0 - 2) == Piece.EMPTY) &&
(pos.getPiece(k0 - 3) == Piece.EMPTY) &&
(pos.getPiece(k0 - 4) == rook) &&
!sqAttacked(pos, k0) &&
!sqAttacked(pos, k0 - 1)) {
moveList.add(getMoveObj(k0, k0 - 2, Piece.EMPTY));
}
}
}
if ((p == Piece.WPAWN) || (p == Piece.BPAWN)) {
int yDir = wtm ? 8 : -8;
if (pos.getPiece(sq + yDir) == Piece.EMPTY) { // non-capture
addPawnMoves(moveList, sq, sq + yDir);
if ((y == (wtm ? 1 : 6)) &&
(pos.getPiece(sq + 2 * yDir) == Piece.EMPTY)) { // double step
addPawnMoves(moveList, sq, sq + yDir * 2);
}
}
if (x > 0) { // Capture to the left
int toSq = sq + yDir - 1;
int cap = pos.getPiece(toSq);
if (cap != Piece.EMPTY) {
if (Piece.isWhite(cap) != wtm) {
if (cap == (wtm ? Piece.BKING : Piece.WKING)) {
returnMoveList(moveList);
moveList = getMoveListObj();
moveList.add(getMoveObj(sq, toSq, Piece.EMPTY));
return moveList;
} else {
addPawnMoves(moveList, sq, toSq);
}
}
} else if (toSq == pos.getEpSquare()) {
addPawnMoves(moveList, sq, toSq);
}
}
if (x < 7) { // Capture to the right
int toSq = sq + yDir + 1;
int cap = pos.getPiece(toSq);
if (cap != Piece.EMPTY) {
if (Piece.isWhite(cap) != wtm) {
if (cap == (wtm ? Piece.BKING : Piece.WKING)) {
returnMoveList(moveList);
moveList = getMoveListObj();
moveList.add(getMoveObj(sq, toSq, Piece.EMPTY));
return moveList;
} else {
addPawnMoves(moveList, sq, toSq);
}
}
} else if (toSq == pos.getEpSquare()) {
addPawnMoves(moveList, sq, toSq);
}
}
}
}
}
return moveList;
}
/**
* Return true if the side to move is in check.
*/
public static final boolean inCheck(Position pos) {
int kingSq = pos.getKingSq(pos.whiteMove);
if (kingSq < 0)
return false;
return sqAttacked(pos, kingSq);
}
/**
* Return true if a square is attacked by the opposite side.
*/
public static final boolean sqAttacked(Position pos, int sq) {
int x = Position.getX(sq);
int y = Position.getY(sq);
boolean isWhiteMove = pos.whiteMove;
final int oQueen= isWhiteMove ? Piece.BQUEEN: Piece.WQUEEN;
final int oRook = isWhiteMove ? Piece.BROOK : Piece.WROOK;
final int oBish = isWhiteMove ? Piece.BBISHOP : Piece.WBISHOP;
final int oKnight = isWhiteMove ? Piece.BKNIGHT : Piece.WKNIGHT;
int p;
if (y > 0) {
p = checkDirection(pos, sq, y, -8); if ((p == oQueen) || (p == oRook)) return true;
p = checkDirection(pos, sq, Math.min( x, y), -9); if ((p == oQueen) || (p == oBish)) return true;
p = checkDirection(pos, sq, Math.min(7-x, y), -7); if ((p == oQueen) || (p == oBish)) return true;
if (x > 1 ) { p = checkDirection(pos, sq, 1, -10); if (p == oKnight) return true; }
if (x > 0 && y > 1) { p = checkDirection(pos, sq, 1, -17); if (p == oKnight) return true; }
if (x < 7 && y > 1) { p = checkDirection(pos, sq, 1, -15); if (p == oKnight) return true; }
if (x < 6 ) { p = checkDirection(pos, sq, 1, -6); if (p == oKnight) return true; }
if (!isWhiteMove) {
if (x < 7 && y > 1) { p = checkDirection(pos, sq, 1, -7); if (p == Piece.WPAWN) return true; }
if (x > 0 && y > 1) { p = checkDirection(pos, sq, 1, -9); if (p == Piece.WPAWN) return true; }
}
}
if (y < 7) {
p = checkDirection(pos, sq, 7-y, 8); if ((p == oQueen) || (p == oRook)) return true;
p = checkDirection(pos, sq, Math.min(7-x, 7-y), 9); if ((p == oQueen) || (p == oBish)) return true;
p = checkDirection(pos, sq, Math.min( x, 7-y), 7); if ((p == oQueen) || (p == oBish)) return true;
if (x < 6 ) { p = checkDirection(pos, sq, 1, 10); if (p == oKnight) return true; }
if (x < 7 && y < 6) { p = checkDirection(pos, sq, 1, 17); if (p == oKnight) return true; }
if (x > 0 && y < 6) { p = checkDirection(pos, sq, 1, 15); if (p == oKnight) return true; }
if (x > 1 ) { p = checkDirection(pos, sq, 1, 6); if (p == oKnight) return true; }
if (isWhiteMove) {
if (x < 7 && y < 6) { p = checkDirection(pos, sq, 1, 9); if (p == Piece.BPAWN) return true; }
if (x > 0 && y < 6) { p = checkDirection(pos, sq, 1, 7); if (p == Piece.BPAWN) return true; }
}
}
p = checkDirection(pos, sq, 7-x, 1); if ((p == oQueen) || (p == oRook)) return true;
p = checkDirection(pos, sq, x, -1); if ((p == oQueen) || (p == oRook)) return true;
int oKingSq = pos.getKingSq(!isWhiteMove);
if (oKingSq >= 0) {
int ox = Position.getX(oKingSq);
int oy = Position.getY(oKingSq);
if ((Math.abs(x - ox) <= 1) && (Math.abs(y - oy) <= 1))
return true;
}
return false;
}
/**
* Remove all illegal moves from moveList.
* "moveList" is assumed to be a list of pseudo-legal moves.
* This function removes the moves that don't defend from check threats.
*/
public static final ArrayList<Move> removeIllegal(Position pos, ArrayList<Move> moveList) {
ArrayList<Move> ret = new ArrayList<Move>();
UndoInfo ui = new UndoInfo();
int mlSize = moveList.size();
for (int mi = 0; mi < mlSize; mi++) {
Move m = moveList.get(mi);
pos.makeMove(m, ui);
pos.setWhiteMove(!pos.whiteMove);
if (!inCheck(pos))
ret.add(m);
pos.setWhiteMove(!pos.whiteMove);
pos.unMakeMove(m, ui);
}
return ret;
}
/**
* Add all moves from square sq0 in direction delta.
* @param maxSteps Max steps until reaching a border. Set to 1 for non-sliding pieces.
* @ return True if the enemy king could be captured, false otherwise.
*/
private final boolean addDirection(ArrayList<Move> moveList, Position pos, int sq0, int maxSteps, int delta) {
int sq = sq0;
boolean wtm = pos.whiteMove;
final int oKing = (wtm ? Piece.BKING : Piece.WKING);
while (maxSteps > 0) {
sq += delta;
int p = pos.getPiece(sq);
if (p == Piece.EMPTY) {
moveList.add(getMoveObj(sq0, sq, Piece.EMPTY));
} else {
if (Piece.isWhite(p) != wtm) {
if (p == oKing) {
returnMoveList(moveList);
moveList = getMoveListObj(); // Ugly! this only works because we get back the same object
moveList.add(getMoveObj(sq0, sq, Piece.EMPTY));
return true;
} else {
moveList.add(getMoveObj(sq0, sq, Piece.EMPTY));
}
}
break;
}
maxSteps--;
}
return false;
}
/**
* Generate all possible pawn moves from (x0,y0) to (x1,y1), taking pawn promotions into account.
*/
private final void addPawnMoves(ArrayList<Move> moveList, int sq0, int sq1) {
if (sq1 >= 56) { // White promotion
moveList.add(getMoveObj(sq0, sq1, Piece.WQUEEN));
moveList.add(getMoveObj(sq0, sq1, Piece.WKNIGHT));
moveList.add(getMoveObj(sq0, sq1, Piece.WROOK));
moveList.add(getMoveObj(sq0, sq1, Piece.WBISHOP));
} else if (sq1 < 8) { // Black promotion
moveList.add(getMoveObj(sq0, sq1, Piece.BQUEEN));
moveList.add(getMoveObj(sq0, sq1, Piece.BKNIGHT));
moveList.add(getMoveObj(sq0, sq1, Piece.BROOK));
moveList.add(getMoveObj(sq0, sq1, Piece.BBISHOP));
} else { // No promotion
moveList.add(getMoveObj(sq0, sq1, Piece.EMPTY));
}
}
/**
* Check if there is an attacking piece in a given direction starting from sq.
* The direction is given by delta.
* @param maxSteps Max steps until reaching a border. Set to 1 for non-sliding pieces.
* @return The first piece in the given direction, or EMPTY if there is no piece
* in that direction.
*/
private static final int checkDirection(Position pos, int sq, int maxSteps, int delta) {
while (maxSteps > 0) {
sq += delta;
int p = pos.getPiece(sq);
if (p != Piece.EMPTY)
return p;
maxSteps--;
}
return Piece.EMPTY;
}
// Code to handle the Move cache.
private Move[] moveCache = new Move[2048];
private int movesInCache = 0;
private Object[] moveListCache = new Object[200];
private int moveListsInCache = 0;
private final Move getMoveObj(int from, int to, int promoteTo) {
if (movesInCache > 0) {
Move m = moveCache[--movesInCache];
m.from = from;
m.to = to;
m.promoteTo = promoteTo;
return m;
}
return new Move(from, to, promoteTo);
}
@SuppressWarnings("unchecked")
private final ArrayList<Move> getMoveListObj() {
if (moveListsInCache > 0) {
return (ArrayList<Move>)moveListCache[--moveListsInCache];
}
return new ArrayList<Move>(60);
}
/** Return all move objects in moveList to the move cache. */
public final void returnMoveList(ArrayList<Move> moveList) {
if (movesInCache + moveList.size() <= moveCache.length) {
int mlSize = moveList.size();
for (int mi = 0; mi < mlSize; mi++) {
moveCache[movesInCache++] = moveList.get(mi);
}
}
moveList.clear();
if (moveListsInCache < moveListCache.length) {
moveListCache[moveListsInCache++] = moveList;
}
}
public final void returnMove(Move m) {
if (movesInCache < moveCache.length) {
moveCache[movesInCache++] = m;
}
}
}