Skip to content

Commit

Permalink
bugfixes + performance; improved scoring; perft
Browse files Browse the repository at this point in the history
* fixed possible bug in PV calc
* implemented "go perft <depth>" command
* improved scoring
* code review
* fixed PGN parsing with simple pawn move + promotion
  • Loading branch information
sictransit authored Nov 18, 2022
1 parent 3dddbd1 commit bf50fdf
Show file tree
Hide file tree
Showing 30 changed files with 483 additions and 272 deletions.
113 changes: 71 additions & 42 deletions Common/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,46 @@ public int Score
{
var phase = white.Phase + black.Phase;

var whiteEvaluation = GetPieces(Piece.White).Sum(p => internals.Scoring.EvaluatePiece(p, phase));
var blackEvaluation = GetPieces(Piece.None).Sum(p => internals.Scoring.EvaluatePiece(p, phase));
var score = 0;

//whiteEvaluation += GetPieces(Pieces.White, Pieces.Pawn).Count(IsPassedPawn) * Scoring.PawnValue / 2;
//blackEvaluation += GetPieces(Pieces.Black, Pieces.Pawn).Count(IsPassedPawn) * Scoring.PawnValue / 2;
foreach (var piece in GetPieces())
{
var evaluation = internals.Scoring.EvaluatePiece(piece, phase);

//var attackers = GetAttackers(piece.GetMask(), piece.GetColor()).Count();
//var defenders = GetAttackers(piece.GetMask(), piece.GetColor().OpponentColor()).Count();

//if (attackers > defenders)
//{
// evaluation /= 2;
//}

switch (piece.GetPieceType())
{
case Piece.Pawn:
if (IsPassedPawn(piece))
{
evaluation *= 2;
}
break;
case Piece.Knight:
break;
case Piece.Bishop:
break;
case Piece.Rook:
break;
case Piece.Queen:
break;
case Piece.King:
break;
default:
break;
}

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

return whiteEvaluation - blackEvaluation;
return score;
}
}

Expand Down Expand Up @@ -225,7 +258,7 @@ private Board Play(Move move)

private Bitboard GetBitboard(Piece color) => color.Is(Piece.White) ? white : black;

private ulong FindKing(Piece color) => GetBitboard(color).King;
private Piece FindKing(Piece color) => Piece.King | color.SetMask(GetBitboard(color).King);

public IEnumerable<Piece> GetPieces() => GetPieces(Piece.White).Concat(GetPieces(Piece.None));

Expand All @@ -235,59 +268,57 @@ private Board Play(Move move)

private IEnumerable<Piece> GetPieces(Piece color, Piece type, ulong mask) => GetBitboard(color).GetPieces(type, mask);

