Skip to content

Commit

Permalink
zobrist hashing
Browse files Browse the repository at this point in the history
  • Loading branch information
sictransit committed Oct 31, 2022
1 parent 02a0b9c commit c5bde21
Show file tree
Hide file tree
Showing 19 changed files with 5,598 additions and 22,302 deletions.
71 changes: 39 additions & 32 deletions Common/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using SicTransit.Woodpusher.Model;
using SicTransit.Woodpusher.Model.Enums;
using SicTransit.Woodpusher.Model.Extensions;
using System.Security.Cryptography;

namespace SicTransit.Woodpusher.Common
{
Expand All @@ -23,37 +22,22 @@ public Board() : this(new Bitboard(Piece.White), new Bitboard(Piece.None), Count

}

private Board(Bitboard white, Bitboard black, Counters counters, BoardInternals internals)
private Board(Bitboard white, Bitboard black, Counters counters, BoardInternals internals, ulong hash)
{
this.white = white;
this.black = black;
occupiedSquares = white.All | black.All;

Counters = counters;
this.internals = internals;
Hash = hash == BoardInternals.InvalidHash ? internals.Zobrist.GetHash(this) : hash;
}

public Board(Bitboard white, Bitboard black, Counters counters) : this(white, black, counters, new BoardInternals())
public Board(Bitboard white, Bitboard black, Counters counters) : this(white, black, counters, new BoardInternals(), BoardInternals.InvalidHash)
{
}

public string GetHash()
{
// TODO: This is a really bad idea. We should adapt to e.g. Zobist hashing, but this will have to do for now.
return BitConverter.ToString(Hash).Replace("-", "");
}

private byte[] Hash
{
get
{
using var md5 = MD5.Create();

var bytes = white.Hash.Concat(black.Hash).Concat(Counters.Hash).ToArray();

return md5.ComputeHash(bytes);
}
}
public ulong Hash { get; }

public int Score
{
Expand All @@ -73,7 +57,7 @@ public int Score

public IEnumerable<Move> GetOpeningBookMoves()
{
var moves = internals.OpeningBook.GetMoves(GetHash());
var moves = internals.OpeningBook.GetMoves(Hash);

if (!moves.Any())
{
Expand All @@ -95,8 +79,8 @@ public IEnumerable<Move> GetOpeningBookMoves()
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.Add(piece), black, Counters, internals)
: (IBoard)new Board(white, black.Add(piece), Counters, internals);
? new Board(white.Add(piece), black, Counters, internals, BoardInternals.InvalidHash)
: (IBoard)new Board(white, black.Add(piece), Counters, internals, BoardInternals.InvalidHash);

public IBoard PlayMove(Move move)
{
Expand All @@ -105,20 +89,28 @@ public IBoard PlayMove(Move move)

private Board Play(Move move)
{
var hash = Hash;
var opponentBitboard = GetBitboard(ActiveColor.OpponentColor());

var targetPieceType = opponentBitboard.Peek(move.Target);
var targetPiece = opponentBitboard.Peek(move.Target);

if (targetPieceType != Piece.None)
if (targetPiece != Piece.None)
{
opponentBitboard = opponentBitboard.Remove(targetPieceType);
opponentBitboard = opponentBitboard.Remove(targetPiece);

hash ^= internals.Zobrist.GetPieceHash(targetPiece | ActiveColor.OpponentColor());
}
else if (move.Flags.HasFlag(SpecialMove.EnPassant))
{
opponentBitboard = opponentBitboard.Remove(Piece.Pawn.SetMask(move.EnPassantMask));
var targetPawn = Piece.Pawn.SetMask(move.EnPassantMask);
opponentBitboard = opponentBitboard.Remove(targetPawn);

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

var activeBitboard = GetBitboard(ActiveColor).Move(move.Piece, move.Target);
hash ^= internals.Zobrist.GetPieceHash(move.Piece);
hash ^= internals.Zobrist.GetPieceHash(move.Piece.SetMask(move.Target));

var castlings = Counters.Castlings;

Expand Down Expand Up @@ -157,7 +149,11 @@ private Board Play(Move move)

if (castling != default)
{
activeBitboard = activeBitboard.Move(Piece.Rook.SetMask(castling.from), castling.to);
var rook = (Piece.Rook | ActiveColor).SetMask(castling.from);

activeBitboard = activeBitboard.Move(rook, castling.to);

hash ^= internals.Zobrist.GetPieceHash(rook) ^ internals.Zobrist.GetPieceHash(rook.SetMask(castling.to));
}
}
else
Expand Down Expand Up @@ -203,21 +199,30 @@ private Board Play(Move move)
}
}
}

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

if (move.Flags.HasFlag(SpecialMove.Promote))
{
activeBitboard = activeBitboard.Remove(Piece.Pawn.SetMask(move.Target)).Add(move.PromotionType.SetMask(move.Target));
var promotedPiece = move.Piece.SetMask(move.Target);
var promotionPiece = (ActiveColor | move.PromotionType).SetMask(move.Target);
activeBitboard = activeBitboard.Remove(promotedPiece).Add(promotionPiece);

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

var halfmoveClock = move.Piece.Is(Piece.Pawn) || targetPieceType.GetPieceType() != Piece.None ? 0 : Counters.HalfmoveClock + 1;
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);

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

return ActiveColor.Is(Piece.White)
? new Board(activeBitboard, opponentBitboard, counters, internals)
: new Board(opponentBitboard, activeBitboard, counters, internals);
? new Board(activeBitboard, opponentBitboard, counters, internals, hash)
: new Board(opponentBitboard, activeBitboard, counters, internals, hash);
}

private bool IsOccupied(ulong mask) => (occupiedSquares & mask) != 0;
Expand All @@ -228,6 +233,8 @@ private Board Play(Move move)

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

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

public IEnumerable<Piece> GetPieces(Piece color) => GetBitboard(color).GetPieces();

public IEnumerable<Piece> GetPieces(Piece color, Piece type) => GetPieces(color, type, ulong.MaxValue);
Expand Down
5 changes: 5 additions & 0 deletions Common/BoardInternals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ internal class BoardInternals
public static readonly Piece BlackQueensideRook =
(Piece.Rook | Piece.None).SetMask(BlackQueensideRookStartingSquare);

public static readonly ulong InvalidHash = 0;

public Attacks Attacks { get; }

public Scoring Scoring { get; }
Expand All @@ -35,12 +37,15 @@ internal class BoardInternals

public OpeningBook OpeningBook { get; }

public Zobrist Zobrist { get; }

public BoardInternals()
{
Attacks = new Attacks();
Scoring = new Scoring();
Moves = new Moves();
OpeningBook = new OpeningBook();
Zobrist = new Zobrist();
}
}
}
2 changes: 1 addition & 1 deletion Common/Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

