/**
* Calculon - A Java chess-engine.
*
* Copyright (C) 2008-2009 Barry Smith
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package nl.zoidberg.calculon.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import nl.zoidberg.calculon.analyzer.GameScorer;
import nl.zoidberg.calculon.engine.BitBoard.BitBoardMove;
public class ChessEngine {
private static final int DEPTH = 3;
private static final int Q_DEPTH = 4;
// private static final Logger log = Logger.getLogger(ChessEngine.class.getName());
private GameScorer gameScorer;
private int searchDepth = DEPTH;
private int scoreMargin = 1000;
private int maxDeepMoves = 5;
private boolean quiesce = false;
public ChessEngine() {
this.gameScorer = GameScorer.getDefaultScorer();
}
public ChessEngine(GameScorer gameScorer) {
this.gameScorer = gameScorer;
}
public void setQuiesce(boolean quiesce) {
// this.quiesce = quiesce;
}
public String getPreferredMove(BitBoard bitBoard) {
// String bookMove = OpeningBook.getDefaultBook() == null ? null : OpeningBook.getDefaultBook().getBookMove(board);
// if(bookMove != null) {
// log.fine("Using book move: " + bookMove);
// return PGNUtils.toPgnMoveMap(board).get(bookMove);
// }
List<ScoredMove> allMoves = getScoredMoves(bitBoard);
// System.out.println(allMoves);
// ChessEngine.selectBestMoves(allMoves, scoreMargin, maxDeepMoves);
// searchDepth++;
// for(ScoredMove move: allMoves) {
// List<ScoredMove> nextDepth = getScoredMoves(board.clone().applyMove(move.getMove()));
// System.out.println(nextDepth);
// }
String rv = ChessEngine.selectBestMove(allMoves);
return rv;
}
public List<ScoredMove> getScoredMoves(BitBoard bitBoard) {
List<ScoredMove> rv = new ArrayList<ScoredMove>();
// BitBoardMove bbm = bitBoard.getMove("G1G2");
// bitBoard.makeMove(bbm);
// int score = alphaBetaMax(Integer.MIN_VALUE, Integer.MAX_VALUE, searchDepth, bitBoard);
// rv.add(new ScoredMove("G1G2", score));
// bitBoard.unmakeMove();
for(Iterator<BitBoardMove> moveItr = new MoveGenerator(bitBoard); moveItr.hasNext(); ) {
BitBoardMove move = moveItr.next();
bitBoard.makeMove(move);
int score = alphaBetaMax(Integer.MIN_VALUE, Integer.MAX_VALUE, searchDepth, bitBoard);
bitBoard.unmakeMove();
rv.add(new ScoredMove(move.getAlgebraic(), score));
}
return rv;
}
public int getScoreMargin() {
return scoreMargin;
}
public void setScoreMargin(int scoreMargin) {
this.scoreMargin = scoreMargin;
}
public int getMaxDeepMoves() {
return maxDeepMoves;
}
public void setMaxDeepMoves(int maxDeepMoves) {
this.maxDeepMoves = maxDeepMoves;
}
public final int getDepth() {
return searchDepth;
}
public final void setDepth(int depth) {
this.searchDepth = depth;
}
private int alphaBetaMaxQ(int alpha, int beta, int depth, BitBoard bitBoard) {
List<BitBoardMove> qmoves = new MoveGenerator(bitBoard).getThreateningMoves();
if(depth >= Q_DEPTH || qmoves.size() == 0) {
return gameScorer.score(bitBoard, false, alpha, beta);
}
for(BitBoardMove move: qmoves) {
bitBoard.makeMove(move);
int score = alphaBetaMinQ(alpha, beta, depth + 1, bitBoard);
bitBoard.unmakeMove();
if(score >= beta) {
return beta;
}
alpha = Math.max(alpha, score);
}
return alpha;
}
private int alphaBetaMinQ(int alpha, int beta, int depth, BitBoard bitBoard) {
List<BitBoardMove> qmoves = new MoveGenerator(bitBoard).getThreateningMoves();
if(depth >= Q_DEPTH || qmoves.size() == 0) {
return -gameScorer.score(bitBoard, false, alpha, beta);
}
for(BitBoardMove move: qmoves) {
bitBoard.makeMove(move);
int score = alphaBetaMaxQ(alpha, beta, depth + 1, bitBoard);
bitBoard.unmakeMove();
if(score <= alpha) {
return alpha;
}
beta = Math.min(beta, score);
}
return beta;
}
private int alphaBetaMax(int alpha, int beta, int depthLeft, BitBoard bitBoard) {
Iterator<BitBoardMove> moveItr = new MoveGenerator(bitBoard);
if(depthLeft == 0 || ! moveItr.hasNext()) {
int rv;
if(quiesce) {
if(! moveItr.hasNext()) {
rv = gameScorer.score(bitBoard, false, alpha, beta);
} else {
rv = alphaBetaMinQ(alpha, beta, 0, bitBoard);
}
} else {
rv = gameScorer.score(bitBoard, false, alpha, beta);
}
if(rv == GameScorer.MATE_SCORE) {
rv *= (depthLeft+1);
}
return rv;
}
while(moveItr.hasNext()) {
BitBoardMove move = moveItr.next();
bitBoard.makeMove(move);
int score = alphaBetaMin(alpha, beta, depthLeft - 1, bitBoard);
bitBoard.unmakeMove();
if(score >= beta) {
return beta;
}
alpha = Math.max(alpha, score);
}
return alpha;
}
private int alphaBetaMin(int alpha, int beta, int depthLeft, BitBoard bitBoard) {
Iterator<BitBoardMove> moveItr = new MoveGenerator(bitBoard);
if(depthLeft == 0 || ! moveItr.hasNext()) {
int rv;
if(quiesce) {
if(! moveItr.hasNext()) {
rv = gameScorer.score(bitBoard, false, alpha, beta);
} else {
rv = alphaBetaMaxQ(alpha, beta, 0, bitBoard);
}
} else {
rv = gameScorer.score(bitBoard, false, alpha, beta);
}
if(rv == GameScorer.MATE_SCORE) {
rv *= (depthLeft+1);
}
return -rv;
}
while(moveItr.hasNext()) {
BitBoardMove move = moveItr.next();
bitBoard.makeMove(move);
int score = alphaBetaMax(alpha, beta, depthLeft - 1, bitBoard);
bitBoard.unmakeMove();
if(score <= alpha) {
return alpha;
}
beta = Math.min(beta, score);
}
return beta;
}
private static List<ScoredMove> selectBestMoves(List<ScoredMove> allMoves) {
return selectBestMoves(allMoves, 0, 5);
}
/**
* Selects all moves sharing the lowest (i.e. best) score.
*
* @param allMoves
* @return
*/
private static List<ScoredMove> selectBestMoves(List<ScoredMove> allMoves, int margin, int maxMoves) {
if(allMoves.size() == 0) {
return allMoves;
}
Collections.sort(allMoves);
int bestScore = allMoves.get(0).getScore();
while(allMoves.size() > maxMoves || allMoves.get(allMoves.size()-1).getScore() > bestScore+margin) {
allMoves.remove(allMoves.size()-1);
}
return allMoves;
}
private static String selectBestMove(List<ScoredMove> allMoves) {
List<ScoredMove> bestMoves = selectBestMoves(allMoves);
if(bestMoves.size() == 0) {
return null;
}
return allMoves.get((int) (Math.random() * allMoves.size())).getMove();
}
private static class ScoredMove implements Comparable<ScoredMove> {
private String move;
private int score;
public ScoredMove(String move, int score) {
super();
this.move = move;
this.score = score;
}
public String getMove() {
return move;
}
public void setMove(String move) {
this.move = move;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return move + "=" + score;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((move == null) ? 0 : move.hashCode());
result = prime * result + score;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ScoredMove other = (ScoredMove) obj;
if (move == null) {
if (other.move != null)
return false;
} else if (!move.equals(other.move))
return false;
if (score != other.score)
return false;
return true;
}
public int compareTo(ScoredMove o) {
return new Integer(score).compareTo(o.getScore());
}
}
}