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 | – |
- Subversion source code revision
- Win At Chess position test, 3 seconds per position
- Bullet chess, 2 min / game + 1 sec / move