<ItemGroup>
<None Update="Resources\eco.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

Expand Down
9 changes: 9 additions & 0 deletions Common/Extensions/BoardExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ public static string PrettyPrint(this IBoard b)
sb.Append($"{(char)('a' + i)} ");
}

sb.AppendLine();

sb.AppendLine($"Hash: {b.Hash}");
sb.AppendLine($"Castlings: {b.Counters.Castlings}");
var target = b.Counters.EnPassantTarget == 0 ? "None" : b.Counters.EnPassantTarget.ToSquare().ToString();
sb.AppendLine($"En passant target: {target}");
var activeColor = b.ActiveColor == Piece.White ? "White" : "Black";
sb.AppendLine($"{activeColor} to play");

return sb.ToString();
}

Expand Down
6 changes: 4 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 PlayMove(Move move);

string GetHash();

bool IsPassedPawn(Piece piece);

IEnumerable<Move> GetOpeningBookMoves();
Expand All @@ -19,6 +17,8 @@ public interface IBoard

IEnumerable<Move> GetLegalMoves(Piece piece);

IEnumerable<Piece> GetPieces();

IEnumerable<Piece> GetPieces(Piece color);

IEnumerable<Piece> GetPieces(Piece color, Piece type);
Expand All @@ -27,6 +27,8 @@ public interface IBoard

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

ulong Hash { get; }

int Score { get; }

bool IsChecked { get; }
Expand Down
6 changes: 2 additions & 4 deletions Common/Lookup/Attacks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ public Attacks()

