Skip to content

Commit

Permalink
major rewrite + bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
sictransit authored Nov 16, 2024
1 parent 8ae346a commit e9b6748
Show file tree
Hide file tree
Showing 33 changed files with 441 additions and 17,486 deletions.
116 changes: 48 additions & 68 deletions Common/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ public class Board : IBoard
{
private readonly Bitboard white;
private readonly Bitboard black;
private readonly BoardInternals internals;
private readonly Bitboard activeBoard;
private readonly Bitboard opponentBoard;
private readonly bool whiteIsPlaying;
private readonly BoardInternals internals;

public Counters Counters { get; }

Expand All @@ -25,6 +28,10 @@ private Board(Bitboard white, Bitboard black, Counters counters, BoardInternals
this.white = white;
this.black = black;

whiteIsPlaying = counters.ActiveColor.Is(Piece.White);
activeBoard = whiteIsPlaying ? white : black;
opponentBoard = whiteIsPlaying ? black : white;

Counters = counters;
this.internals = internals;
Hash = hash == BoardInternals.InvalidHash ? internals.Zobrist.GetHash(this) : hash;
Expand Down Expand Up @@ -52,64 +59,31 @@ public int Score

var score = 0;

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

if (piece.GetPieceType() != Piece.King)
{
// TODO: Good idea, but too slow. Node pruning will be needed.
//var attackers = GetPiecesInRange(piece, piece.OpponentColor()).Count();
//if (attackers > 0)
//{
// var defenders = GetPiecesInRange(piece, piece & Piece.White).Count();

// if (attackers > defenders)
// {
// // A piece not defended will score half.
// evaluation /= 2;
// }
//}

if (piece.GetPieceType() == Piece.Pawn && IsPassedPawn(piece))
{
evaluation *= 2;
}
}
score += internals.Scoring.EvaluatePiece(piece, phase);
}

score += piece.Is(Piece.White) ? evaluation : -evaluation;
foreach (var piece in black.GetPieces())
{
score -= internals.Scoring.EvaluatePiece(piece, phase);
}

return score;
}
}

public bool IsPassedPawn(Piece piece) => (internals.Moves.GetPassedPawnMask(piece) & GetBitboard(piece.OpponentColor()).Pawn) == 0;

public IBoard SetPiece(Piece piece) => piece.Is(Piece.White)
? new Board(white.Toggle(piece), black, Counters, internals, BoardInternals.InvalidHash)
: (IBoard)new Board(white, black.Toggle(piece), Counters, internals, BoardInternals.InvalidHash);

