package org.myrobotlab.chess;
//
// Search.java
// ChessApp
//
// This search code is heavily based on Tom Kerrigan's tscp for which he
// owns the copyright - (c) 1997 Tom Kerrigan - and is used with his permission.
// All rights are reserved by the owners of the respective copyrights.
// Java version created by Peter Hunter on Sat Jan 05 2002.
// Copyright (c) 2002 Peter Hunter. All rights reserved.
//
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
final public class Search {
final static int MAX_PLY = 32;
public Board board = new Board();
private HMove pv[][] = new HMove[MAX_PLY][MAX_PLY];
private int pvLength[] = new int[MAX_PLY];
private boolean followPV;
private int ply = 0;
private int nodes = 0;
private long stopTime = Long.MAX_VALUE;
private boolean stop = false;
void checkup() throws StopSearchingException {
/*
* is the engine's time up? if so, longjmp back to the beginning of think()
*/
if (System.currentTimeMillis() >= stopTime || stop) {
throw new StopSearchingException();
}
}
public void clearPV() {
for (int i = 0; i < MAX_PLY; i++)
for (int j = 0; j < MAX_PLY; j++)
pv[i][j] = null;
}
/*
* quiesce() is a recursive minimax search function with alpha-beta cutoffs.
* In other words, negamax. It basically only searches capture sequences and
* allows the evaluation function to cut the search off (and set alpha). The
* idea is to find a position where there isn't a lot going on so the static
* evaluation function will work.
*/
public HMove getBest() {
return pv[0][0];
}
/*
* sortPV() is called when the search function is following the PV (Principal
* Variation). It looks through the current ply's move list to see if the PV
* move is there. If so, it adds 10,000,000 to the move's score so it's played
* first by the search function. If not, followPV remains FALSE and search()
* stops calling sortPV().
*/
public HMove getBestNext() {
return pv[0][1];
}
/* checkup() is called once in a while during the search. */
public boolean isStopped() {
return stop;
}
int quiesce(int alpha, int beta) throws StopSearchingException {
pvLength[ply] = ply;
/* are we too deep? */
if (ply >= MAX_PLY - 1)
return board.eval();
/*
* if (hply >= HIST_STACK - 1) return board.eval(); FIXME!! see above
*/
/* check with the evaluation function */
int x = board.eval();
if (x >= beta)
return beta;
if (x > alpha)
alpha = x;
List<HMove> validCaptures = board.genCaps();
if (followPV) /* are we following the PV? */
sortPV(validCaptures);
Collections.sort(validCaptures);
/* loop through the moves */
Iterator<HMove> i = validCaptures.iterator();
while (i.hasNext()) {
HMove m = (HMove) i.next();
if (!board.makeMove(m))
continue;
++ply;
++nodes;
/* do some housekeeping every 1024 nodes */
if ((nodes & 1023) == 0)
checkup();
x = -quiesce(-beta, -alpha);
board.takeBack();
--ply;
if (x > alpha) {
if (x >= beta)
return beta;
alpha = x;
/* update the PV */
pv[ply][ply] = m;
for (int j = ply + 1; j < pvLength[ply + 1]; ++j)
pv[ply][j] = pv[ply + 1][j];
pvLength[ply] = pvLength[ply + 1];
}
}
return alpha;
}
public void restartThinking() {
stop = false;
}
/** search() does just that, in negascout fashion */
int search(int alpha, int beta, int depth) throws StopSearchingException {
/*
* we're as deep as we want to be; call quiesce() to get a reasonable score
* and return it.
*/
if (depth == 0)
return quiesce(alpha, beta);
if (beta - alpha != 1) {
pvLength[ply] = ply;
}
/*
* if this isn't the root of the search tree (where we have to pick a move
* and can't simply return 0) then check to see if the position is a repeat.
* if so, we can assume that this line is a draw and return 0.
*/
if ((ply > 0) && (board.reps() > 0))
return 0;
/* are we too deep? */
if (ply >= MAX_PLY - 1)
return board.eval();
/*
* if (hply >= HIST_STACK - 1) return board.eval(); FIXME!!! We could in
* principle overflow the move history stack.
*/
/* are we in check? if so, we want to search deeper */
boolean check = board.inCheck(board.side);
if (check)
++depth;
List<HMove> validMoves = board.gen();
if (followPV) /* are we following the PV? */
sortPV(validMoves);
Collections.sort(validMoves);
/* loop through the moves */
boolean foundMove = false;
Iterator<HMove> i = validMoves.iterator();
int a = alpha;
int b = beta;
boolean first = true;
while (i.hasNext()) {
HMove m = (HMove) i.next();
if (!board.makeMove(m))
continue;
++ply;
++nodes;
/* do some housekeeping every 1024 nodes */
if ((nodes & 1023) == 0)
checkup();
foundMove = true;
int x = -search(-b, -a, depth - 1);
boolean betterMove = false;
if ((x > a) && (x < beta) && (!first)) {
a = -search(-beta, -x, depth - 1);
if (a >= x)
betterMove = true;
}
board.takeBack();
--ply;
if (x > a) {
a = x;
betterMove = true;
}
if (betterMove) {
/*
* this move caused a cutoff, so increase the history value so it gets
* ordered high next time we can search it
*/
board.history[m.getFrom()][m.getTo()] += depth;
if (x >= beta)
return beta;
/* update the PV */
pv[ply][ply] = m;
for (int j = ply + 1; j < pvLength[ply + 1]; ++j)
pv[ply][j] = pv[ply + 1][j];
pvLength[ply] = pvLength[ply + 1];
}
b = a + 1;
first = false;
}
/* no legal moves? then we're in checkmate or stalemate */
if (!foundMove) {
if (check)
return -10000 + ply;
else
return 0;
}
/* fifty move draw rule */
if (board.fifty >= 100)
return 0;
return a;
}
public void setStopTime(long stop) {
stopTime = stop;
}
public void shiftPV() {
pvLength[0] -= 2;
for (int i = 0; i < pvLength[0]; i++)
pv[0][i] = pv[0][i + 2];
}
void sortPV(Collection<HMove> moves) {
followPV = false;
if (pv[0][ply] == null)
return;
Iterator<HMove> i = moves.iterator();
while (i.hasNext()) {
HMove m = (HMove) i.next();
if (m.equals(pv[0][ply])) {
followPV = true;
m.score += 10000000;
return;
}
}
}
public void stopThinking() {
stop = true;
}
public void think() {
think(null);
}
void think(ChessApp app) {
stop = false;
try {
ply = 0;
nodes = 0;
for (int i = 0; i < 64; i++)
for (int j = 0; j < 64; j++)
board.history[i][j] = 0;
for (int i = 3; i <= MAX_PLY; ++i) {
followPV = true;
int x = search(-10000, 10000, i);
System.out.println(i + " " + nodes + " " + x);
StringBuffer sb = new StringBuffer("[");
sb.append(x);
sb.append("]");
for (int j = 0; j < pvLength[0]; ++j) {
sb.append(" ");
sb.append(pv[0][j].toString());
}
// app.setPrincipalVariation(sb.toString());
// log.info(sb.toString());
if (x > 9000 || x < -9000)
break;
}
} catch (StopSearchingException e) {
/* make sure to take back the line we were searching */
while (ply != 0) {
board.takeBack();
--ply;
}
}
System.out.println("Nodes searched: " + nodes);
return;
}
}