Skip to content

Commit

Permalink
bug fixes (matein) + performance boost (#32)
Browse files Browse the repository at this point in the history
* better piece evalulation
* removed obsolete test
* sending currmovenumber
* improved move ordering
* implemented hard problems test
* massive gain by sorting on checks
* fixed matein calculation
* bump version: 1.3.0
* update README
  • Loading branch information
sictransit authored Nov 30, 2024
1 parent 3945c16 commit ee94aff
Show file tree
Hide file tree
Showing 28 changed files with 174 additions and 1,617 deletions.
37 changes: 17 additions & 20 deletions Common/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class Board : IBoard
private readonly BoardInternals internals;

private int? score = null;
private bool? isChecked = null;

public Counters Counters { get; }

Expand Down Expand Up @@ -63,27 +64,12 @@ public int Score

score = 0;

foreach (var piece in GetPieces(Piece.White))
foreach (var piece in GetPieces())
{
var evaluation = internals.Scoring.EvaluatePiece(piece, phase);

score += evaluation;
score += evaluation * (piece.Is(Piece.White) ? 1 : -1);
}

//score += BitOperations.PopCount(white.Knight) > 1 ? 50 : 0;
//score += BitOperations.PopCount(white.Bishop) > 1 ? 50 : 0;
//score += BitOperations.PopCount(white.Rook) > 1 ? 50 : 0;

foreach (var piece in GetPieces(Piece.None))
{
var evaluation = internals.Scoring.EvaluatePiece(piece, phase);

score -= evaluation;
}

//score -= BitOperations.PopCount(black.Knight) > 1 ? 50 : 0;
//score -= BitOperations.PopCount(black.Bishop) > 1 ? 50 : 0;
//score -= BitOperations.PopCount(black.Rook) > 1 ? 50 : 0;
}

return score.Value;
Expand Down Expand Up @@ -322,15 +308,18 @@ public IEnumerable<Move> GetLegalMoves(Piece piece)
return PlayLegalMoves().Where(b => b.Counters.LastMove.Piece == piece).Select(b => b.Counters.LastMove);
}

public IEnumerable<IBoard> PlayLegalMoves(bool onlyCaptures = false)
public List<IBoard> PlayLegalMoves(bool onlyCaptures = false)
{
var boards = new List<IBoard>();
foreach (var piece in activeBoard.GetPieces())
{
foreach (var board in PlayLegalMoves(piece, onlyCaptures))
{
yield return board;
boards.Add(board);
}
}

return boards;
}

private IEnumerable<IBoard> PlayLegalMoves(Piece piece, bool onlyCaptures)
Expand Down Expand Up @@ -421,7 +410,15 @@ private bool ValidateMove(Move move, bool taking)
return true;
}

public bool IsChecked => IsAttacked(FindKing(ActiveColor));
public bool IsChecked
{
get
{
isChecked ??= IsAttacked(FindKing(ActiveColor));

return isChecked.Value;
}
}

public bool IsAttacked(Piece piece) => GetPiecesInRange(piece, piece.OpponentColor()).Any();
}
2 changes: 1 addition & 1 deletion Common/Interfaces/IBoard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public interface IBoard

IBoard Play(Move move);

IEnumerable<IBoard> PlayLegalMoves(bool onlyCaptures = false);
List<IBoard> PlayLegalMoves(bool onlyCaptures = false);

IEnumerable<Move> GetLegalMoves();

Expand Down
11 changes: 11 additions & 0 deletions Common/Lookup/Scoring.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,5 +212,16 @@ public int EvaluatePiece(Piece piece, int phase)

return (middle + end) / 24;
}

public static int GetBasicBieceValue(Piece piece) => piece.GetPieceType() switch
{
Piece.Pawn => 1,
Piece.Knight => 3,
Piece.Bishop => 3,
Piece.Rook => 5,
Piece.Queen => 9,
Piece.King => 1,
_ => 0,
};
}
}
115 changes: 57 additions & 58 deletions Engine/Patzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ public class Patzer : IEngine
private bool timeIsUp = false;
private int maxDepth = 0;
private int selDepth = 0;
private long nodeCount = 0;
private static int engineMaxDepth = 128;

private int PlayerSign => Board.ActiveColor.Is(Piece.White) ? 1 : -1;
private uint nodeCount = 0;
private const uint EngineMaxDepth = 128;

private OpeningBook? openingBook;

Expand Down Expand Up @@ -220,14 +218,14 @@ private void AddKillerMove(int ply, ulong hash)
}
}