public IEnumerable<Piece> GetAttackers(ulong target, Piece color)
public IEnumerable<Piece> GetAttackers(Piece piece)
{
var threatMask = internals.Attacks.GetThreatMask(color.SetMask(target));
var threats = internals.Attacks.GetThreatMask(piece);

var target = piece.GetMask();

var opponentColor = color.OpponentColor();
var opponentColor = piece.OpponentColor();

foreach (var queen in GetPieces(opponentColor, Piece.Queen, threatMask.QueenMask))
if (!IsOccupied(threats.Any, opponentColor))
{
if (!IsOccupied(internals.Moves.GetTravelMask(queen.GetMask(), target)))
{
yield return queen;
}
yield break;
}

foreach (var rook in GetPieces(opponentColor, Piece.Rook, threatMask.RookMask))
foreach (var pawn in GetPieces(opponentColor, Piece.Pawn, threats.Pawn))
{
if (!IsOccupied(internals.Moves.GetTravelMask(rook.GetMask(), target)))
{
yield return rook;
}
yield return pawn;
}

foreach (var bishop in GetPieces(opponentColor, Piece.Bishop, threatMask.BishopMask))
foreach (var knight in GetPieces(opponentColor, Piece.Knight, threats.Knight))
{
if (!IsOccupied(internals.Moves.GetTravelMask(bishop.GetMask(), target)))
{
yield return bishop;
}
yield return knight;
}

foreach (var pawn in GetPieces(opponentColor, Piece.Pawn, threatMask.PawnMask))
foreach (var queen in GetPieces(opponentColor, Piece.Queen, threats.Queen))
{
if (!IsOccupied(internals.Moves.GetTravelMask(pawn.GetMask(), target)))
if (!IsOccupied(internals.Moves.GetTravelMask(queen.GetMask(), target)))
{
yield return pawn;
yield return queen;
}
}

foreach (var knight in GetPieces(opponentColor, Piece.Knight, threatMask.KnightMask))
foreach (var rook in GetPieces(opponentColor, Piece.Rook, threats.Rook))
{
if (!IsOccupied(internals.Moves.GetTravelMask(knight.GetMask(), target)))
if (!IsOccupied(internals.Moves.GetTravelMask(rook.GetMask(), target)))
{
yield return knight;
yield return rook;
}
}

foreach (var king in GetPieces(opponentColor, Piece.King, threatMask.KingMask))
foreach (var bishop in GetPieces(opponentColor, Piece.Bishop, threats.Bishop))
{
if (!IsOccupied(internals.Moves.GetTravelMask(king.GetMask(), target)))
if (!IsOccupied(internals.Moves.GetTravelMask(bishop.GetMask(), target)))
{
yield return king;
yield return bishop;
}
}

foreach (var king in GetPieces(opponentColor, Piece.King, threats.King))
{
yield return king;
}
}

public IEnumerable<Move> GetLegalMoves()
Expand Down Expand Up @@ -371,14 +402,14 @@ private bool ValidateMove(Move move)
return false;
}

// castling from or into check
if (IsAttacked(move.Piece.GetMask(), move.Piece.GetColor()) || IsAttacked(move.CastlingCheckMask, move.Piece.GetColor()) || IsAttacked(move.Target, move.Piece.GetColor()))
// castling path is blocked
if (IsOccupied(move.CastlingEmptySquaresMask | move.CastlingCheckMask))
{
return false;
}

// castling path is blocked
if (IsOccupied(move.CastlingEmptySquaresMask) || IsOccupied(move.CastlingCheckMask))
// castling from or into check
if (IsAttacked(move.Piece) || IsAttacked(move.Piece.SetMask(move.CastlingCheckMask)) || IsAttacked(move.Piece.SetMask(move.Target)))
{
return false;
}
Expand All @@ -391,13 +422,11 @@ private bool IsMovingIntoCheck(Move move)
{
var testBoard = Play(move);

var opponentColor = testBoard.ActiveColor.OpponentColor();

return testBoard.IsAttacked(testBoard.FindKing(opponentColor), opponentColor);
return testBoard.IsAttacked(testBoard.FindKing(ActiveColor));
}

public bool IsChecked => IsAttacked(FindKing(ActiveColor), ActiveColor);
public bool IsChecked => IsAttacked(FindKing(ActiveColor));

private bool IsAttacked(ulong square, Piece color) => GetAttackers(square, color).Any();
private bool IsAttacked(Piece piece) => GetAttackers(piece).Any();
}
}
2 changes: 1 addition & 1 deletion Common/Extensions/BoardExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static string PrettyPrint(this IBoard b)

var piece = pieces.SingleOrDefault(p => p.GetSquare().Equals(square));

var c = piece != default ? piece.ToAlgebraicNotation() : ' ';
var c = piece != default ? piece.ToAlgebraicNotation() : '·';

sb.Append($"{c} ");
}
Expand Down
2 changes: 1 addition & 1 deletion Common/Interfaces/IBoard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public interface IBoard

IBoard SetPiece(Piece piece);

IEnumerable<Piece> GetAttackers(ulong square, Piece color);
IEnumerable<Piece> GetAttackers(Piece piece);

ulong Hash { get; }

Expand Down
2 changes: 2 additions & 0 deletions Common/Interfaces/IEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ public interface IEngine
void Position(string fen, IEnumerable<AlgebraicMove>? algebraicMoves = null);

