Chess engine in zig
const std = @import("std");

const Chess = @import("Chess.zig");
const Board = @import("Board.zig");
const GameState = @import("Board.zig").GameState;

pub var piece_hashes: [@typeInfo(Chess.PE).Enum.fields.len][@typeInfo(Board.Square).Enum.fields.len]u64 = undefined;

pub var enpassant_hashes: [@typeInfo(Board.Square).Enum.fields.len]u64 = undefined;

// 1111 -> 16
pub var castle_hashes: [16]u64 = undefined;

pub var side_hash: u64 = undefined;

pub fn init() void {
    var rnd = std.rand.DefaultPrng.init(@as(u64, @intCast(std.time.timestamp())));

    for (piece_hashes, 0..) |pieces, i| {
        for (pieces, 0..) |_, j| {
            piece_hashes[i][j] = rnd.next();
        }
    }

    for (&enpassant_hashes) |*ep| {
        ep.* = rnd.next();
    }

    for (&castle_hashes) |*cas| {
        cas.* = rnd.next();
    }

    side_hash = rnd.next();
}

pub fn updateHash(gs: *GameState) void {
    gs.hash = 0; // starting point

    for (gs.bitboards, 0..) |bb, piece| {
        var bitboard = @as(Board.BoardType, @bitCast(bb));

        while (bitboard != 0) {
            const square = @ctz(bitboard);

            gs.hash ^= piece_hashes[piece][square];

            bitboard ^= @as(u64, 1) << @as(Board.SquareType, @intCast(square));
        }
    }

    if (gs.enpassant != null) {
        gs.hash ^= enpassant_hashes[@intFromEnum(gs.enpassant.?)];
    }

    gs.hash ^= castle_hashes[@as(Board.CastlingType, @bitCast(gs.castling))];

    if (gs.side == .black) {
        gs.hash ^= side_hash;
    }
}

test "zobrist - parseFEN" {
    var gs = try GameState.init(
        std.testing.allocator,
        "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1", // tricky
    );
    defer gs.deinit();

    const orig = gs.hash;

    // side
    try gs.parseFEN("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R b KQkq - 0 1");
    try std.testing.expect(orig != gs.hash);

    // castling
    try gs.parseFEN("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w kq - 0 1");
    try std.testing.expect(orig != gs.hash);

    // enpassant
    try gs.parseFEN("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq c6 0 1");
    try std.testing.expect(orig != gs.hash);

    // move
    try gs.parseFEN("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2PQ2/2N211p/PPPBBPPP/R3K2R b KQkq - 0 1");
    try std.testing.expect(orig != gs.hash);

    // reset original
    try gs.parseFEN("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1");
    try std.testing.expectEqual(orig, gs.hash);
}