private void Initialize()
{
foreach (var color in new[] { Piece.White, Piece.None })
foreach (var color in PieceExtensions.Colors)
{
var squares = Enumerable.Range(0, 8).Select(f => Enumerable.Range(0, 8).Select(r => new Square(f, r))).SelectMany(x => x).ToList();

foreach (var square in squares)
foreach (var square in SquareExtensions.AllSquares)
{
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());
Expand Down
11 changes: 4 additions & 7 deletions Common/Lookup/Moves.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ private void InitializeTravelMasks()

private void InitializePassedPawnMasks()
{
var pieceTypes = new[] { Piece.White, Piece.None }.Select(c => Piece.Pawn | c);
var squares = Enumerable.Range(0, 8).Select(f => Enumerable.Range(1, 6).Select(r => new Square(f, r))).SelectMany(x => x).ToList();
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);

Expand Down Expand Up @@ -71,12 +71,9 @@ private void InitializePassedPawnMasks()

private void InitializeVectors()
{
var pieces = new[] { Piece.White, Piece.None }.Select(c => new[] { Piece.Pawn, Piece.Rook, Piece.Knight, Piece.Bishop, Piece.Queen, Piece.King }.Select(t => t | c)).SelectMany(x => x).ToList();
var squares = Enumerable.Range(0, 8).Select(f => Enumerable.Range(0, 8).Select(r => new Square(f, r))).SelectMany(x => x).ToList();

pieces.ForEach(p =>
PieceExtensions.AllPieces.ToList().ForEach(p =>
{
squares.ForEach(s =>
SquareExtensions.AllSquares.ToList().ForEach(s =>
{
var piece = p.SetSquare(s);
Expand Down
15 changes: 9 additions & 6 deletions Common/Lookup/OpeningBook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ public class OpeningBook
{
// Credit: https://github.com/lichess-org/chess-openings

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

private static readonly string BookFilename = Path.Combine(@"Resources\eco.json");

public OpeningBook()
public OpeningBook(bool startEmpty = false)
{
LoadFromFile();
if (!startEmpty)
{
LoadFromFile();
}
}

public void LoadFromFile(string? filename = null)
Expand All @@ -28,7 +31,7 @@ public void LoadFromFile(string? filename = null)
}
else
{
book = JsonConvert.DeserializeObject<Dictionary<string, HashSet<string>>>(File.ReadAllText(filename))!;
book = JsonConvert.DeserializeObject<Dictionary<ulong, HashSet<string>>>(File.ReadAllText(filename))!;
}
}

Expand All @@ -41,7 +44,7 @@ public void SaveToFile(string? filename = null)
File.WriteAllText(filename, json);
}

public void AddMove(string hash, Move move)
public void AddMove(ulong hash, Move move)
{
if (book.TryGetValue(hash, out var moves))
{
Expand All @@ -53,7 +56,7 @@ public void AddMove(string hash, Move move)
}
}

public IEnumerable<AlgebraicMove> GetMoves(string hash)
public IEnumerable<AlgebraicMove> GetMoves(ulong hash)
{
return book.TryGetValue(hash, out var moves) ? moves.Select(AlgebraicMove.Parse) : Enumerable.Empty<AlgebraicMove>();
}
Expand Down
7 changes: 2 additions & 5 deletions Common/Lookup/Scoring.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using SicTransit.Woodpusher.Model;
using SicTransit.Woodpusher.Model.Enums;
using SicTransit.Woodpusher.Model.Enums;
using SicTransit.Woodpusher.Model.Extensions;

namespace SicTransit.Woodpusher.Common.Lookup
Expand Down Expand Up @@ -174,9 +173,7 @@ public Scoring()

private static void InitializeEvaluations(Dictionary<Piece, int> evaluations, bool endGame)
{
var pieceTypes = new[] { Piece.None, Piece.White }.Select(c => new[] { Piece.Pawn, Piece.Rook, Piece.Knight, Piece.Bishop, Piece.Queen, Piece.King }.Select(t => t | c)).SelectMany(x => x).ToList();
var squares = Enumerable.Range(0, 8).Select(f => Enumerable.Range(0, 8).Select(r => new Square(f, r))).SelectMany(x => x).ToList();
var pieces = pieceTypes.Select(p => squares.Select(s => p.SetSquare(s))).SelectMany(p => p);
var pieces = PieceExtensions.AllPieces.Select(p => SquareExtensions.AllSquares.Select(s => p.SetSquare(s))).SelectMany(p => p);

foreach (var piece in pieces)
{
Expand Down
Loading

0 comments on commit c5bde21

Please sign in to comment.