AlgebraicMove FindBestMove(int timeLimit = 1000, Action<string>? infoCallback = null);

void Perft(int depth, Action<string> infoCallback);
}
}
71 changes: 37 additions & 34 deletions Common/Lookup/Attacks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,60 +7,63 @@ namespace SicTransit.Woodpusher.Common.Lookup
{
public class Attacks
{
private readonly Dictionary<Piece, ThreatMask> threatMasks = new();
private readonly Dictionary<Piece, ThreatMasks> threatMasks = new();

public Attacks()
{
Initialize();
}

public ThreatMask GetThreatMask(Piece piece) => threatMasks[piece];
public ThreatMasks GetThreatMask(Piece piece) => threatMasks[piece];

private void Initialize()
{
foreach (var color in PieceExtensions.Colors)
foreach (var pieceType in PieceExtensions.Types)
{
foreach (var square in SquareExtensions.AllSquares)
foreach (var color in PieceExtensions.Colors)
{
var queenMask = QueenMovement.GetTargetVectors((Piece.Queen | color).SetSquare(square)).SelectMany(v => v).Aggregate(0ul, (a, b) => a | b.GetTarget().ToMask());
var bishopMask = BishopMovement.GetTargetVectors((Piece.Queen | color).SetSquare(square)).SelectMany(v => v).Aggregate(0ul, (a, b) => a | b.GetTarget().ToMask());
var knightMask = KnightMovement.GetTargetVectors((Piece.Queen | color).SetSquare(square)).SelectMany(v => v).Aggregate(0ul, (a, b) => a | b.GetTarget().ToMask());
var rookMask = RookMovement.GetTargetVectors((Piece.Queen | color).SetSquare(square)).SelectMany(v => v).Aggregate(0ul, (a, b) => a | b.GetTarget().ToMask());
var kingMask = KingMovement.GetTargetVectors((Piece.Queen | color.OpponentColor()).SetSquare(square)).SelectMany(v => v).Where(v => !v.Flags.HasFlag(SpecialMove.CastleQueen) && !v.Flags.HasFlag(SpecialMove.CastleKing)).Aggregate(0ul, (a, b) => a | b.GetTarget().ToMask());
foreach (var square in SquareExtensions.AllSquares)
{
var queenMask = QueenMovement.GetTargetVectors(color.SetSquare(square)).SelectMany(v => v).Aggregate(0ul, (a, b) => a | b.GetTarget().ToMask());
var bishopMask = BishopMovement.GetTargetVectors(color.SetSquare(square)).SelectMany(v => v).Aggregate(0ul, (a, b) => a | b.GetTarget().ToMask());
var knightMask = KnightMovement.GetTargetVectors(color.SetSquare(square)).SelectMany(v => v).Aggregate(0ul, (a, b) => a | b.GetTarget().ToMask());
var rookMask = RookMovement.GetTargetVectors(color.SetSquare(square)).SelectMany(v => v).Aggregate(0ul, (a, b) => a | b.GetTarget().ToMask());
var kingMask = KingMovement.GetTargetVectors(color.OpponentColor().SetSquare(square)).SelectMany(v => v).Where(v => !v.Flags.HasFlag(SpecialMove.CastleQueen) && !v.Flags.HasFlag(SpecialMove.CastleKing)).Aggregate(0ul, (a, b) => a | b.GetTarget().ToMask());

var pawnMask = 0ul;
var pawnMask = 0ul;

switch (color)
{
case Piece.White when square.Rank < 6:
{
if (Square.TryCreate(square.File - 1, square.Rank + 1, out var upLeft))
{
pawnMask |= upLeft.ToMask();
}
if (Square.TryCreate(square.File + 1, square.Rank + 1, out var upRight))
switch (color)
{
case Piece.White when square.Rank < 6:
{
pawnMask |= upRight.ToMask();
}
if (Square.TryCreate(square.File - 1, square.Rank + 1, out var upLeft))
{
pawnMask |= upLeft.ToMask();
}
if (Square.TryCreate(square.File + 1, square.Rank + 1, out var upRight))
{
pawnMask |= upRight.ToMask();
}

break;
}
case Piece.None when square.Rank > 1:
{
if (Square.TryCreate(square.File - 1, square.Rank - 1, out var downLeft))
{
pawnMask |= downLeft.ToMask();
break;
}
if (Square.TryCreate(square.File + 1, square.Rank - 1, out var downRight))
case Piece.None when square.Rank > 1:
{
pawnMask |= downRight.ToMask();
if (Square.TryCreate(square.File - 1, square.Rank - 1, out var downLeft))
{
pawnMask |= downLeft.ToMask();
}
if (Square.TryCreate(square.File + 1, square.Rank - 1, out var downRight))
{
pawnMask |= downRight.ToMask();
}

break;
}
}

break;
}
threatMasks.Add(pieceType | color.SetSquare(square), new ThreatMasks(pawnMask, rookMask, knightMask, bishopMask, queenMask, kingMask));
}

threatMasks.Add(color.SetSquare(square), new ThreatMask(pawnMask, rookMask, knightMask, bishopMask, queenMask, kingMask));
}
}
}
Expand Down
22 changes: 0 additions & 22 deletions Common/Lookup/ThreatMask.cs

This file was deleted.

24 changes: 24 additions & 0 deletions Common/Lookup/ThreatMasks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace SicTransit.Woodpusher.Common.Lookup
{
public class ThreatMasks
{
public ulong Pawn { get; }
public ulong Rook { get; }
public ulong Knight { get; }
public ulong Bishop { get; }
public ulong Queen { get; }
public ulong King { get; }
public ulong Any { get; }

public ThreatMasks(ulong pawn, ulong rook, ulong knight, ulong bishop, ulong queen, ulong king)
{
Pawn = pawn;
Rook = rook;
Knight = knight;
Bishop = bishop;
Queen = queen;
King = king;
Any = pawn | rook | knight | bishop | queen | king;
}
}
}
10 changes: 5 additions & 5 deletions Common/Lookup/Zobrist.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace SicTransit.Woodpusher.Common.Lookup
public class Zobrist
{
// Credit: https://www.random.org/
private static readonly byte[] randomness = new byte[]{
private static readonly byte[] Randomness = {
0x2c, 0x92, 0x39, 0xb1, 0xba, 0x85, 0x95, 0xe8, 0x76, 0x8f, 0xfa, 0xfa, 0xbb, 0xa8, 0xde, 0x2c,
0x90, 0x92, 0x00, 0x0a, 0x53, 0xc4, 0xfe, 0x78, 0x5a, 0x74, 0x18, 0x10, 0x2f, 0xde, 0x28, 0x77,
0x29, 0x38, 0xa6, 0xdf, 0x7e, 0x79, 0x52, 0x05, 0xfc, 0x8c, 0xcb, 0xfb, 0x62, 0x3c, 0xcc, 0x41,
Expand Down Expand Up @@ -413,7 +413,7 @@ public class Zobrist
0x90, 0xde, 0xb5, 0x9b, 0x50, 0xc1, 0x6d, 0x81
};

private int randomCounter = 0;
private int randomCounter;

private readonly Dictionary<Piece, ulong> pieceHashes = new();
private readonly Dictionary<Castlings, ulong> castlingsHashes = new();
Expand Down Expand Up @@ -458,7 +458,7 @@ private void InitializePieces()

private void InitializeCastlings()
{
for (int i = 0; i < 16; i++)
for (var i = 0; i < 16; i++)
{
castlingsHashes.Add((Castlings)i, GenerateRandomNumber());
}
Expand Down Expand Up @@ -489,12 +489,12 @@ private void InitializeActiveColors()

private ulong GenerateRandomNumber()
{
if (randomCounter * 8 >= randomness.Length)
if (randomCounter * 8 >= Randomness.Length)
{
throw new InvalidOperationException("Out of randomness!");
}

return BitConverter.ToUInt64(randomness, 8 * randomCounter++);
return BitConverter.ToUInt64(Randomness, 8 * randomCounter++);
}
}
}
Loading

0 comments on commit bf50fdf

Please sign in to comment.