Skip to content
This repository has been archived by the owner on Jan 7, 2022. It is now read-only.

Commit

Permalink
PV Search & null move heuristic, killer move heuristic, history heuri…
Browse files Browse the repository at this point in the history
…stic
  • Loading branch information
Heiaha committed Oct 6, 2020
1 parent 2112ad2 commit aff46ad
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 46 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ https://lichess.org/@/Weiawaga
- Move generation
- [Magic bitboard hashing](https://www.chessprogramming.org/Magic_Bitboards)
- Search
- [Negamax / alpha-beta pruning](https://www.chessprogramming.org/Alpha-Beta)
- [Principal variation search](https://en.wikipedia.org/wiki/Principal_variation_search)
- [Iterative deepening](https://en.wikipedia.org/wiki/Iterative_deepening_depth-first_search)
- [Quiescence search](https://en.wikipedia.org/wiki/Quiescence_search)
- [Null move pruning](https://www.chessprogramming.org/Null_Move_Pruning)
- [Check extensions](https://www.chessprogramming.org/Check_Extensions)
- Evaluation
- [Material imbalance](https://www.chessprogramming.org/Material)
Expand All @@ -26,5 +27,7 @@ https://lichess.org/@/Weiawaga
- Move ordering
- [Hash move](https://www.chessprogramming.org/Hash_Move)
- [MVV/LVA](https://www.chessprogramming.org/MVV-LVA)
- [Killer Move Heuristic](https://www.chessprogramming.org/Killer_Heuristic)
- [History Heuristic](https://www.chessprogramming.org/History_Heuristic)
- Other
- [Zobrist hashing](https://www.chessprogramming.org/Zobrist_Hashing) / [Transposition table](https://en.wikipedia.org/wiki/Transposition_table)
9 changes: 5 additions & 4 deletions src/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
public class Main {
private final static String ENGINENAME = "Weiawaga";
private final static String AUTHOR = "Mal";

private final static String UCI = "uci";
private final static String GOTHINK = "go";
private final static String MOVEFROMSTART = "position startpos moves ";
Expand All @@ -20,13 +19,13 @@ public class Main {
private final static String POSITIONFEN = "position fen";



public static void main(String[] args) throws IOException {

FileWriter fr = new FileWriter("./uciCommands.txt", true);
BufferedWriter bw = new BufferedWriter(fr);
Scanner input_stream = new Scanner(System.in);
Board board = new Board();

while (true){
String input = input_stream.nextLine();
bw.write(input + "\n");
Expand Down Expand Up @@ -90,10 +89,12 @@ else if (input.startsWith(GOTHINK)){
Move best_move = Search.IDMove;
int best_value = Search.IDScore;


System.out.println("info score cp " + best_value);
System.out.println("bestmove " + best_move.uci());

TranspTable.reset();
MoveOrder.clearKillers();
MoveOrder.clearHistory();
System.gc();
Limits.resetTime();
}
Expand Down Expand Up @@ -132,7 +133,7 @@ private static long printPerft(Board board, int depth) {

private static long perft(Board board, int depth) {
MoveList moves = board.generateLegalMoves();
if (depth == 1) {
if (depth == 0) {
return moves.size();
}
long nodes = 0;
Expand Down
58 changes: 56 additions & 2 deletions src/mind/MoveOrder.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

public class MoveOrder {

private static final int[][][] killerMoves = new int[2][1000][3];
private static final int[][][] historyMoves = new int[2][64][64];
private static final int[][] MvvLvaScores = new int[6][6];

static {
final int[] VictimScore = {100, 200, 300, 400, 500, 600};
for (int attacker = PieceType.PAWN; attacker <= PieceType.KING; attacker++) {
Expand All @@ -16,12 +19,58 @@ public class MoveOrder {
}
}

public static void addKiller(Board board, Move move, int ply){
int side = board.getSideToPlay();
for (int i = killerMoves[side][ply].length - 2; i >= 0; i--)
killerMoves[side][ply][i+1] = killerMoves[side][ply][i];
killerMoves[side][ply][0] = move.move();
}

public static boolean isKiller(Board board, Move move, int ply){
int moveInt = move.move();
int side = board.getSideToPlay();
for (int i = 0; i < killerMoves[side][ply].length; i++){
if (moveInt == killerMoves[side][ply][i])
return true;
}
return false;
}

public static void clearKillers(){
for (int color = Side.WHITE; color <= Side.BLACK; color++){
for (int ply = 0; ply < killerMoves[0].length; ply++){
for (int killer_i = 0; killer_i < killerMoves[0][0].length; killer_i++){
killerMoves[color][ply][killer_i] = 0;
}
}
}
}

public static void addHistory(Board board, Move move, int depth){
historyMoves[board.getSideToPlay()][move.from()][move.to()] += depth*depth;
}

public static int getHistoryValue(Board board, Move move){
return historyMoves[board.getSideToPlay()][move.from()][move.to()];
}

public static void clearHistory(){
for (int color = Side.WHITE; color <= Side.BLACK; color++){
for (int sq1 = Square.A1; sq1 <= Square.H8; sq1++){
for (int sq2 = Square.A1; sq2 <= Square.H8; sq2++){
historyMoves[color][sq1][sq2] = 0;
}
}
}
}

public static int getMvvLvaScore(Board board, Move move){
return MvvLvaScores[board.pieceTypeAt(move.to())][board.pieceTypeAt(move.from())];
}

public static MoveList moveOrdering(final Board board, final MoveList moves){
public static MoveList moveOrdering(final Board board, final MoveList moves, int ply){
MoveList sortedMoves = new MoveList();
MoveList killers = new MoveList();
MoveList promotions = new MoveList();
MoveList captures = new MoveList();
MoveList quiet = new MoveList();
Expand All @@ -36,6 +85,9 @@ public static MoveList moveOrdering(final Board board, final MoveList moves){
if (move.equals(pvMove)) {
sortedMoves.add(move);
}
else if(isKiller(board, move, ply)){
killers.add(move);
}
else {
switch (move.flags()) {
case Move.PC_BISHOP, Move.PC_KNIGHT, Move.PC_ROOK, Move.PC_QUEEN, Move.PR_BISHOP, Move.PR_KNIGHT, Move.PR_ROOK, Move.PR_QUEEN -> promotions.add(move);
Expand All @@ -46,9 +98,11 @@ public static MoveList moveOrdering(final Board board, final MoveList moves){
}

Comparator<Move> compareByMvvLva = (Move move1, Move move2) -> Integer.compare(getMvvLvaScore(board, move2), getMvvLvaScore(board, move1));
Comparator<Move> compareByHistory = (Move move1, Move move2) -> Integer.compare(getHistoryValue(board, move2), getHistoryValue(board, move1));
captures.sort(compareByMvvLva);

quiet.sort(compareByHistory);
sortedMoves.addAll(captures);
sortedMoves.addAll(killers);
sortedMoves.addAll(promotions);
sortedMoves.addAll(quiet);
return sortedMoves;
Expand Down
103 changes: 79 additions & 24 deletions src/mind/Search.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
import movegen.*;

public class Search {
final public static int MAX_SEARCH_DEPTH = 12;
final public static int MAX_SEARCH_DEPTH = 25;
final public static int INF = 99999;
public static boolean stop;
public static Move IDMove = null;
public static int IDScore = -INF;

private final static int NullMinDepth = 5;
private final static int LMRMinDepth = 2;
private final static int LMRMoveWOReduction = 3;
private final static int LMRPVReduction = 1;
private final static int LMRNonPVDiv = 3;

public Search(){}

public static void itDeep(Board board){
Expand Down Expand Up @@ -42,12 +48,12 @@ public static void negaMaxRoot(Board board, int depth){
return;
}

moves = MoveOrder.moveOrdering(board, moves);
moves = MoveOrder.moveOrdering(board, moves, 0);
int value;
Move bestMove = null;
for (Move move : moves){
board.push(move);
value = -negamax(board, depth - 1, -beta, -alpha);
value = -negamax(board, depth - 1, 1, -beta, -alpha, true);
board.pop();
if (stop || Limits.checkLimits()) {
stop = true;
Expand All @@ -68,18 +74,35 @@ public static void negaMaxRoot(Board board, int depth){
}
}

public static int negamax(Board board, int depth, int alpha, int beta){
public static int negamax(Board board, int depth, int ply, int alpha, int beta, boolean pV){

if (stop || Limits.checkLimits()) {
stop = true;
return 0;
}

// check if King is attacked. If so, we may be in checkmate. If we're not in checkmate, extend the
// search depth by 1. We use inCheck to see if we're in checkmate or stalemate.

Statistics.nodes++;
if (board.isThreefoldOrFiftyMove()) {
// look for checkmate. If only in check, this will simply generate the moves.
MoveList moves = board.generateLegalMoves();
boolean inCheck = board.checkers() != 0;
if (moves.size() == 0 && inCheck) {
Statistics.leafs++;
return -INF + ply;
}

if (board.isDraw(moves)) {
Statistics.leafs++;
return 0;
}

if (depth + (inCheck ? 1 : 0) <= 0) {
Statistics.leafs++;
return qSearch(board, depth, ply, alpha, beta);
}

// check to see if the board state has already been encountered
int alphaOrig = alpha;
final TTEntry ttEntry = TranspTable.get(board.hash());
Expand All @@ -101,37 +124,55 @@ public static int negamax(Board board, int depth, int alpha, int beta){
}
}

// check if King is attacked. If so, we may be in checkmate. If we're not in checkmate, extend the
// search depth by 1. We use checkExtension to see if we're in checkmate or stalemate.
int checkExtension = board.kingAttacked() ? 1 : 0;
if (depth + checkExtension <= 0) {
Statistics.leafs++;
return qSearch(board, alpha, beta, depth);
}
// Do a null move check if allowed
if (canApplyNullWindow(board, depth, pV)){
int r = depth > 6 ? 3 : 2;
board.pushNull();
int value = -negamax(board, depth - r - 1, ply + 1, -beta, -beta + 1, false);

// look for checkmate. If only in check, this will simply generate the moves.
MoveList moves = board.generateLegalMoves();
if (moves.size() == 0) {
Statistics.leafs++;
return checkExtension == 1 ? -INF - depth : 0;
board.popNull();
if (value >= beta){
Statistics.betaCutoffs++;
return beta;
}
}

int value;
int moveIndex = 0;
Move bestMove = null;
moves = MoveOrder.moveOrdering(board, moves);
moves = MoveOrder.moveOrdering(board, moves, ply);
boolean pVS = true;
for (Move move : moves){

board.push(move);
value = -negamax(board, depth - 1 + checkExtension, -beta, -alpha);
if (pVS) {
value = -negamax(board, depth - 1, ply + 1, -beta, -alpha, true);
pVS = false;
}
else{
int reducedDepth = depth;
if (canApplyLMR(board, depth, move, moveIndex, inCheck))
reducedDepth = LMRReducedDepth(depth, pV);
value = -negamax(board, reducedDepth - 1, ply + 1, -alpha - 1, -alpha, false);
if (value > alpha)
value = -negamax(board, depth - 1, ply + 1, -beta, -alpha, false);
}
board.pop();

if (value >= beta){
TranspTable.set(board.hash(), value, depth, TTEntry.LOWER_BOUND, move);
if (move.flags() == Move.QUIET) {
MoveOrder.addKiller(board, move, ply);
MoveOrder.addHistory(board, move, depth);
}
Statistics.betaCutoffs++;
return beta;
}
if (value > alpha) {
bestMove = move;
alpha = value;
}
moveIndex++;
}

if (bestMove != null){
Expand All @@ -145,7 +186,7 @@ public static int negamax(Board board, int depth, int alpha, int beta){
return alpha;
}

public static int qSearch(Board board, int alpha, int beta, int depth){
public static int qSearch(Board board, int depth, int ply, int alpha, int beta){
Statistics.qnodes++;
if (stop || Limits.checkLimits()){
stop = true;
Expand All @@ -154,7 +195,7 @@ public static int qSearch(Board board, int alpha, int beta, int depth){

if (board.kingAttacked() && board.generateLegalMoves().size() == 0) {
Statistics.qleafs++;
return -INF - depth;
return -INF + ply;
}

int standPat = Evaluation.evaluateState(board);
Expand All @@ -166,11 +207,11 @@ public static int qSearch(Board board, int alpha, int beta, int depth){
alpha = standPat;

MoveList moves = board.generateLegalQMoves();
moves = MoveOrder.moveOrdering(board, moves);
moves = MoveOrder.moveOrdering(board, moves, ply);
int value;
for (Move move : moves){
board.push(move);
value = -qSearch(board, -beta, -alpha, depth - 1);
value = -qSearch(board, depth - 1, ply + 1, -beta, -alpha);
board.pop();
if (value >= beta) {
Statistics.qbetaCutoffs++;
Expand All @@ -183,7 +224,21 @@ public static int qSearch(Board board, int alpha, int beta, int depth){
}

public static boolean isScoreCheckmate(int score){
return Math.abs(score) >= INF;
return Math.abs(score) >= INF/2;
}

public static boolean canApplyNullWindow(Board board, int depth, boolean pV){
return !pV && depth >= NullMinDepth && !board.kingAttacked();
}

public static boolean canApplyLMR(Board board, int depth, Move move, int moveIndex, boolean fromCheck){
return depth >= LMRMinDepth && moveIndex >= LMRMoveWOReduction && move.flags() == Move.QUIET
&& !fromCheck && !board.kingAttacked();
}

public static int LMRReducedDepth(int depth, boolean pV){
return pV ?
depth - LMRPVReduction :
depth / LMRNonPVDiv;
}
}
Loading

0 comments on commit aff46ad

Please sign in to comment.