while (maxDepth < engineMaxDepth)
while (maxDepth < EngineMaxDepth)
{
maxDepth++;
selDepth = maxDepth;
long startTime = stopwatch.ElapsedMilliseconds;
int? mateIn = default;

var score = EvaluateBoard(Board, 0, -Scoring.MoveMaximumScore, Scoring.MoveMaximumScore, PlayerSign);
var score = EvaluateBoard(Board, 0, -Scoring.MoveMaximumScore, Scoring.MoveMaximumScore, Board.ActiveColor.Is(Piece.White) ? 1 : -1);

long evaluationTime = stopwatch.ElapsedMilliseconds - startTime;

Expand All @@ -240,7 +238,10 @@ private void AddKillerMove(int ply, ulong hash)
UpdateBestLine(bestMove);
}

mateIn = CalculateMateIn(score, PlayerSign);
if (Math.Abs(score) == Scoring.MateScore)
{
mateIn = maxDepth / 2 * Math.Sign(score);
}

SendProgress(stopwatch, score, mateIn);

Expand All @@ -260,7 +261,7 @@ private void AddKillerMove(int ply, ulong hash)

string? abortMessage = null;

if (mateIn is > 0)
if (mateIn.HasValue)
{
abortMessage = $"aborting search @ depth {maxDepth}, mate in {mateIn}";
}
Expand Down Expand Up @@ -295,9 +296,9 @@ private void SendProgress(Stopwatch stopwatch, int score, int? mateIn)
SendInfo($"depth {maxDepth} seldepth {selDepth} nodes {nodeCount} nps {nodesPerSecond} hashfull {hashFull} score {scoreString} time {stopwatch.ElapsedMilliseconds} pv {pvString}");
}

private void SendCurrentMove(IBoard board)
private void SendCurrentMove(IBoard board, int currentMoveNumber)
{
SendInfo($"depth {maxDepth} currmove {board.Counters.LastMove.ToAlgebraicMoveNotation()}");
SendInfo($"depth {maxDepth} currmove {board.Counters.LastMove.ToAlgebraicMoveNotation()} currmovenumber {currentMoveNumber}");
}

private void UpdateBestLine(Move bestMove)
Expand All @@ -309,10 +310,10 @@ private void UpdateBestLine(Move bestMove)

var board = Board.Play(bestMove);

for (var i = 0; i < selDepth; i++)
for (var i = 0; i < maxDepth; i++)
{
var entry = transpositionTable[board.Hash % transpositionTableSize];
if (entry.EntryType == Enum.EntryType.Exact && entry.Hash == board.Hash && entry.Move != null && board.GetLegalMoves().Contains(entry.Move))
if (entry.EntryType == Enum.EntryType.Exact && entry.Hash == board.Hash && board.GetLegalMoves().Contains(entry.Move))
{
bestLine.Add((ply + i + 1, entry.Move));
board = board.Play(entry.Move);
Expand All @@ -324,55 +325,51 @@ private void UpdateBestLine(Move bestMove)
}
}

private static int? CalculateMateIn(int evaluation, int sign)
{
var mateInPlies = Math.Abs(Math.Abs(evaluation) - Scoring.MateScore);

if (mateInPlies <= engineMaxDepth)
{
var resultSign = Math.Sign(evaluation);

return (mateInPlies / 2 + (sign == 1 ? 1 : 0)) * resultSign;
}

return null;
}

private void SendInfo(string info) => SendCallbackInfo($"info {info}");

private void SendDebugInfo(string debugInfo) => SendInfo($"string debug {debugInfo}");

private void SendExceptionInfo(Exception exception) => SendInfo($"string exception {exception.GetType().Name} {exception.Message}");

private IEnumerable<IBoard> SortBords(IEnumerable<IBoard> boards, Move? preferredMove = null)
private IEnumerable<IBoard> SortBoards(IEnumerable<IBoard> boards, Move? preferredMove = null)
{
return boards.OrderByDescending(board =>
{
if (board.Counters.LastMove.Equals(preferredMove))
if (preferredMove != null && board.Counters.LastMove.Equals(preferredMove))
{
return int.MaxValue; // Highest priority for preferred move.
return 20; // Highest priority for preferred move.
}
if (transpositionTable[board.Hash % transpositionTableSize].EntryType == Enum.EntryType.Exact)
{
return int.MaxValue - 1; // High priority for exact transposition table entries.
return 10; // High priority for exact transposition table entries.
}
if (board.Counters.Capture != Piece.None)
{
return (int)board.Counters.Capture - (int)board.Counters.LastMove.Piece; // Capture value, sorting valuable captures first.
return Scoring.GetBasicBieceValue(board.Counters.Capture) - Scoring.GetBasicBieceValue(board.Counters.LastMove.Piece); // Capture value, sorting valuable captures first.
}
if (killerMoves[board.Counters.Ply].Contains(board.Hash))
{
return int.MinValue + 1; // High priority for killer moves
return -10; // High priority for killer moves
}
return int.MinValue;
if (board.IsChecked)
{
return -15;
}
//if (board.Counters.LastMove.Flags.HasFlag(SpecialMove.PawnPromotes))
//{
// return -20;
//}
return -25;
});
}

