MadChess 2.0 Beta Build 059 (Free Passed Pawns)

I added free passed pawn evaluation to MadChess 2.0 Beta. Unobstructed passed pawns on the sixth or seventh rank are awarded a bonus if they may advance to the next square without material loss. The square must be unoccupied and the score after exchanging pieces on the advance square must not be negative.

private const int _freePassedPawn = 100;
// Enemy has minor or major pieces.
if (Rank >= 6)
{
// Determine if passed pawn is free.
int advanceSquare;
if (_board.IsPawnAdvanceSquareFree(pawnSquare, direction, out advanceSquare))
{
// Pawn is free to advance one square.
ulong move = 0ul;
Move.SetFrom(ref move, pawnSquare);
Move.SetTo(ref move, advanceSquare);
Move.SetPromotedPiece(ref move, Rank == 7 ? Piece.Queen : Piece.None);
if (!pawnMove)
{
// Play null move.
_board.PlayNullMove();
}
if (_board.IsMoveLegal(ref move))
{
if (_search.GetExchangeScore(move) >= 0)
{
// Pawn can advance without material loss.
MgPassedPawn += _freePassedPawn;
EgPassedPawn += _freePassedPawn;
}
}
if (!pawnMove)
{
// Undo null move.
_board.UndoMove();
}
}
}

In addition, I simplified the move ordering code. I felt the GetNextMove() method required too much external bookkeeping (to mark played moves, illegal moves, update local move variable, save move back to array, etc) and therefore was too prone to bugs. I replaced it with a two stage sort and a simple for loop. First, the four highest priority moves are identified. If the fifth move is reached, the remaining moves are sorted. This avoids the cost of sorting all moves in 90% of the cases where a cutoff occurs in the first few moves. The two stage sort performed as well as the GetNextMove() technique.

