Skip to content

Commit

Permalink
fixed bug causing white to play on black's time limit
Browse files Browse the repository at this point in the history
  • Loading branch information
sictransit authored Nov 2, 2022
1 parent c5bde21 commit 2f259a3
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 108 deletions.
7 changes: 6 additions & 1 deletion Common/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public IBoard PlayMove(Move move)
private Board Play(Move move)
{
var hash = Hash;
var quiet = true;
var opponentBitboard = GetBitboard(ActiveColor.OpponentColor());

var targetPiece = opponentBitboard.Peek(move.Target);
Expand All @@ -99,13 +100,17 @@ private Board Play(Move move)
opponentBitboard = opponentBitboard.Remove(targetPiece);

hash ^= internals.Zobrist.GetPieceHash(targetPiece | ActiveColor.OpponentColor());

quiet = false;
}
else if (move.Flags.HasFlag(SpecialMove.EnPassant))
{
var targetPawn = Piece.Pawn.SetMask(move.EnPassantMask);
opponentBitboard = opponentBitboard.Remove(targetPawn);

hash ^= internals.Zobrist.GetPieceHash(targetPawn | ActiveColor.OpponentColor());

quiet = false;
}

var activeBitboard = GetBitboard(ActiveColor).Move(move.Piece, move.Target);
Expand Down Expand Up @@ -215,7 +220,7 @@ private Board Play(Move move)
var halfmoveClock = move.Piece.Is(Piece.Pawn) || targetPiece.GetPieceType() != Piece.None ? 0 : Counters.HalfmoveClock + 1;

var fullmoveCounter = Counters.FullmoveNumber + (ActiveColor.Is(Piece.White) ? 0 : 1);
var counters = new Counters(ActiveColor.OpponentColor(), castlings, move.EnPassantTarget, halfmoveClock, fullmoveCounter);
var counters = new Counters(ActiveColor.OpponentColor(), castlings, move.EnPassantTarget, halfmoveClock, fullmoveCounter, quiet);

hash ^= internals.Zobrist.GetMaskHash(Counters.EnPassantTarget) ^ internals.Zobrist.GetMaskHash(counters.EnPassantTarget);
hash ^= internals.Zobrist.GetPieceHash(Counters.ActiveColor) ^ internals.Zobrist.GetPieceHash(counters.ActiveColor);
Expand Down
3 changes: 0 additions & 3 deletions Common/Lookup/Scoring.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ public class Scoring
private readonly Dictionary<Piece, int> middleGameEvaluations = new();
private readonly Dictionary<Piece, int> endGameEvaluations = new();

public const int MateScore = 1000000;
public const int DrawScore = 0;

private static readonly int[] PawnMiddleGameModifiers =
{
0, 0, 0, 0, 0, 0, 0, 0,
Expand Down
2 changes: 1 addition & 1 deletion Common/Parsing/ForsythEdwardsNotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static IBoard Parse(string fen)

var fullmoveNumber = parts.Length > 5 ? ParseFullmoveNumber(parts[5]) : 0;

var counters = new Counters(activeColor, castling, enPassantTarget?.ToMask() ?? 0, halfmoveClock, fullmoveNumber);
var counters = new Counters(activeColor, castling, enPassantTarget?.ToMask() ?? 0, halfmoveClock, fullmoveNumber, true);

return new Board(white, black, counters);
}
Expand Down
13 changes: 13 additions & 0 deletions Engine/Declarations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace SicTransit.Woodpusher.Engine
{
internal static class Declarations
{
public const int MaxDepth = 128;

public const int MateScore = 1000000;
public const int DrawScore = 0;

public const int MoveMaximumScore = MateScore * 2;
public const int BoardMaximumScore = MoveMaximumScore * 2;
}
}
22 changes: 0 additions & 22 deletions Engine/Extensions/NodeExtensions.cs

This file was deleted.

36 changes: 26 additions & 10 deletions Engine/Node.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
using SicTransit.Woodpusher.Common.Lookup;
using SicTransit.Woodpusher.Model;
using SicTransit.Woodpusher.Model;
using SicTransit.Woodpusher.Model.Enums;
using SicTransit.Woodpusher.Model.Extensions;