public IBoard Play(Move move)
{
var hash = Hash;

var whitePlaying = ActiveColor.Is(Piece.White);

var opponentBitboard = whitePlaying ? black : white;

var capture = opponentBitboard.Peek(move.EnPassantTarget == 0 ? move.Target : move.EnPassantMask);
var newHash = Hash;

if (capture != Piece.None)
{
opponentBitboard = opponentBitboard.Toggle(capture);

hash ^= internals.Zobrist.GetPieceHash(capture);
}

var activeBitboard = GetBitboard(ActiveColor).Toggle(move.Piece, move.Target);
var newActiveBoard = activeBoard.Toggle(move.Piece, move.Target);

hash ^= internals.Zobrist.GetPieceHash(move.Piece) ^ internals.Zobrist.GetPieceHash(move.Piece.SetMask(move.Target));
newHash ^= internals.Zobrist.GetPieceHash(move.Piece) ^ internals.Zobrist.GetPieceHash(move.Piece.SetMask(move.Target));

var castlings = Counters.Castlings;

Expand All @@ -119,7 +93,7 @@ public IBoard Play(Move move)
{
(ulong from, ulong to) castling = default;

if (whitePlaying)
if (whiteIsPlaying)
{
castlings &= ~(Castlings.WhiteKingside | Castlings.WhiteQueenside);

Expand Down Expand Up @@ -150,14 +124,14 @@ public IBoard Play(Move move)
{
var rook = Piece.Rook | ActiveColor.SetMask(castling.from);

activeBitboard = activeBitboard.Toggle(rook, castling.to);
newActiveBoard = newActiveBoard.Toggle(rook, castling.to);

hash ^= internals.Zobrist.GetPieceHash(rook) ^ internals.Zobrist.GetPieceHash(rook.SetMask(castling.to));
newHash ^= internals.Zobrist.GetPieceHash(rook) ^ internals.Zobrist.GetPieceHash(rook.SetMask(castling.to));
}
}
else
{
if (whitePlaying)
if (whiteIsPlaying)
{
if (move.Piece == BoardInternals.WhiteKingsideRook)
{
Expand Down Expand Up @@ -198,42 +172,52 @@ public IBoard Play(Move move)
}
}

hash ^= internals.Zobrist.GetCastlingsHash(Counters.Castlings) ^ internals.Zobrist.GetCastlingsHash(castlings);
newHash ^= internals.Zobrist.GetCastlingsHash(Counters.Castlings) ^ internals.Zobrist.GetCastlingsHash(castlings);
}

if (move.Flags.HasFlag(SpecialMove.PawnPromotes))
{
var promotedPiece = move.Piece.SetMask(move.Target);
var promotionPiece = (ActiveColor | move.PromotionType).SetMask(move.Target);
activeBitboard = activeBitboard.Toggle(promotedPiece).Toggle(promotionPiece);
newActiveBoard = newActiveBoard.Toggle(promotedPiece).Toggle(promotionPiece);

newHash ^= internals.Zobrist.GetPieceHash(promotedPiece) ^ internals.Zobrist.GetPieceHash(promotionPiece);
}

var capture = opponentBoard.Peek(move.EnPassantTarget == 0 ? move.Target : move.EnPassantMask);
var newOpponentBoard = opponentBoard;

if (capture != Piece.None)
{
newOpponentBoard = newOpponentBoard.Toggle(capture);

hash ^= internals.Zobrist.GetPieceHash(promotedPiece) ^ internals.Zobrist.GetPieceHash(promotionPiece);
newHash ^= internals.Zobrist.GetPieceHash(capture);
}

var counters = new Counters(
whitePlaying ? Piece.None : Piece.White,
whiteIsPlaying ? Piece.None : Piece.White,
castlings,
move.EnPassantTarget,
move.Piece.Is(Piece.Pawn) || capture.GetPieceType() != Piece.None ? 0 : Counters.HalfmoveClock + 1,
Counters.FullmoveNumber + (whitePlaying ? 0 : 1),
Counters.FullmoveNumber + (whiteIsPlaying ? 0 : 1),
move,
capture);

hash ^= internals.Zobrist.GetMaskHash(Counters.EnPassantTarget)
newHash ^= internals.Zobrist.GetMaskHash(Counters.EnPassantTarget)
^ internals.Zobrist.GetMaskHash(counters.EnPassantTarget)
^ internals.Zobrist.GetPieceHash(Counters.ActiveColor)
^ internals.Zobrist.GetPieceHash(counters.ActiveColor);

return whitePlaying
? new Board(activeBitboard, opponentBitboard, counters, internals, hash)
: new Board(opponentBitboard, activeBitboard, counters, internals, hash);
return whiteIsPlaying
? new Board(newActiveBoard, newOpponentBoard, counters, internals, newHash)
: new Board(newOpponentBoard, newActiveBoard, counters, internals, newHash);
}

private bool IsOccupied(ulong mask) => ((white.All | black.All) & mask) != 0;
private bool IsOccupied(ulong mask) => ((white.AllPieces | black.AllPieces) & mask) != 0;

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

public Piece FindKing(Piece color) => Piece.King | color.SetMask(GetBitboard(color).King);
public Piece FindKing(Piece color) => GetBitboard(color).GetKing();

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

Expand Down Expand Up @@ -303,7 +287,7 @@ public IEnumerable<Move> GetLegalMoves(Piece piece)

public IEnumerable<IBoard> PlayLegalMoves()
{
foreach (var piece in GetPieces(ActiveColor))
foreach (var piece in activeBoard.GetPieces())
{
foreach (var board in PlayLegalMoves(piece))
{
Expand All @@ -312,22 +296,18 @@ public IEnumerable<IBoard> PlayLegalMoves()
}
}

public IEnumerable<IBoard> PlayLegalMoves(Piece piece)
private IEnumerable<IBoard> PlayLegalMoves(Piece piece)
{
var whiteIsPlaying = ActiveColor.Is(Piece.White);
var friendlyBoard = whiteIsPlaying ? white : black;
var hostileBoard = whiteIsPlaying ? black : white;

foreach (var vector in internals.Moves.GetVectors(piece))
{
foreach (var move in vector)
{
if (friendlyBoard.IsOccupied(move.Target))
if (activeBoard.IsOccupied(move.Target))
{
break;
}

var hostileTarget = hostileBoard.Peek(move.Target);
var hostileTarget = opponentBoard.Peek(move.Target);

if (!ValidateMove(move, hostileTarget))
{
Expand Down Expand Up @@ -383,12 +363,12 @@ private bool ValidateMove(Move move, Piece hostileTarget)
}
else if (move.Piece.Is(Piece.King) && (move.Flags.HasFlag(SpecialMove.CastleQueen) || move.Flags.HasFlag(SpecialMove.CastleKing)))
{
if (move.Flags.HasFlag(SpecialMove.CastleQueen) && !Counters.Castlings.HasFlag(ActiveColor.Is(Piece.White) ? Castlings.WhiteQueenside : Castlings.BlackQueenside))
if (move.Flags.HasFlag(SpecialMove.CastleQueen) && !Counters.Castlings.HasFlag(whiteIsPlaying ? Castlings.WhiteQueenside : Castlings.BlackQueenside))
{
return false;
}

if (move.Flags.HasFlag(SpecialMove.CastleKing) && !Counters.Castlings.HasFlag(ActiveColor.Is(Piece.White) ? Castlings.WhiteKingside : Castlings.BlackKingside))
if (move.Flags.HasFlag(SpecialMove.CastleKing) && !Counters.Castlings.HasFlag(whiteIsPlaying ? Castlings.WhiteKingside : Castlings.BlackKingside))
{
return false;
}
Expand Down
9 changes: 6 additions & 3 deletions Common/Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="MessagePack" Version="2.5.187" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Trace" Version="4.0.0" />
Expand All @@ -20,7 +20,10 @@
</ItemGroup>

<ItemGroup>
<None Update="Resources\openings.json">
<None Update="Resources\openings.black.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\openings.white.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions Common/Interfaces/IBoard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ public interface IBoard

IBoard Play(Move move);

bool IsPassedPawn(Piece piece);

IEnumerable<IBoard> PlayLegalMoves();

IEnumerable<Move> GetLegalMoves();
Expand Down
33 changes: 0 additions & 33 deletions Common/Lookup/Moves.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ public class Moves
{
private readonly Dictionary<Piece, Move[][]> vectors = new();
private readonly Dictionary<ulong, ulong> travelMasks = new();
private readonly Dictionary<Piece, ulong> passedPawnMasks = new();

public Moves()
{
InitializeVectors();

InitializeTravelMasks();

InitializePassedPawnMasks();
}

private void InitializeTravelMasks()
Expand All @@ -37,38 +34,8 @@ private void InitializeTravelMasks()
}
}

private void InitializePassedPawnMasks()
{
var pieceTypes = PieceExtensions.Colors.Select(c => Piece.Pawn | c);
var squares = SquareExtensions.AllSquares.Where(s => s.Rank is > 0 and < 7);

var pieces = pieceTypes.Select(p => squares.Select(s => p.SetSquare(s))).SelectMany(p => p);

foreach (var piece in pieces)
{
var mask = 0ul;
var minRank = piece.Is(Piece.White) ? piece.GetSquare().Rank + 1 : 1;
var maxRank = piece.Is(Piece.White) ? 6 : piece.GetSquare().Rank - 1;

foreach (var dFile in new[] { -1, 0, 1 })
{
for (var rank = minRank; rank <= maxRank; rank++)
{
if (Square.TryCreate(piece.GetSquare().File + dFile, rank, out var square))
{
mask |= square.ToMask();
}
}
}

passedPawnMasks.Add(piece, mask);
}
}

public ulong GetTravelMask(ulong current, ulong target) => travelMasks[current | target];

public ulong GetPassedPawnMask(Piece piece) => passedPawnMasks[piece];

private void InitializeVectors()
{
PieceExtensions.AllPieces.ToList().ForEach(p =>
Expand Down
35 changes: 28 additions & 7 deletions Common/Lookup/OpeningBook.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Newtonsoft.Json;
using MessagePack;
using Serilog;
using SicTransit.Woodpusher.Model;
using SicTransit.Woodpusher.Model.Enums;
using SicTransit.Woodpusher.Model.Extensions;

namespace SicTransit.Woodpusher.Common.Lookup
Expand All @@ -11,16 +12,36 @@ public class OpeningBook

private Dictionary<ulong, Dictionary<string, int>> book = new();

private static readonly string BookFilename = Path.Combine(@"Resources","openings.json");
public string BookFilename { get; }

public OpeningBook(bool startEmpty = false)
public OpeningBook(Piece color, bool startEmpty = false)
{
BookFilename = Path.Combine(@"Resources", $"openings.{(color.Is(Piece.White) ? "white" : "black")}.bin");

if (!startEmpty)
{
LoadFromFile();
}
}

public void Prune(int keepTopPercent)
{
foreach (var hash in book.Keys)
{
var moves = book[hash];

foreach (var move in moves.OrderByDescending(m => m.Value).Skip(1))
{
moves.Remove(move.Key);
}
}

foreach (var entry in book.OrderByDescending(e => e.Value.Sum(x => x.Value)).Skip(book.Count / keepTopPercent))
{
book.Remove(entry.Key);
}
}

public void LoadFromFile(string? filename = null)
{
filename ??= BookFilename;
Expand All @@ -31,7 +52,7 @@ public void LoadFromFile(string? filename = null)
}
else
{
var loadedBook = JsonConvert.DeserializeObject<Dictionary<ulong, Dictionary<string, int>>>(File.ReadAllText(filename))!;
var loadedBook = MessagePackSerializer.Deserialize<Dictionary<ulong, Dictionary<string, int>>>(File.ReadAllBytes(filename));

foreach (var hash in loadedBook)
{
Expand All @@ -47,12 +68,12 @@ public void SaveToFile(string? filename = null)
{
filename ??= BookFilename;

var json = JsonConvert.SerializeObject(book, Formatting.Indented);
var bytes = MessagePackSerializer.Serialize(book);

File.WriteAllText(filename, json);
File.WriteAllBytes(filename, bytes);
}

public void AddMove(ulong hash, string move, int count = 1)
private void AddMove(ulong hash, string move, int count = 1)
{
if (book.TryGetValue(hash, out var moves))
{
Expand Down
Loading

0 comments on commit e9b6748

Please sign in to comment.