private const int _sortBestMoves = 4;
bool movesGenerated = Depth == 0;
bool bestMovePlayed = bestMove == 0ul;
for (int moveIndex = 0; !movesGenerated || (moveIndex < _board.CurrentPosition.MoveIndex); moveIndex++)
{
ulong move;
if (Depth == 0)
{
// Root moves
if (moveIndex == 0)
{
// Sort root moves.
SortMovesByPriority(_rootMoves, _rootScores, 0, _board.RootPosition.MoveIndex - 1, true,
bestMove, Depth);
}
// Get next move.
move = _rootMoves[moveIndex];
}
else
{
// Other moves
if (movesGenerated)
{
// Moves have been generated.
if (moveIndex == _sortBestMoves)
{
// Sort moves.
SortMovesByPriority(_board.CurrentPosition.Moves, _sortBestMoves,
_board.CurrentPosition.MoveIndex - 1, false, bestMove, Depth);
}
// Get next move.
move = _board.CurrentPosition.Moves[moveIndex];
}
else
{
// Moves have not been generated.
if (bestMovePlayed)
{
// Best move has been played.
// Generate moves.
_board.GenerateMoves(false);
movesGenerated = true;
if (_board.CurrentPosition.MoveIndex > 0)
{
// Candidate moves found.
// Sort best moves.
SortBestMovesByPriority(_board.CurrentPosition.Moves, _sortBestMoves,
_board.CurrentPosition.MoveIndex - 1, true, bestMove, Depth);
// Get next move.
moveIndex = 0;
move = _board.CurrentPosition.Moves[moveIndex];
}
else
{
// No candidate moves found.
break;
}
if (Move.Equals(move, bestMove))
{
// Do not play best move twice.
continue;
}
}
else
{
// Best move has not been played.
// Play best move.
move = bestMove;
bestMovePlayed = true;
}
}
}
// Determine if move is legal.
if (_board.IsMoveLegal(ref move))
{
// Move is legal.
legalMoveNumber++;
if (Depth == 0)
{
// Update root move.
_rootMove = move;
_rootMoveNumber = legalMoveNumber;
}
}
else
{
// Skip illegal move.
continue;
}
// ...
private void SortBestMovesByPriority(ulong[] Moves, int BestMoves, int LastMoveIndex, bool PrioritizeMoves,
ulong BestMove, int Depth)
{
if (PrioritizeMoves)
{
// Prioritize moves.
this.PrioritizeMoves(Moves, 0, LastMoveIndex, BestMove, Depth);
}
// Sort a limited number of best moves. Moves beyond this number remain unsorted.
int maxRank = Math.Min(BestMoves, LastMoveIndex);
for (int rank = 0; rank < maxRank; rank++)
{
ulong bestMove = 0ul;
int bestMoveIndex = 0;
for (int moveIndex = rank; moveIndex <= LastMoveIndex; moveIndex++)
{
ulong move = Moves[moveIndex];
if (move > bestMove)
{
bestMove = move;
bestMoveIndex = moveIndex;
}
}
ulong tempMove = Moves[rank];
Moves[rank] = Moves[bestMoveIndex];
Moves[bestMoveIndex] = tempMove;
}
}
public void SortMovesByPriority(ulong[] Moves, int FirstMoveIndex, int LastMoveIndex, bool PrioritizeMoves,
ulong BestMove, int Depth)
{
SortMovesByPriority(Moves, null, FirstMoveIndex, LastMoveIndex, PrioritizeMoves, BestMove, Depth);
}
private void SortMovesByPriority(ulong[] Moves, int[] Scores, int FirstMoveIndex, int LastMoveIndex,
bool PrioritizeMoves, ulong BestMove, int Depth)
{
if (PrioritizeMoves)
{
// Prioritize moves.
this.PrioritizeMoves(Moves, FirstMoveIndex, LastMoveIndex, BestMove, Depth);
}
int length = LastMoveIndex - FirstMoveIndex + 1;
if (Scores == null)
{
// Sort moves.
Array.Sort(Moves, FirstMoveIndex, length, _movePriorityComparer);
}
else
{
// Sort moves and scores.
Array.Sort(Moves, Scores, FirstMoveIndex, length, _movePriorityComparer);
}
}
private void PrioritizeMoves(ulong[] Moves, int FirstMoveIndex, int LastMoveIndex, ulong BestMove, int Depth)
{
for (int moveIndex = FirstMoveIndex; moveIndex <= LastMoveIndex; moveIndex++)
{
ulong move = Moves[moveIndex];
if (BestMove != 0ul)
{
// Prioritize best move.
if (Move.Equals(move, BestMove))
{
Move.SetIsBest(ref move, true);
}
}
// Prioritize killer moves.
Move.SetKiller(ref move, _killerMoves.GetValue(Depth, move));
// Prioritize by move history.
Move.SetHistory(ref move, _moveHistory.GetValue(move));
Moves[moveIndex] = move;
}
}
private void SortMovesByScore(ulong[] Moves, int[] Scores, int FirstMoveIndex, int LastMoveIndex)
{
int length = LastMoveIndex - FirstMoveIndex + 1;
// Sort moves.
Array.Sort(Scores, Moves, FirstMoveIndex, length, _moveScoreComparer);
}

This added 31 Elo to the playing strength of MadChess 2.0 Beta.

MadChess 2.0                   2224 :    800 (+439,=151,-210),  64.3 %

vs.                                 :  games (   +,   =,   -),   (%) :   Diff,  SD, CFS (%)
Glass 1.6                           :     50 (   4,   9,  37),  17.0 :   -196,  25,    0.0
Sungorus 1.4                        :     50 (  19,   9,  22),  47.0 :    -86,  12,    0.0
FireFly v2.6.0                      :     50 (  25,  11,  14),  61.0 :    +21,  14,   93.6
ZCT 0.3.2450                        :     50 (  20,  12,  18),  52.0 :    +23,  14,   95.4
Jazz v444                           :     50 (  25,  10,  15),  60.0 :    +43,  13,  100.0
Beowulf 2.4                         :     50 (  23,   9,  18),  55.0 :    +58,  19,   99.9
Wing 2.0                            :     50 (  24,  18,   8),  66.0 :   +108,  15,  100.0
BikJump v2.01                       :     50 (  17,  16,  17),  50.0 :   +123,  13,  100.0
Brainless 1.0                       :     50 (  33,   6,  11),  72.0 :   +125,  19,  100.0
Matheus-2.3                         :     50 (  27,  10,  13),  64.0 :   +148,  12,  100.0
Monarch 1.7                         :     50 (  29,  12,   9),  70.0 :   +175,  15,  100.0
BigLion 2.23w                       :     50 (  34,   6,  10),  74.0 :   +201,  13,  100.0
Sharper 0.17                        :     50 (  38,   6,   6),  82.0 :   +217,  13,  100.0
Faile 1.4                           :     50 (  37,   8,   5),  82.0 :   +244,  12,  100.0
Jabba13032012                       :     50 (  42,   4,   4),  88.0 :   +275,  12,  100.0
Roce 0.0390                         :     50 (  42,   5,   3),  89.0 :   +344,  13,  100.0
Feature Category Date Rev1 WAC2 Elo Rating3 Improvement
Free Passed Pawns Evaluation 2015 Mar 10 59 270 2224 +31
Unstoppable Pawns
Draws, Material Trades
Evaluation 2015 Jan 31 52 270 2193 +39
Late Move Pruning Search 2015 Jan 10 44 273 2154 +39
History Heuristic
Late Move Reductions
Search 2015 Jan 04 40 275 2115 +50
Killer Moves Search 2015 Jan 03 38 275 2065 +61
Futility Pruning Search 2014 Dec 29 37 256 2004 +54
Null Move
Quiescence Recaptures
Search 2014 Dec 28 34 242 1950 +46
King Safety Evaluation 2014 Dec 24 32 225 1904 +27
Piece Mobility Evaluation 2014 Dec 16 29 225 1877 +64
Draw By Repetition Bug Evaluation 2014 Dec 10 27 225 1813 +47
Passed Pawns Evaluation 2014 Dec 09 26 225 1766 +72
Time Management Search 2014 Dec 08 25 231 1694 +24
Delay Move Generation
Aspiration Window Bug
Search 2014 Dec 02 23 231 1670 +44
MVV / LVA Move Order
Draw By Insufficient Material
Move List Overflow Bug
Search 2014 Dec 01 22 235 1626 +30
Tapered Evaluation
MG and EG Piece Location
Evaluation 2014 Nov 29 21 234 1596 +107
Alpha / Beta Negamax
Aspiration Windows
Quiescence, Hash
Material, Piece Squares
Baseline 2014 Nov 25 20 236 1489
  1. Subversion source code revision
  2. Win At Chess position test, 3 seconds per position
  3. Bullet chess, 2 min / game + 1 sec / move

Banks 51st Amateur Series Division 7

MadChess 1.4 participated in Graham Banks’ 51st amateur tournament in division 7.

                                  1    2    3    4    5    6    7    8    9    0    1    2    
1   Jazz 840 64-bit               **** 0100 ½01½ 01½1 0011 1½11 1101 1½½½ 11½½ ½1½½ ½1½½ 1111  28.5/44
2   Carballo 1.1                  1011 **** ½000 ½001 ½1½1 111½ 101½ 0110 0110 1½10 111½ 1111  28.0/44
3   FireFly 2.7.0 64-bit          ½10½ ½111 **** 0110 1110 0½½1 01½0 1111 ½1½0 ½010 00½0 11½½  25.0/44
4   PikoSzachy Extreme            10½0 ½110 1001 **** 01½1 0½0½ 1001 1½11 1111 0100 1001 0½1½  24.0/44
5   Lozza 1.14a 64-bit            1100 ½0½0 0001 10½0 **** ½011 1½00 1000 0½01 1111 01½½ 1111  22.0/44
6   Orion 0.2 64-bit              0½00 000½ 1½½0 1½1½ ½100 **** 1101 0000 11½0 ½111 0½½1 1110  21.5/44
7   Protej 0.5.8c                 0010 010½ 10½1 0110 0½11 0010 **** 0111 ½001 1010 011½ 0010  20.5/44  451.00
8   Fischerle 0.9.60 64-bit       0½½½ 1001 0000 0½00 0111 1111 1000 **** 1010 10½0 1111 0010  20.5/44  443.25
9   Mango Paola Ajedrez 4.1       00½½ 1001 ½0½1 0000 1½10 00½1 ½110 0101 **** 101½ ½101 ½01½  20.5/44  440.25
10  Absolute Zero 2.4.0.0 64-bit  ½0½½ 0½01 ½101 1011 0000 ½000 0101 01½1 010½ **** 10½½ 0111  20.0/44
11  Exacto 0.e 64-bit             ½0½½ 000½ 11½1 0110 10½½ 1½½0 100½ 0000 ½010 01½½ **** 1½0½  18.5/44
12  MadChess 1.4 64-bit           0000 0000 00½½ 1½0½ 0000 0001 1101 1101 ½10½ 1000 0½1½ ****  15.0/44

Games

MadChess 2.0 Beta Build 052 (Unstoppable Pawns, Draws, Material Trades)

I added code to detect unstoppable passed pawns and drawn endgames, and code to evaluate material trades. The code that evaluates material trades implements the age old advice, “When ahead trade pieces. When behind trade pawns.” A greater bonus is giving for trading pawns, since in many endgames the side with a material advantage cannot win without promoting a pawn.

private const int _unstoppablePassedPawn = 700; // Less than a queen minus a pawn to encourage pawn promotion.
private const int _pieceTradesPercent = 25;
private const int _pawnTradesPercent = 50;
private void GetPassedPawnScore(bool White, int Rank, int File, out int MgPassedPawn, out int EgPassedPawn)
{
MgPassedPawn = _mgPassedPawns[Rank];
EgPassedPawn = _egPassedPawns[Rank];
int pawnSquare;
int promotionSquare;
int enemyKingSquare;
int direction;
int enemyMinorAndMajorPieces;
if (White)
{
// White pawn
pawnSquare = _board.WhiteRankFiles[Rank][File];
promotionSquare = _board.WhiteRankFiles[8][File];
enemyKingSquare = _board.CurrentPosition.BlackKingSquare;
direction = Direction.North;
enemyMinorAndMajorPieces = _board.CurrentPosition.BlackMinorPieces +
_board.CurrentPosition.BlackMajorPieces;
}
else
{
// Black pawn
pawnSquare = _board.BlackRankFiles[Rank][File];
promotionSquare = _board.BlackRankFiles[8][File];
enemyKingSquare = _board.CurrentPosition.WhiteKingSquare;
direction = Direction.South;
enemyMinorAndMajorPieces = _board.CurrentPosition.WhiteMinorPieces +
_board.CurrentPosition.WhiteMajorPieces;
}
if (enemyMinorAndMajorPieces == 0)
{
// Enemy has no minor or major pieces.
if (_board.IsPawnPromotionPathFree(pawnSquare, direction))
{
// Pawn is not blocked by own king.
bool pawnMove = White == _board.CurrentPosition.WhiteMove;
int pawnDistanceToPromotionSquare = _board.GetDistance(pawnSquare, promotionSquare);
int kingDistanceToPromotionSquare = _board.GetDistance(enemyKingSquare, promotionSquare);
if (!pawnMove)
{
// Enemy king can move one square closer to pawn.
kingDistanceToPromotionSquare--;
}
if (kingDistanceToPromotionSquare > pawnDistanceToPromotionSquare)
{
// Enemy king cannot stop pawn from promoting.
MgPassedPawn = _unstoppablePassedPawn;
EgPassedPawn = _unstoppablePassedPawn;
}
}
}
}

private bool IsDrawnEndgame(int MaterialScore)
{
// Return true if position is drawn. Do not terminate search based on this method because a sequence
of moves could make the game winnable.
if ((_board.CurrentPosition.WhitePawns > 0) || (_board.CurrentPosition.BlackPawns > 0)) return false;
// Neither side has any pawns.
// Material score represents score for side to move.
bool whiteWinning = _board.CurrentPosition.WhiteMove ? MaterialScore >= 0 : MaterialScore <= 0;
int winningKnights;
int winningBishops;
int winningRooks;
int winningQueens;
int winningMinorPieces;
int winningMajorPieces;
int losingKnights;
int losingBishops;
int losingRooks;
int losingQueens;
int losingMinorPieces;
int losingMajorPieces;
if (whiteWinning)
{
// White is ahead in material.
winningKnights = _board.CurrentPosition.WhiteKnights;
winningBishops = _board.CurrentPosition.WhiteBishops;
winningRooks = _board.CurrentPosition.WhiteRooks;
winningQueens = _board.CurrentPosition.WhiteQueens;
winningMinorPieces = _board.CurrentPosition.WhiteMinorPieces;
winningMajorPieces = _board.CurrentPosition.WhiteMajorPieces;
losingKnights = _board.CurrentPosition.BlackKnights;
losingBishops = _board.CurrentPosition.BlackBishops;
losingRooks = _board.CurrentPosition.BlackRooks;
losingQueens = _board.CurrentPosition.BlackQueens;
losingMinorPieces = _board.CurrentPosition.BlackMinorPieces;
losingMajorPieces = _board.CurrentPosition.BlackMajorPieces;
}
else
{
// Black is ahead in material.
winningKnights = _board.CurrentPosition.BlackKnights;
winningBishops = _board.CurrentPosition.BlackBishops;
winningRooks = _board.CurrentPosition.BlackRooks;
winningQueens = _board.CurrentPosition.BlackQueens;
winningMinorPieces = _board.CurrentPosition.BlackMinorPieces;
winningMajorPieces = _board.CurrentPosition.BlackMajorPieces;
losingKnights = _board.CurrentPosition.WhiteKnights;
losingBishops = _board.CurrentPosition.WhiteBishops;
losingRooks = _board.CurrentPosition.WhiteRooks;
losingQueens = _board.CurrentPosition.WhiteQueens;
losingMinorPieces = _board.CurrentPosition.WhiteMinorPieces;
losingMajorPieces = _board.CurrentPosition.WhiteMajorPieces;
}
if ((winningRooks == 2) && (winningMajorPieces == 2) && (winningMinorPieces == 0))
{
// Winning side has two rooks.
if ((losingQueens == 1) && (losingMajorPieces == 1) && (losingMinorPieces == 0))
{
// Losing side has queen.
return true;
}
if ((losingRooks == 1) && (losingMajorPieces == 1))
{
if ((losingBishops == 1) && (losingKnights == 1))
{
// Losing side has rook, bishop, and knight.
return true;
}
if (losingMinorPieces == 1)
{
// Losing side has rook and minor piece.
return true;
}
}
}
if ((winningQueens == 1) && (winningMajorPieces == 1) && (winningMinorPieces == 0))
{
// Winning side has queen.
if ((losingRooks == 2) && (losingMajorPieces == 2) && (losingMinorPieces == 0))
{
// Losing side has two rooks.
return true;
}
if ((losingRooks == 1) && (losingMajorPieces == 1) && (losingMinorPieces == 1))
{
// Losing side has rook and minor piece.
return true;
}
}
if ((winningRooks == 1) && (winningMajorPieces == 1) && (winningMinorPieces == 1))
{
// Winning side has rook and minor piece.
if ((losingRooks == 1) && (losingMajorPieces == 1) && (losingMinorPieces == 0))
{
// Losing side has rook.
return true;
}
}
if ((winningBishops == 1) && (winningKnights == 1) && (winningMajorPieces == 0))
{
// Winning side has bishop and knight.
if ((losingRooks == 1) && (losingMajorPieces == 1) && (losingMinorPieces == 0))
{
// Losing side has rook.
return true;
}
}
if ((winningRooks == 1) && (winningMajorPieces == 1) && (winningMinorPieces == 0))
{
// Winning side has rook.
if ((losingRooks == 1) && (losingMajorPieces == 1) && (losingMinorPieces == 0))
{
// Losing side has rook.
return true;
}
if ((losingMinorPieces == 1) && (losingMajorPieces == 0))
{
// Losing side has minor piece.
return true;
}
}
if ((winningKnights == 2) && (winningMinorPieces == 2) && (winningMajorPieces == 0))
{
// Winning side has two knights.
if ((losingMinorPieces <= 1) && (losingMajorPieces == 0))
{
// Losing side has one or zero minor pieces.
return true;
}
}
return false;
}

private int EvaluateMaterialTrades(int MaterialScore)
{
// Material score represents score for side to move.
bool whiteWinning = _board.CurrentPosition.WhiteMove ? MaterialScore >= 0 : MaterialScore <= 0;
int absoluteMaterialScore = Math.Abs(MaterialScore);
// When ahead trade pieces.
// When behind trade pawns.
int losingMissingPieces;
int winningMissingPawns;
if (whiteWinning)
{
// White is ahead in material.
losingMissingPieces = 7 - _board.CurrentPosition.BlackMinorPieces -
_board.CurrentPosition.BlackMajorPieces;
winningMissingPawns = 8 - _board.CurrentPosition.WhitePawns;
}
else
{
// Black is ahead in material.
losingMissingPieces = 7 - _board.CurrentPosition.WhiteMinorPieces -
_board.CurrentPosition.WhiteMajorPieces;
winningMissingPawns = 8 - _board.CurrentPosition.BlackPawns;
}
int losingMissingPiecesScore = (absoluteMaterialScore * losingMissingPieces * _pieceTradesPercent) / 700;
int winningMissingPawnsScore = (absoluteMaterialScore * winningMissingPawns * _pawnTradesPercent) / 800;
int materialTrades = losingMissingPiecesScore - winningMissingPawnsScore;
return whiteWinning ? materialTrades : -materialTrades;
}

This added 39 Elo to the playing strength of MadChess 2.0 Beta.

MadChess 2.0                   2193 :    800 (+399,=168,-233),  60.4 %

vs.                                 :  games (   +,   =,   -),   (%) :   Diff,  SD, CFS (%)
Glass 1.6                           :     50 (   4,   7,  39),  15.0 :   -223,  27,    0.0
Sungorus 1.4                        :     50 (  18,   7,  25),  43.0 :   -117,  12,    0.0
ZCT 0.3.2450                        :     50 (  13,  12,  25),  38.0 :    -15,  14,   14.3
FireFly v2.6.0                      :     50 (  18,  19,  13),  55.0 :    -13,  15,   19.0
Jazz v444                           :     50 (  19,  15,  16),  53.0 :     +9,  14,   73.6
Beowulf 2.4                         :     50 (  21,  13,  16),  55.0 :    +31,  20,   93.9
Wing 2.0                            :     50 (  22,  11,  17),  55.0 :    +71,  15,  100.0
Brainless 1.0                       :     50 (  26,  11,  13),  63.0 :    +82,  19,  100.0
BikJump v2.01                       :     50 (  22,  10,  18),  54.0 :    +93,  13,  100.0
Matheus-2.3                         :     50 (  21,  17,  12),  59.0 :   +115,  13,  100.0
Monarch 1.7                         :     50 (  28,  11,  11),  67.0 :   +142,  15,  100.0
BigLion 2.23w                       :     50 (  32,  12,   6),  76.0 :   +170,  13,  100.0
Sharper 0.17                        :     50 (  37,   5,   8),  79.0 :   +184,  13,  100.0
Faile 1.4                           :     50 (  36,  12,   2),  84.0 :   +213,  12,  100.0
Jabba13032012                       :     50 (  41,   1,   8),  83.0 :   +242,  13,  100.0
Roce 0.0390                         :     50 (  41,   5,   4),  87.0 :   +311,  13,  100.0
Feature Category Date Rev1 WAC2 Elo Rating3 Improvement
Unstoppable Pawns
Draws, Material Trades
Evaluation 2015 Jan 31 52 270 2193 +39
Late Move Pruning Search 2015 Jan 10 44 273 2154 +39
History Heuristic
Late Move Reductions
Search 2015 Jan 04 40 275 2115 +50
Killer Moves Search 2015 Jan 03 38 275 2065 +61
Futility Pruning Search 2014 Dec 29 37 256 2004 +54
Null Move
Quiescence Recaptures
Search 2014 Dec 28 34 242 1950 +46
King Safety Evaluation 2014 Dec 24 32 225 1904 +27
Piece Mobility Evaluation 2014 Dec 16 29 225 1877 +64
Draw By Repetition Bug Evaluation 2014 Dec 10 27 225 1813 +47
Passed Pawns Evaluation 2014 Dec 09 26 225 1766 +72
Time Management Search 2014 Dec 08 25 231 1694 +24
Delay Move Generation
Aspiration Window Bug
Search 2014 Dec 02 23 231 1670 +44
MVV / LVA Move Order
Draw By Insufficient Material
Move List Overflow Bug
Search 2014 Dec 01 22 235 1626 +30
Tapered Evaluation
MG and EG Piece Location
Evaluation 2014 Nov 29 21 234 1596 +107
Alpha / Beta Negamax
Aspiration Windows
Quiescence, Hash
Material, Piece Squares
Baseline 2014 Nov 25 20 236 1489
  1. Subversion source code revision
  2. Win At Chess position test, 3 seconds per position
  3. Bullet chess, 2 min / game + 1 sec / move

MadChess 2.0 Beta Build 044 (Late Move Pruning)

I added late move pruning to MadChess 2.0 Beta. Moves are sorted according to the history heuristic. If a quiet move qualifies for the largest late move reduction, but is less than or equal to four moves from the search horizon, it is skipped. The move is very unlikely to cause a beta cutoff and no time is wasted searching it.

// Get move horizon.
int moveHorizon = GetMoveHorizon(Depth, Horizon, move, pawnPush, pawnPromotion, quietMoveNumber);
if (moveHorizon < 0)
{
// Prune late move.
// Mark move as played.
Move.SetPlayed(ref move, true);
moves[moveIndex] = move;
// Skip move.
continue;
}
private int GetMoveHorizon(int Depth, int Horizon, ulong Move, bool PawnPush, bool PawnPromotion,
int QuietMoveNumber)
{
if (PawnPush || PawnPromotion || (MadChess.Move.CaptureVictim(Move) != Piece.None) ||
MadChess.Move.IsCheck(Move) || _board.CurrentPosition.KingInCheck)
{
// Do not reduce search horizon of pawn pushes, pawn promotions, captures, checking moves,
or when king is in check.
return Horizon;
}
int toHorizon = Horizon - Depth;
int lastIndex = _lateMoveReductions.Length - 1;
for (int index = lastIndex; index >= 0; index--)
{
int lateMove = _lateMoveReductions[index];
if (QuietMoveNumber >= lateMove)
{
if ((index == lastIndex) && (toHorizon <= _lateMovePruningMaxToHorizon))
{
// Prune late move.
return -1;
}
// Reduce search horizon of late move.
return Horizon - index - 1;
}
}
return Horizon;
}

This added 39 Elo to the playing strength of MadChess 2.0 Beta. MadChess 2.0 Beta is now stronger than MadChess 1.4.

MadChess 2.0                   2154 :    800 (+347,=162,-291),  53.5 %

vs.                                 :  games (   +,   =,   -),   (%) :   Diff,  SD, CFS (%)
Glass 1.6                           :     50 (   4,   2,  44),  10.0 :   -281,  27,    0.0
Sungorus 1.4                        :     50 (  14,  16,  20),  44.0 :   -156,  12,    0.0
Capivara LK 0.09a01a                :     50 (   9,  14,  27),  32.0 :   -153,  16,    0.0
Mediocre v0.34                      :     50 (  13,   8,  29),  34.0 :    -63,  15,    0.0
FireFly v2.6.0                      :     50 (  20,  13,  17),  53.0 :    -46,  14,    0.0
ZCT 0.3.2450                        :     50 (  20,  11,  19),  51.0 :    -43,  15,    0.2
Jazz v444                           :     50 (  12,   9,  29),  33.0 :    -39,  13,    0.1
Wing 2.0                            :     50 (  19,   9,  22),  47.0 :    +31,  14,   98.9
BikJump v2.01                       :     50 (  20,  12,  18),  52.0 :    +58,  14,  100.0
Matheus-2.3                         :     50 (  21,  12,  17),  54.0 :    +76,  14,  100.0
Monarch 1.7                         :     50 (  27,  11,  12),  65.0 :   +106,  14,  100.0
BigLion 2.23w                       :     50 (  33,   6,  11),  72.0 :   +133,  12,  100.0
Sharper 0.17                        :     50 (  34,  10,   6),  78.0 :   +147,  13,  100.0
Faile 1.4                           :     50 (  30,  11,   9),  71.0 :   +174,  12,  100.0
Jabba13032012                       :     50 (  36,   7,   7),  79.0 :   +204,  13,  100.0
Roce 0.0390                         :     50 (  35,  11,   4),  81.0 :   +273,  12,  100.0
Feature Category Date Rev1 WAC2 Elo Rating3 Improvement
Late Move Pruning Search 2015 Jan 10 44 273 2154 +39
History Heuristic
Late Move Reductions
Search 2015 Jan 04 40 275 2115 +50
Killer Moves Search 2015 Jan 03 38 275 2065 +61
Futility Pruning Search 2014 Dec 29 37 256 2004 +54
Null Move
Quiescence Recaptures
Search 2014 Dec 28 34 242 1950 +46
King Safety Evaluation 2014 Dec 24 32 225 1904 +27
Piece Mobility Evaluation 2014 Dec 16 29 225 1877 +64
Draw By Repetition Bug Evaluation 2014 Dec 10 27 225 1813 +47
Passed Pawns Evaluation 2014 Dec 09 26 225 1766 +72
Time Management Search 2014 Dec 08 25 231 1694 +24
Delay Move Generation
Aspiration Window Bug
Search 2014 Dec 02 23 231 1670 +44
MVV / LVA Move Order
Draw By Insufficient Material
Move List Overflow Bug
Search 2014 Dec 01 22 235 1626 +30
Tapered Evaluation
MG and EG Piece Location
Evaluation 2014 Nov 29 21 234 1596 +107
Alpha / Beta Negamax
Aspiration Windows
Quiescence, Hash
Material, Piece Squares
Baseline 2014 Nov 25 20 236 1489
  1. Subversion source code revision
  2. Win At Chess position test, 3 seconds per position
  3. Bullet chess, 2 min / game + 1 sec / move

MadChess 2.0 Beta Build 040 (History Heuristic + Late Move Reductions)

I added a history heuristic and late move reductions to MadChess 2.0 Beta. When a move causes a beta cutoff, the move’s history value is incremented by the distance to the horizon squared. (Distance to the horizon squared is used to prevent moves deep in the search tree from having too much influence over move order.) A history value is saved for each combination of piece type and move destination square. Moves are ordered by their history values descending (after cached best move, capture victim, capture attacker, promoted piece, and killer moves). The search horizon is reduced for moves that appear late in the ordered move list. The later the move, the more the search horizon is reduced. Because the move does not have a history of causing a beta cutoff, it is likely to fail low and less time is spent searching it.

History values are aged (reduced by a percentage) during iterative deepening of the search, to prevent moves from shallow searches from having too much influence over the move order of deeper searches.

public sealed class MoveHistory
{
private readonly Board _board;
private readonly int[][] _moveHistory;
public MoveHistory(Board Board)
{
_board = Board;
_moveHistory = new int[Piece.BlackKing + 1][];
for (int piece = Piece.WhitePawn; piece <= Piece.BlackKing; piece++)
{
_moveHistory[piece] = new int[64];
for (int square = 0; square < 64; square++)
{
_moveHistory[piece][square] = 0;
}
}
}
public int GetValue(ulong Move)
{
int piece = _board.CurrentPosition.Squares[MadChess.Move.From(Move)];
int toSquare = MadChess.Move.To(Move);
return _moveHistory[piece][toSquare];
}
public void UpdateValue(ulong Move, int Increment)
{
int piece = _board.CurrentPosition.Squares[MadChess.Move.From(Move)];
int toSquare = MadChess.Move.To(Move);
_moveHistory[piece][toSquare] += Increment;
}
public void Reset()
{
Age(0, true);
Age(0, false);
}
public void Age(int Percent, bool WhiteMove)
{
int pieceMin;
int pieceMax;
if (WhiteMove)
{
pieceMin = Piece.WhitePawn;
pieceMax = Piece.WhiteKing;
}
else
{
pieceMin = Piece.BlackPawn;
pieceMax = Piece.BlackKing;
}
for (int piece = pieceMin; piece <= pieceMax; piece++)
{
for (int square = 0; square < 64; square++)
{
_moveHistory[piece][square] = (_moveHistory[piece][square] * Percent) / 100;
}
}
}
}

int[] _lateMoveReductions = new[] {3, 7, 15};
// Play move.
_board.PlayMove(move);
// Mark move as played.
Move.SetPlayed(ref move, true);
moves[moveIndex] = move;
// Get move horizon.
int moveHorizon = GetMoveHorizon(Horizon, move, pawnPush, pawnPromotion, quietMoveNumber);
// Search move.
int score = -GetDynamicScore(Depth + 1, moveHorizon, OriginalHorizon, true, -moveBeta, -Alpha);
if (score > bestScore)
{
// Move may be stronger than principal variation.
bool windowNarrowed = moveBeta < Beta;
bool horizonReduced = moveHorizon < Horizon;
if (windowNarrowed || horizonReduced)
{
// Search move with full alpha / beta window at unreduced horizon.
score = -GetDynamicScore(Depth + 1, Horizon, OriginalHorizon, true, -Beta, -Alpha);
}
}
// Undo move.
_board.UndoMove();
if ((score > Alpha) && (score < Beta))
{
if (Depth == 0)
{
// Update root move score.
_rootScores[moveIndex] = score;
}
}
if (score >= Beta)
{
// Position is not the result of best play by both players.
if (quietMove)
{
// Update killer moves.
_killerMoves.UpdateValue(Depth, move);
// Update move history.
_moveHistory.UpdateValue(move, toHorizon * toHorizon);
}
// Update best move cache.
UpdateBestMoveCache(Depth, Horizon, move, score, Alpha, Beta);
// Beta cutoff
return Beta;
}
private int GetMoveHorizon(int Horizon, ulong Move, bool PawnPush, bool PawnPromotion, int QuietMoveNumber)
{
if (PawnPush || PawnPromotion || (MadChess.Move.CaptureVictim(Move) != Piece.None) ||
MadChess.Move.IsCheck(Move) || _board.CurrentPosition.KingInCheck)
{
// Do not reduce search horizon of pawn pushes, pawn promotions, captures,
// checking moves, or when king is in check.
return Horizon;
}
for (int index = _lateMoveReductions.Length - 1; index >= 0; index--)
{
int lateMove = _lateMoveReductions[index];
if (QuietMoveNumber >= lateMove)
{
// Reduce search horizon of late move.
return Horizon - index - 1;
}
}
return Horizon;
}
public ulong GetNextMove(ulong[] Moves, int Depth, int CandidateMoveNumber, out int MoveIndex)
{
for (int moveIndex = 0; moveIndex < _board.CurrentPosition.MoveIndex; moveIndex++)
{
ulong move = Moves[moveIndex];
if (move != 0ul)
{
if (CandidateMoveNumber == 0)
{
// Prioritize killer moves.
int killer = _killerMoves.GetValue(Depth, move);
Move.SetKiller(ref move, killer);
}
// Prioritize by move history.
Move.SetHistory(ref move, _moveHistory.GetValue(move));
Moves[moveIndex] = move;
}
}
// Find highest priority unplayed move.
ulong nextMove = 0ul;
MoveIndex = 0;
for (int moveIndex = 0; moveIndex < _board.CurrentPosition.MoveIndex; moveIndex++)
{
ulong move = Moves[moveIndex];
if (!Move.Played(move))
{
// Move has not been played.
if (move > nextMove)
{
nextMove = move;
MoveIndex = moveIndex;
}
}
}
return nextMove;
}
view raw Search.cs hosted with ❤ by GitHub

In addition, I changed the move representation from unsigned integers to unsigned longs to provide more bits for recording move history values.

This added 50 Elo to the playing strength of MadChess 2.0 Beta.

MadChess 2.0                   2115 :    800 (+305,=170,-325),  48.8 %

vs.                                 :  games (   +,   =,   -),   (%) :   Diff,  SD, CFS (%)
Glass 1.6                           :     50 (   3,  10,  37),  16.0 :   -273,  24,    0.0
Capivara LK 0.09a01a                :     50 (   3,  18,  29),  24.0 :   -196,  14,    0.0
Sungorus 1.4                        :     50 (  12,   9,  29),  33.0 :   -195,  11,    0.0
Mediocre v0.34                      :     50 (  14,   7,  29),  35.0 :    -99,  14,    0.0
FireFly v2.6.0                      :     50 (  13,  10,  27),  36.0 :    -93,  13,    0.0
ZCT 0.3.2450                        :     50 (  14,  13,  23),  41.0 :    -86,  15,    0.0
Jazz v444                           :     50 (  11,  12,  27),  34.0 :    -75,  13,    0.0
Wing 2.0                            :     50 (  19,   9,  22),  47.0 :     -6,  13,   32.2
BikJump v2.01                       :     50 (  20,   6,  24),  46.0 :    +18,  14,   89.2
Matheus-2.3                         :     50 (  19,  14,  17),  52.0 :    +37,  13,   99.7
Monarch 1.7                         :     50 (  20,   8,  22),  48.0 :    +60,  13,  100.0
BigLion 2.23w                       :     50 (  24,  17,   9),  65.0 :    +92,  12,  100.0
Sharper 0.17                        :     50 (  39,   4,   7),  82.0 :   +110,  12,  100.0
Faile 1.4                           :     50 (  29,  16,   5),  74.0 :   +135,  11,  100.0
Jabba13032012                       :     50 (  30,  10,  10),  70.0 :   +162,  13,  100.0
Roce 0.0390                         :     50 (  35,   7,   8),  77.0 :   +233,  12,  100.0
Feature Category Date Rev1 WAC2 Elo Rating3 Improvement
History Heuristic
Late Move Reductions
Search 2015 Jan 04 40 275 2115 +50
Killer Moves Search 2015 Jan 03 38 275 2065 +61
Futility Pruning Search 2014 Dec 29 37 256 2004 +54
Null Move
Quiescence Recaptures
Search 2014 Dec 28 34 242 1950 +46
King Safety Evaluation 2014 Dec 24 32 225 1904 +27
Piece Mobility Evaluation 2014 Dec 16 29 225 1877 +64
Draw By Repetition Bug Evaluation 2014 Dec 10 27 225 1813 +47
Passed Pawns Evaluation 2014 Dec 09 26 225 1766 +72
Time Management Search 2014 Dec 08 25 231 1694 +24
Delay Move Generation
Aspiration Window Bug
Search 2014 Dec 02 23 231 1670 +44
MVV / LVA Move Order
Draw By Insufficient Material
Move List Overflow Bug
Search 2014 Dec 01 22 235 1626 +30
Tapered Evaluation
MG and EG Piece Location
Evaluation 2014 Nov 29 21 234 1596 +107
Alpha / Beta Negamax
Aspiration Windows
Quiescence, Hash
Material, Piece Squares
Baseline 2014 Nov 25 20 236 1489
  1. Subversion source code revision
  2. Win At Chess position test, 3 seconds per position
  3. Bullet chess, 2 min / game + 1 sec / move