namespace SicTransit.Woodpusher.Engine
{
public class Node
{
public Node(Move move, int maxDepth)
public Node(Move move)
{
Sign = move.Piece.Is(Piece.White) ? 1 : -1;
Score = -Sign * Scoring.MateScore * 2;
Line = new Move[maxDepth];
Line[0] = move;
Score = -Sign * Declarations.MoveMaximumScore;
MaxDepth = 2;
Move = move;
}

public Move[] Line { get; }
public int MaxDepth { get; set; }

public IEnumerable<Move> GetLine() => Line.TakeWhile(m => m != null);

public Move Move => Line[0];
public Move Move { get; }

public int Score { get; set; }

Expand All @@ -29,6 +26,25 @@ public Node(Move move, int maxDepth)

public int AbsoluteScore => Score * Sign;

public int? MateIn
{
get
{
var mateIn = Math.Abs(Math.Abs(Score) - Declarations.MateScore);

mateIn += Sign == -1 ? 1 : 0;

if (mateIn <= Declarations.MaxDepth)
{
var mateSign = AbsoluteScore > 0 ? 1 : -1;

return mateSign * mateIn / 2;
}

return null;
}
}

public override string ToString()
{
return $"{Move} {Score}";
Expand Down
109 changes: 44 additions & 65 deletions Engine/Patzer.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using Serilog;
using SicTransit.Woodpusher.Common.Exceptions;
using SicTransit.Woodpusher.Common.Interfaces;
using SicTransit.Woodpusher.Common.Lookup;
using SicTransit.Woodpusher.Common.Parsing;
using SicTransit.Woodpusher.Engine.Extensions;
using SicTransit.Woodpusher.Model;
using SicTransit.Woodpusher.Model.Enums;
using SicTransit.Woodpusher.Model.Extensions;
Expand All @@ -21,8 +19,6 @@ public class Patzer : IEngine

private readonly Stopwatch stopwatch = new();

private const int MaxDepth = 16;

private readonly IDictionary<string, int> repetitions = new Dictionary<string, int>();

public Patzer()
Expand Down Expand Up @@ -80,7 +76,7 @@ public AlgebraicMove FindBestMove(int timeLimit = 1000, Action<string>? infoCall

var openingMoves = Board.GetOpeningBookMoves();

var nodes = (openingMoves.Any() ? openingMoves : Board.GetLegalMoves()).Select(m => new Node(m, MaxDepth)).ToList();
var nodes = (openingMoves.Any() ? openingMoves : Board.GetLegalMoves()).Select(m => new Node(m)).ToList();

if (!nodes.Any())
{
Expand All @@ -97,68 +93,60 @@ public AlgebraicMove FindBestMove(int timeLimit = 1000, Action<string>? infoCall
MaxDegreeOfParallelism = Debugger.IsAttached ? 1 : -1
};

var maxDepth = 1;

while (!cancellationTokenSource.IsCancellationRequested && nodes.Count > 1 && maxDepth < MaxDepth)
while (!cancellationTokenSource.IsCancellationRequested && nodes.Count > 1)
{
if (nodes.Any(n => n.MateIn() > 0))
if (nodes.Any(n => n.MateIn > 0))
{
break;
}

foreach (var chunk in nodes.Where(n => !n.MateIn().HasValue).OrderByDescending(e => e.AbsoluteScore).Chunk(Environment.ProcessorCount))
var tasks = nodes.Where(n => !n.MateIn.HasValue).Select(node => Task.Run(() =>
{
try
{
Parallel.ForEach(chunk, parallelOptions, node =>
var score = EvaluateBoard(Board.PlayMove(node.Move), node, 1, -Declarations.BoardMaximumScore, Declarations.BoardMaximumScore, cancellationToken);

if (!cancellationToken.IsCancellationRequested)
{
try
node.Score = score;

if (infoCallback != null)
{
var score = EvaluateBoard(Board.PlayMove(node.Move), node, maxDepth, 0, -Scoring.MateScore * 4, Scoring.MateScore * 4, cancellationToken);

if (!cancellationToken.IsCancellationRequested)
{
node.Score = score;

if (infoCallback != null)
{
SendAnalysisInfo(infoCallback, maxDepth + 1, nodes.Sum(n => n.Count), node, stopwatch.ElapsedMilliseconds);
}
}
else
{
if (infoCallback != null)
{
SendDebugInfo(infoCallback, $"aborting {node.Move.ToAlgebraicMoveNotation()} @ depth {maxDepth + 1}");
}

Log.Debug($"Discarding evaluation due to timeout: {node.Move} @ depth {maxDepth}");
}
SendAnalysisInfo(infoCallback, node.MaxDepth, nodes.Sum(n => n.Count), node, stopwatch.ElapsedMilliseconds);
}
catch (OperationCanceledException)
}
else
{
if (infoCallback != null)
{
throw;
SendDebugInfo(infoCallback, $"aborting {node.Move.ToAlgebraicMoveNotation()} @ depth {node.MaxDepth}");
}
catch (Exception ex)
{
if (infoCallback != null)
{
SendExceptionInfo(infoCallback, ex);
}

throw;
}
});
Log.Debug($"Discarding evaluation due to timeout: {node.Move} @ depth {node.MaxDepth}");
}
}
catch (OperationCanceledException)
{
break;
throw;
}
}
catch (Exception ex)
{
if (infoCallback != null)
{
SendExceptionInfo(infoCallback, ex);
}

throw;
}

})).ToArray();

Task.WaitAll(tasks);

maxDepth += 2;
nodes.ForEach(n => n.MaxDepth += 2);
}


// Set node score to zero for threefold repetition moves.
UpdateForThreefoldRepetition(nodes);

Expand Down Expand Up @@ -213,17 +201,16 @@ private static void SendExceptionInfo(Action<string> callback, Exception excepti

private static void SendAnalysisInfo(Action<string> callback, int depth, long nodes, Node node, long time)
{
var preview = string.Join(" ", node.GetLine().Select(m => m.ToAlgebraicMoveNotation()));
var preview = node.Move.ToAlgebraicMoveNotation();

var mateIn = node.MateIn();
var score = mateIn.HasValue ? $"mate {mateIn}" : $"cp {node.AbsoluteScore}";
var score = node.MateIn.HasValue ? $"mate {node.MateIn.Value}" : $"cp {node.AbsoluteScore}";

var nodesPerSecond = time == 0 ? 0 : nodes * 1000 / time;

SendInfo(callback, $"depth {depth} nodes {nodes} score {score} time {time} pv {preview} nps {nodesPerSecond}");
}

private int EvaluateBoard(IBoard board, Node node, int maxDepth, int depth, int alpha, int beta, CancellationToken cancellationToken)
private int EvaluateBoard(IBoard board, Node node, int depth, int alpha, int beta, CancellationToken cancellationToken)
{
var moves = board.GetLegalMoves();

Expand All @@ -232,31 +219,27 @@ private int EvaluateBoard(IBoard board, Node node, int maxDepth, int depth, int
// ReSharper disable once PossibleMultipleEnumeration
if (!moves.Any())
{
var mateScore = maximizing ? -Scoring.MateScore + depth + 1 : Scoring.MateScore - (depth + 1);
return board.IsChecked ? mateScore : Scoring.DrawScore;
var mateScore = maximizing ? -Declarations.MateScore + depth + 1 : Declarations.MateScore - (depth + 1);
return board.IsChecked ? mateScore : Declarations.DrawScore;
}

if (depth == maxDepth || cancellationToken.IsCancellationRequested)
if (depth == node.MaxDepth || cancellationToken.IsCancellationRequested)
{
return board.Score;
}

var bestScore = maximizing ? -Scoring.MateScore * 2 : Scoring.MateScore * 2;
var bestScore = maximizing ? -Declarations.MoveMaximumScore : Declarations.MoveMaximumScore;

// ReSharper disable once PossibleMultipleEnumeration
foreach (var move in moves)
{
node.Count++;

var score = EvaluateBoard(board.PlayMove(move), node, maxDepth, depth + 1, alpha, beta, cancellationToken);
var score = EvaluateBoard(board.PlayMove(move), node, depth + 1, alpha, beta, cancellationToken);

if (maximizing)
{
if (score > bestScore)
{
node.Line[depth + 1] = move;
bestScore = score;
}
bestScore = Math.Max(score, bestScore);

if (bestScore >= beta)
{
Expand All @@ -267,11 +250,7 @@ private int EvaluateBoard(IBoard board, Node node, int maxDepth, int depth, int
}
else
{
if (score < bestScore)
{
node.Line[depth + 1] = move;
bestScore = score;
}
bestScore = Math.Min(score, bestScore);

if (bestScore <= alpha)
{
Expand Down
4 changes: 2 additions & 2 deletions EngineTests/PatzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ public void PromotionIsNotTheBestMoveAfterAll()

patzer.Position("6K1/8/1k6/8/6b1/8/6p1/8 b - - 3 156");

var bestmove = patzer.FindBestMove(20000);
var bestmove = patzer.FindBestMove(10000);

Assert.IsTrue(bestmove.Notation.Length == 4);
}
Expand Down Expand Up @@ -215,7 +215,7 @@ void Callback(string s)

Assert.IsNotNull(move);

Assert.IsTrue(infos.Any(i => i.Contains("mate 3")));
Assert.IsTrue(infos.Any(i => i.Contains("mate 4")));
}

[TestMethod()]
Expand Down
7 changes: 5 additions & 2 deletions Model/Counters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ public class Counters

public int FullmoveNumber { get; }

public Counters(Piece activeColor, Castlings castlings, ulong enPassantTarget, int halfmoveClock, int fullmoveNumber)
public bool Quiet { get; }

public Counters(Piece activeColor, Castlings castlings, ulong enPassantTarget, int halfmoveClock, int fullmoveNumber, bool quiet)
{
ActiveColor = activeColor;
Castlings = castlings;
EnPassantTarget = enPassantTarget;
HalfmoveClock = halfmoveClock;
FullmoveNumber = fullmoveNumber;
Quiet = quiet;
}

public static Counters Default => new(Piece.White, Castlings.WhiteKingside | Castlings.WhiteQueenside | Castlings.BlackKingside | Castlings.BlackQueenside, 0, 0, 0);
public static Counters Default => new(Piece.White, Castlings.WhiteKingside | Castlings.WhiteQueenside | Castlings.BlackKingside | Castlings.BlackQueenside, 0, 0, 0, true);
}
}
Loading

0 comments on commit 2f259a3

Please sign in to comment.