private int Quiesce(IBoard board, int α, int β, int sign, int depth)
private int Quiesce(IBoard board, int α, int β, int sign)
{
var standPat = board.Score * sign;

Expand All @@ -383,12 +380,12 @@ private int Quiesce(IBoard board, int α, int β, int sign, int depth)

α = Math.Max(α, standPat);

selDepth = Math.Max(selDepth, depth);
selDepth = Math.Max(selDepth, board.Counters.Ply - Board.Counters.Ply);

foreach (var newBoard in SortBords(board.PlayLegalMoves(true)))
foreach (var newBoard in SortBoards(board.PlayLegalMoves(true)))
{
nodeCount++;
var score = -Quiesce(newBoard, -β, -α, -sign, depth + 1);
var score = -Quiesce(newBoard, -β, -α, -sign);
if (score >= β)
{
return β;
Expand All @@ -406,17 +403,15 @@ private int EvaluateBoard(IBoard board, int depth, int α, int β, int sign)
return 0;
}

var α0 = α;

if (depth == maxDepth)
{
return Quiesce(board, α, β, sign, maxDepth);
return Quiesce(board, α, β, sign);
}

var transpositionIndex = board.Hash % transpositionTableSize;
var cachedEntry = transpositionTable[transpositionIndex];

if (cachedEntry.EntryType != Enum.EntryType.None && cachedEntry.Hash == board.Hash && cachedEntry.Depth >= maxDepth - depth)
if (cachedEntry.Hash == board.Hash && cachedEntry.Depth >= maxDepth - depth)
{
switch (cachedEntry.EntryType)
{
Expand All @@ -436,33 +431,41 @@ private int EvaluateBoard(IBoard board, int depth, int α, int β, int sign)
}
}

var legalmoves = board.PlayLegalMoves();

if (legalmoves.Count == 0)
{
return board.IsChecked ? -Scoring.MateScore : Scoring.DrawScore;
}

Move? bestMove = null;
var bestScore = -Scoring.MoveMaximumScore;
var α0 = α;
var currentMoveNumber = 0;

foreach (var newBoard in SortBords(board.PlayLegalMoves(), cachedEntry.Move))
foreach (var newBoard in SortBoards(legalmoves, cachedEntry.Move))
{
nodeCount++;

if (depth == 0)
{
SendCurrentMove(newBoard);
SendCurrentMove(newBoard, ++currentMoveNumber);
}

var score = -EvaluateBoard(newBoard, depth + 1, -β, -α, -sign);
var evaluation = -EvaluateBoard(newBoard, depth + 1, -β, -α, -sign);

if (repetitionTable.GetValueOrDefault(newBoard.Hash) >= 2)
{
// A draw be repetition may be forced by either player.
score = Scoring.DrawScore;
// TODO: This does not take into account moves made in the current evaluated line.
evaluation = Scoring.DrawScore;
}

if (score > bestScore)
if (evaluation > α)
{
bestMove = newBoard.Counters.LastMove;
bestScore = score;
α = evaluation;
}

α = Math.Max(α, bestScore);
if (α >= β)
{
if (newBoard.Counters.Capture == Piece.None)
Expand All @@ -473,16 +476,12 @@ private int EvaluateBoard(IBoard board, int depth, int α, int β, int sign)
}
}

if (bestMove == null)
{
bestScore = board.IsChecked ? -Scoring.MateScore + depth : Scoring.DrawScore;
}
else if (transpositionTable[transpositionIndex].Depth <= maxDepth - depth)
if (transpositionTable[transpositionIndex].Depth <= maxDepth - depth)
{
transpositionTable[transpositionIndex] = new TranspositionTableEntry(
bestScore <= α0 ? Enum.EntryType.UpperBound : bestScore >= β ? Enum.EntryType.LowerBound : Enum.EntryType.Exact,
bestMove,
bestScore,
α <= α0 ? Enum.EntryType.UpperBound : α >= β ? Enum.EntryType.LowerBound : Enum.EntryType.Exact,
bestMove!,
α,
board.Hash,
maxDepth - depth
);
Expand All @@ -493,7 +492,7 @@ private int EvaluateBoard(IBoard board, int depth, int α, int β, int sign)
evaluatedBestMove = bestMove;
}

return bestScore;
return α;
}

public void Stop() => timeIsUp = true;
Expand Down
4 changes: 2 additions & 2 deletions Engine/TranspositionTableEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ namespace SicTransit.Woodpusher.Engine
{
internal readonly struct TranspositionTableEntry
{
public Move? Move { get; }
public Move Move { get; }
public int Score { get; }
public ulong Hash { get; }
public int Depth { get; }
public EntryType EntryType { get; }

public TranspositionTableEntry(EntryType entryType, Move? move, int score, ulong hash, int depth)
public TranspositionTableEntry(EntryType entryType, Move move, int score, ulong hash, int depth)
{
Move = move;
Score = score;
Expand Down
Loading

0 comments on commit ee94aff

Please sign in to comment.