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

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

pub const Attacks = struct {
    // leaper pieces
    pawn_attacks: [2][64]Board.BoardType = genPawnAttacks(),
    king_attacks: [64]Board.BoardType = genKingAttacks(),
    knight_attacks: [64]Board.BoardType = genKnightAttacks(),

    // rider pieces
    const bishop_magic: [64]Board.BoardType = .{ 9241390835893862432, 9234710240556097536, 1227239730261824640, 577604527957049348, 6918659473418780672, 351888113804529668, 2328924575914001472, 4614013701991826049, 2331927815258656, 9016004274033184, 2305860618585916944, 648597650767120384, 577061163230053010, 579277706207166480, 4614220409926336512, 580964498095089664, 4616199239888937985, 1126041776325888, 616996207142899796, 10141929783173120, 5630608183136385, 36873230555873632, 181275456685879328, 576743335520240152, 2603115769260982529, 2380875882396914688, 2289189183030272, 171145581949894664, 1153203048319827969, 2305913930985283584, 72657961755345152, 1127003718435328, 148904939935000608, 282608955041792, 1153062518049211400, 10378688279935975552, 290279660069376, 290279660069376, 6922619801915492428, 6922619801915492428, 1158067358212916246, 2328924575914001472, 36319360414908928, 4611703757044990977, 1224980203021800448, 4521260535005248, 290561443953836160, 2310347987528229122, 2328924575914001472, 564053894369288, 1585408924206502016, 4675139272704, 144185593598050818, 7440091971840, 4612816883383795840, 9234710240556097536, 4614013701991826049, 580964498095089664, 83334186434036744, 648597650767120384, 17592766972164, 9439545093914103944, 2331927815258656, 9241390835893862432 };
    const rook_magic: [64]Board.BoardType = .{ 9259401108760043536, 594475563268280320, 144124055069409314, 72068610631143424, 72076285769418752, 108090789187420288, 324265770315940096, 9367487500631408770, 2305983747248373760, 14051934662285148160, 4612249107028804096, 72198881416318976, 9223653529145639440, 4648277836300289160, 1153204079162360320, 167055404054872320, 4791830278402211904, 13511073762115648, 10090880114674926608, 18332157638541321, 4902733343504729120, 141287311278592, 876517475657892112, 573945342918788, 35804994879488, 9223688714458236480, 72906490033217536, 844463587201024, 290486580455932032, 144117389246857344, 18295937913061888, 5406589228312428673, 9223442407754825769, 4503874543034369, 48431846559596576, 9295438429141602308, 18018869587216385, 3377768473690120, 2233852801025, 4785075694604450, 36030189125550082, 9227875774059790342, 9223407221495332992, 18332157638541321, 1155182100580696192, 35201820918080, 4612820723034226768, 45041495719411716, 603668391936256, 18084767791612032, 4612249107028804096, 15276649741245483264, 288371148000592256, 14628817498200408128, 1127068272296960, 4657003602455658752, 88510690829569, 2535612326510617, 144124055069409314, 2377918195506874625, 4611967527763988485, 1181631959278293026, 9232412290735146116, 261213468561048706 };

    const bishop_masks = genBishopMasks();
    const rook_masks = genRookMasks();

    const bishop_relevant_bits = genRelevantOccupancy(.bishop);
    const rook_relevant_bits = genRelevantOccupancy(.rook);

    var bishop_attacks: [64][512]Board.BoardType = undefined;
    var rook_attacks: [64][4096]Board.BoardType = undefined;

    pub fn init() @This() {
        var attacks: @This() = .{};
        // queen attacks are generated by merging bishop and rook attacks
        initSliderAttacks(.bishop);
        initSliderAttacks(.rook);
        return attacks;
    }

    fn initSliderAttacks(piece: Chess.Pieces) void {
        for (std.enums.values(Board.Square)) |square| {
            const square_int = @intFromEnum(square);
            const attack_mask = switch (piece) {
                .bishop => bishop_masks[square_int],
                .rook => rook_masks[square_int],
                else => unreachable,
            };

            // init relevant occupancy bit count
            const relevant_bits = @popCount(attack_mask);

            const occupancy_idx = @as(Board.BoardType, 1) << @as(Board.SquareType, @intCast(relevant_bits));

            var idx: usize = 0;
            while (idx < occupancy_idx) : (idx += 1) {
                switch (piece) {
                    .bishop => {
                        const occupancy = setOccupancy(idx, relevant_bits, attack_mask);
                        const res = @mulWithOverflow(occupancy, bishop_magic[square_int])[0];
                        const magic_index = res >>
                            @as(Board.SquareType, @intCast(@as(u7, 64) - bishop_relevant_bits[square_int]));

                        bishop_attacks[square_int][magic_index] =
                            attackWithBlocker(occupancy, square, .bishop);
                    },
                    .rook => {
                        const occupancy = setOccupancy(idx, relevant_bits, attack_mask);
                        const res = @mulWithOverflow(occupancy, rook_magic[@intFromEnum(square)])[0];
                        const magic_index = res >>
                            @as(Board.SquareType, @intCast(@as(u7, 64) - rook_relevant_bits[square_int]));

                        rook_attacks[square_int][magic_index] =
                            attackWithBlocker(occupancy, square, .rook);
                    },
                    else => unreachable,
                }
            }
        }
    }

    pub fn getBishopAttacks(self: @This(), square: Board.SquareType, occupancy: Board.BoardType) *Board.BoardType {
        _ = self;
        var mut_occupancy: Board.BoardType = occupancy;

        mut_occupancy &= bishop_masks[square];
        mut_occupancy = @mulWithOverflow(mut_occupancy, bishop_magic[square])[0];
        mut_occupancy >>= @as(Board.SquareType, @intCast(@as(u7, 64) -
            bishop_relevant_bits[square]));

        return &bishop_attacks[square][mut_occupancy];
    }

    pub fn getRookAttacks(self: @This(), square: Board.SquareType, occupancy: Board.BoardType) *Board.BoardType {
        _ = self;
        var mut_occupancy: Board.BoardType = occupancy;

        mut_occupancy &= rook_masks[square];
        mut_occupancy = @mulWithOverflow(mut_occupancy, rook_magic[square])[0];
        mut_occupancy >>= @as(Board.SquareType, @intCast(@as(u7, 64) -
            rook_relevant_bits[square]));

        return &rook_attacks[square][mut_occupancy];
    }

    pub fn getQueenAttacks(self: @This(), square: Board.SquareType, occupancy: Board.BoardType) Board.BoardType {
        return getBishopAttacks(self, square, occupancy).* |
            getRookAttacks(self, square, occupancy).*;
    }
};

test "getQueenAttacks" {
    var attack = Attacks.init();

    var occupancy = Board.BitBoard{};
    occupancy.setSlice(&[_]Board.Square{ .d7, .f2, .c3, .f4 });

    const got = attack.getQueenAttacks(
        @intFromEnum(Board.Square.d4),
        @as(Board.BoardType, @bitCast(occupancy)),
    );

    var expected = Board.BitBoard{};
    expected.setSlice(&[_]Board.Square{ .h8, .a7, .d7, .g7, .b6, .d6, .f6, .c5, .d5, .e5, .a4, .b4, .c4, .e4, .f4, .c3, .d3, .e3, .d2, .f2, .d1 });

    try std.testing.expectEqual(@as(Board.BoardType, @bitCast(expected)), got);
}

fn genPawnAttacks() [@typeInfo(Chess.Colors).Enum.fields.len][@typeInfo(Board.Square).Enum.fields.len]Board.BoardType {
    var ret: [2][64]Board.BoardType = [_][64]Board.BoardType{[_]Board.BoardType{0} ** 64} ** 2;

    for (std.enums.values(Chess.Colors)) |color| {
        for (std.enums.values(Board.Square)) |square| {
            ret[@intFromEnum(color)][@intFromEnum(square)] = genSteps(square, .pawn, color);
        }
    }

    return ret;
}

test "PawnAttacks" {
    const pa = genPawnAttacks();

    { // White
        {
            var bb = Board.BitBoard{};
            bb.setSlice(&[2]Board.Square{ .c5, .e5 });
            try std.testing.expectEqual(
                @as(Board.BoardType, @bitCast(bb)),
                pa[@intFromEnum(Chess.Colors.white)][@intFromEnum(Board.Square.d4)],
            );
        }
        {
            // no left attack
            var bb = Board.BitBoard{};
            bb.b5 = true;
            try std.testing.expectEqual(
                @as(Board.BoardType, @bitCast(bb)),
                pa[@intFromEnum(Chess.Colors.white)][@intFromEnum(Board.Square.a4)],
            );
        }
        {
            // no right attack
            var bb = Board.BitBoard{};
            bb.g5 = true;
            try std.testing.expectEqual(
                @as(Board.BoardType, @bitCast(bb)),
                pa[@intFromEnum(Chess.Colors.white)][@intFromEnum(Board.Square.h4)],
            );
        }
    }
    { // Black
        {
            var bb = Board.BitBoard{};
            bb.setSlice(&[2]Board.Square{ .c3, .e3 });
            try std.testing.expectEqual(
                @as(Board.BoardType, @bitCast(bb)),
                pa[@intFromEnum(Chess.Colors.black)][@intFromEnum(Board.Square.d4)],
            );
        }
        {
            // no left attack
            var bb = Board.BitBoard{};
            bb.b3 = true;
            try std.testing.expectEqual(
                @as(Board.BoardType, @bitCast(bb)),
                pa[@intFromEnum(Chess.Colors.black)][@intFromEnum(Board.Square.a4)],
            );
        }
        {
            // no right attack
            var bb = Board.BitBoard{};
            bb.g3 = true;
            try std.testing.expectEqual(
                @as(Board.BoardType, @bitCast(bb)),
                pa[@intFromEnum(Chess.Colors.black)][@intFromEnum(Board.Square.h4)],
            );
        }
    }
}

fn genKnightAttacks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType {
    var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64;

    for (std.enums.values(Board.Square)) |square| {
        ret[@intFromEnum(square)] = genSteps(square, .knight, .white);
    }

    return ret;
}

test "KnightAttacks" {
    const ka = genKnightAttacks();

    { // full move
        var bb = Board.BitBoard{};
        bb.setSlice(&[8]Board.Square{ .c7, .e7, .b6, .f6, .c3, .e3, .b4, .f4 });
        try std.testing.expectEqual(
            @as(Board.BoardType, @bitCast(bb)),
            ka[@intFromEnum(Board.Square.d5)],
        );
    }
    { // only 6 move
        var bb = Board.BitBoard{};
        bb.setSlice(&[6]Board.Square{ .f7, .h7, .e6, .e4, .f3, .h3 });
        try std.testing.expectEqual(
            @as(Board.BoardType, @bitCast(bb)),
            ka[@intFromEnum(Board.Square.g5)],
        );
    }
    { // only 4 move
        var bb = Board.BitBoard{};
        bb.setSlice(&[4]Board.Square{ .b7, .c6, .c4, .b3 });
        try std.testing.expectEqual(
            @as(Board.BoardType, @bitCast(bb)),
            ka[@intFromEnum(Board.Square.a5)],
        );
    }
}

fn genKingAttacks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType {
    var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64;

    for (std.enums.values(Board.Square)) |square| {
        ret[@intFromEnum(square)] = genSteps(square, .king, .white);
    }

    return ret;
}

test "KingAttacks" {
    const ka = genKingAttacks();

    { // full move
        var bb = Board.BitBoard{};
        bb.setSlice(&[8]Board.Square{ .c5, .d5, .e5, .c3, .d3, .e3, .c4, .e4 });
        try std.testing.expectEqual(
            @as(Board.BoardType, @bitCast(bb)),
            ka[@intFromEnum(Board.Square.d4)],
        );
    }
    { // sides - 5 moves
        var bb = Board.BitBoard{};
        bb.setSlice(&[5]Board.Square{ .a5, .a3, .b5, .b4, .b3 });
        try std.testing.expectEqual(
            @as(Board.BoardType, @bitCast(bb)),
            ka[@intFromEnum(Board.Square.a4)],
        );
    }
    { // sides - 5 moves
        var bb = Board.BitBoard{};
        bb.setSlice(&[5]Board.Square{ .c8, .e8, .c7, .d7, .e7 });
        try std.testing.expectEqual(
            @as(Board.BoardType, @bitCast(bb)),
            ka[@intFromEnum(Board.Square.d8)],
        );
    }
    { // corner - 3 moves
        var bb = Board.BitBoard{};
        bb.setSlice(&[3]Board.Square{ .g1, .g2, .h2 });
        try std.testing.expectEqual(
            @as(Board.BoardType, @bitCast(bb)),
            ka[@intFromEnum(Board.Square.h1)],
        );
    }
}

fn genBishopMasks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType {
    var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64;

    for (std.enums.values(Board.Square)) |square| {
        ret[@intFromEnum(square)] = genSteps(square, .bishop, .white);
    }

    return ret;
}

test "BishopMasks" {
    const ba = genBishopMasks();

    {
        var bb = Board.BitBoard{};
        bb.setSlice(&[_]Board.Square{ .b2, .c3, .d4, .e5, .f6, .g7 });
        try std.testing.expectEqual(
            @as(Board.BoardType, @bitCast(bb)),
            ba[@intFromEnum(Board.Square.a1)],
        );
    }
}

fn genRookMasks() [@typeInfo(Board.Square).Enum.fields.len]Board.BoardType {
    var ret: [64]Board.BoardType = [_]Board.BoardType{0} ** 64;

    for (std.enums.values(Board.Square)) |square| {
        ret[@intFromEnum(square)] = genSteps(square, .rook, .white);
    }

    return ret;
}

test "RookMasks" {
    const ra = genRookMasks();

    {
        var bb = Board.BitBoard{};
        bb.setSlice(&[_]Board.Square{ .b1, .c1, .d1, .e1, .f1, .g2, .g3, .g4, .g5, .g6, .g7 });
        try std.testing.expectEqual(
            @as(Board.BoardType, @bitCast(bb)),
            ra[@intFromEnum(Board.Square.g1)],
        );
    }
    {
        var bb = Board.BitBoard{};
        bb.setSlice(&[_]Board.Square{ .b1, .c1, .d1, .e1, .f1, .g1, .h2, .h3, .h4, .h5, .h6, .h7 });
        try std.testing.expectEqual(
            @as(Board.BoardType, @bitCast(bb)),
            ra[@intFromEnum(Board.Square.h1)],
        );
    }
}

fn genSteps(square: Board.Square, piece: Chess.Pieces, color: Chess.Colors) Board.BoardType {
    const rank = @as(isize, @intCast(square.rank()));
    const file = @as(isize, @intCast(square.file()));

    const rider = switch (piece) {
        .pawn => false,
        .knight => false,
        .bishop => true,
        .rook => true,
        .king => false,
        .queen => unreachable,
    };

    // TODO: I repeat some moves here to make zig happy, but ugly.
    const moves = switch (piece) {
        .pawn => switch (color) {
            .white => [_][2]i3{ .{ -1, -1 }, .{ -1, 1 }, .{ -1, -1 }, .{ -1, 1 }, .{ -1, -1 }, .{ -1, 1 }, .{ -1, -1 }, .{ -1, 1 } },
            .black => [_][2]i3{ .{ 1, -1 }, .{ 1, 1 }, .{ 1, -1 }, .{ 1, 1 }, .{ 1, -1 }, .{ 1, 1 }, .{ 1, -1 }, .{ 1, 1 } },
        },
        .knight => [_][2]i3{ .{ -2, -1 }, .{ -2, 1 }, .{ 2, -1 }, .{ 2, 1 }, .{ -1, -2 }, .{ 1, 2 }, .{ -1, 2 }, .{ 1, -2 } },
        .bishop => [_][2]i3{ .{ -1, -1 }, .{ -1, 1 }, .{ 1, -1 }, .{ 1, 1 }, .{ -1, -1 }, .{ -1, 1 }, .{ 1, -1 }, .{ 1, 1 } },
        .king => [_][2]i3{ .{ -1, -1 }, .{ -1, 1 }, .{ 1, -1 }, .{ 1, 1 }, .{ -1, 0 }, .{ 0, 1 }, .{ 1, 0 }, .{ 0, -1 } },
        .rook => [_][2]i3{ .{ -1, 0 }, .{ 1, 0 }, .{ 0, -1 }, .{ 0, 1 }, .{ -1, 0 }, .{ 1, 0 }, .{ 0, -1 }, .{ 0, 1 } },
        .queen => unreachable,
    };

    var attacks: Board.BoardType = 0;

    @setEvalBranchQuota(10_000);

    var step: i5 = 1;
    while (step < Board.SIZE - 1) : (step += 1) {
        for (moves) |m| {
            const dr = rank + m[0] * step;
            const df = file + m[1] * step;

            switch (rider) {
                true => {
                    if (m[0] > 0 and dr > 6) continue;
                    if (m[0] < 0 and dr < 1) continue;
                    if (m[1] > 0 and df > 6) continue;
                    if (m[1] < 0 and df < 1) continue;
                },
                false => {
                    if (dr < 0 or dr > 7) continue;
                    if (df < 0 or df > 7) continue;
                },
            }

            const s = @as(Board.SquareType, @intCast(dr * Board.SIZE + df));

            attacks |= @as(Board.BoardType, 1) << s;
        }
        if (!rider) break;
    }

    return attacks;
}

fn attackWithBlocker(blocks: Board.BoardType, square: Board.Square, piece: Chess.Pieces) Board.BoardType {
    const rank = @as(isize, @intCast(square.rank()));
    const file = @as(isize, @intCast(square.file()));

    const moves = switch (piece) {
        .bishop => [_][2]i3{
            .{ -1, -1 },
            .{ -1, 1 },
            .{ 1, -1 },
            .{ 1, 1 },
        },
        .rook => [_][2]i3{
            .{ -1, 0 },
            .{ 1, 0 },
            .{ 0, -1 },
            .{ 0, 1 },
        },
        else => unreachable,
    };

    var attacks: Board.BoardType = 0;

    for (moves) |m| {
        var step: i5 = 1;
        while (step < Board.SIZE) : (step += 1) {
            const dr = rank + m[0] * step;
            const df = file + m[1] * step;

            if (m[0] > 0 and dr > 7) break;
            if (m[0] < 0 and dr < 0) break;
            if (m[1] > 0 and df > 7) break;
            if (m[1] < 0 and df < 0) break;

            const s = @as(Board.SquareType, @intCast(dr * Board.SIZE + df));
            const mask = @as(Board.BoardType, 1) << s;

            attacks |= mask;

            // stop when we hit a blocker
            if (mask & blocks != 0) break;
        }
    }

    return attacks;
}

test "attackWithBlocker" {
    { // bishop d4
        var blocks = Board.BitBoard{};
        blocks.setSlice(&[_]Board.Square{ .c3, .b6, .f2 });

        const got = attackWithBlocker(@as(Board.BoardType, @bitCast(blocks)), .d4, .bishop);

        var bb = Board.BitBoard{};
        bb.setSlice(&[_]Board.Square{ .c3, .f2, .e3, .c5, .b6, .e5, .f6, .g7, .h8 });

        try std.testing.expectEqual(bb, @as(Board.BitBoard, @bitCast(got)));
    }

    { // rook d4
        var blocks = Board.BitBoard{};
        blocks.setSlice(&[_]Board.Square{ .b4, .d3, .d5 });

        const got = attackWithBlocker(@as(Board.BoardType, @bitCast(blocks)), .d4, .rook);

        var bb = Board.BitBoard{};
        bb.setSlice(&[_]Board.Square{ .c4, .b4, .d3, .d5, .e4, .f4, .g4, .h4 });

        try std.testing.expectEqual(bb, @as(Board.BitBoard, @bitCast(got)));
    }

    { // rook h8
        var blocks = Board.BitBoard{};
        blocks.setSlice(&[_]Board.Square{.b8});

        const got = attackWithBlocker(@as(Board.BoardType, @bitCast(blocks)), .h8, .rook);

        var bb = Board.BitBoard{};
        bb.setSlice(&[_]Board.Square{ .b8, .c8, .d8, .e8, .f8, .g8, .h7, .h6, .h5, .h4, .h3, .h2, .h1 });

        try std.testing.expectEqual(bb, @as(Board.BitBoard, @bitCast(got)));
    }
}

fn setOccupancy(index: usize, bits_in_mask: Board.BoardType, attack_mask: Board.BoardType) Board.BoardType {
    var occupancy: Board.BoardType = 0;

    var attack: Board.BoardType = attack_mask;

    var count: Board.SquareType = 0;
    while (count < bits_in_mask) : (count += 1) {
        const square = @as(Board.SquareType, @intCast(@ctz(attack)));

        // pop square bit
        // if (attack & @as(Board.BoardType, 1) << square != 0)
        attack ^= @as(Board.BoardType, 1) << square;

        if (index & @as(Board.BoardType, 1) << count != 0) {
            occupancy |= @as(Board.BoardType, 1) << square;
        }
    }

    return occupancy;
}

test "occupancy bits" {
    const attack_mask = genSteps(.a1, .rook, .white);

    var occupancy: Board.BoardType = 0;
    occupancy = setOccupancy(4095, @popCount(attack_mask), attack_mask);

    var expected: Board.BitBoard = .{};
    expected.setSlice(&[_]Board.Square{ .a2, .a3, .a4, .a5, .a6, .a7, .b1, .c1, .d1, .e1, .f1, .g1 });

    try std.testing.expectEqual(expected, @as(Board.BitBoard, @bitCast(occupancy)));
}

fn genRelevantOccupancy(piece: Chess.Pieces) [@typeInfo(Board.Square).Enum.fields.len]u4 {
    var ret: [64]u4 = [_]u4{0} ** 64;

    for (std.enums.values(Board.Square)) |square| {
        const count = @popCount(genSteps(square, piece, .white));
        ret[@intFromEnum(square)] = @as(u4, @intCast(count));
    }

    return ret;
}

test "genRelevantOccupancy" {
    { // bishop
        const bishop = genRelevantOccupancy(.bishop);

        // zig fmt: off
        const expected = [64]u4{
            6, 5, 5, 5, 5, 5, 5, 6,
            5, 5, 5, 5, 5, 5, 5, 5,
            5, 5, 7, 7, 7, 7, 5, 5,
            5, 5, 7, 9, 9, 7, 5, 5,
            5, 5, 7, 9, 9, 7, 5, 5,
            5, 5, 7, 7, 7, 7, 5, 5,
            5, 5, 5, 5, 5, 5, 5, 5,
            6, 5, 5, 5, 5, 5, 5, 6,
        };
        // zig fmt: on

        try std.testing.expectEqualSlices(u4, &expected, &bishop);
    }

    { // rook
        const rook = genRelevantOccupancy(.rook);

        // zig fmt: off
        const expected = [64]u4{
            12, 11, 11, 11, 11, 11, 11, 12,
            11, 10, 10, 10, 10, 10, 10, 11,
            11, 10, 10, 10, 10, 10, 10, 11,
            11, 10, 10, 10, 10, 10, 10, 11,
            11, 10, 10, 10, 10, 10, 10, 11,
            11, 10, 10, 10, 10, 10, 10, 11,
            11, 10, 10, 10, 10, 10, 10, 11,
            12, 11, 11, 11, 11, 11, 11, 12,
        };
        // zig fmt: on

        try std.testing.expectEqualSlices(u4, &expected, &rook);
    }
}

fn findMagicNumber(square: Board.Square, relevant: u4, piece: Chess.Pieces) Board.BoardType {
    // this is for rooks, bishop needs less
    var occupancies: [4096]Board.BoardType = undefined;

    var attacks: [4096]Board.BoardType = undefined;

    var used: [4096]Board.BoardType = undefined;

    // attack mask for a current piece
    const attack_mask = genSteps(square, piece, .white);

    const occupancy_idx = @as(Board.BoardType, 1) << relevant;

    var idx: usize = 0;
    while (idx < occupancy_idx) : (idx += 1) {
        occupancies[idx] = setOccupancy(idx, relevant, attack_mask);
        attacks[idx] = attackWithBlocker(occupancies[idx], square, piece);
    }

    var rnd = std.rand.DefaultPrng.init(0);

    // test the generated number
    var loop: usize = 0;
    while (loop < 100_000_000) : (loop += 1) {
        const magic = rnd.next() & rnd.next() & rnd.next();

        // skip inappropriate numbers FIXME: ugly
        const tmp1 = @mulWithOverflow(attack_mask, magic)[0];
        if (@popCount(tmp1 & 0xFF00000000000000) < 6) continue;

        used = [_]Board.BoardType{0} ** 4096;

        var index: usize = 0;
        var fail: bool = false;
        while (!fail and index < occupancy_idx) : (index += 1) {
            const tmp2 = @mulWithOverflow(occupancies[index], magic)[0];
            var magic_index: usize = @as(usize, @intCast(tmp2 >>
                @as(Board.SquareType, @intCast(@as(u7, 64) - relevant))));

            if (used[magic_index] == 0) {
                // magic index works
                used[magic_index] = attacks[index];
            } else if (used[magic_index] != attacks[index]) {
                // magic index does not work
                fail = true;
            }
        }

        if (!fail) {
            return magic;
        }
    }

    unreachable;
}

test "BishopMagicNumbers" {
    if (true) return error.SkipZigTest; // slow

    var bishop_magic: [64]Board.BoardType = undefined;
    for (std.enums.values(Board.Square), 0..) |square, idx| {
        bishop_magic[idx] = findMagicNumber(
            square,
            genRelevantOccupancy(.bishop)[@intFromEnum(square)],
            .bishop,
        );
    }

    const expected_bishop_magic: [64]Board.BoardType = .{ 9241390835893862432, 9234710240556097536, 1227239730261824640, 577604527957049348, 6918659473418780672, 351888113804529668, 2328924575914001472, 4614013701991826049, 2331927815258656, 9016004274033184, 2305860618585916944, 648597650767120384, 577061163230053010, 579277706207166480, 4614220409926336512, 580964498095089664, 4616199239888937985, 1126041776325888, 616996207142899796, 10141929783173120, 5630608183136385, 36873230555873632, 181275456685879328, 576743335520240152, 2603115769260982529, 2380875882396914688, 2289189183030272, 171145581949894664, 1153203048319827969, 2305913930985283584, 72657961755345152, 1127003718435328, 148904939935000608, 282608955041792, 1153062518049211400, 10378688279935975552, 290279660069376, 290279660069376, 6922619801915492428, 6922619801915492428, 1158067358212916246, 2328924575914001472, 36319360414908928, 4611703757044990977, 1224980203021800448, 4521260535005248, 290561443953836160, 2310347987528229122, 2328924575914001472, 564053894369288, 1585408924206502016, 4675139272704, 144185593598050818, 7440091971840, 4612816883383795840, 9234710240556097536, 4614013701991826049, 580964498095089664, 83334186434036744, 648597650767120384, 17592766972164, 9439545093914103944, 2331927815258656, 9241390835893862432 };

    try std.testing.expectEqualSlices(Board.BoardType, &expected_bishop_magic, &bishop_magic);
}

test "RookMagicNumbers" {
    if (true) return error.SkipZigTest; // slow

    var rook_magic: [64]Board.BoardType = undefined;
    for (std.enums.values(Board.Square), 0..) |square, idx| {
        rook_magic[idx] = findMagicNumber(
            square,
            genRelevantOccupancy(.rook)[@intFromEnum(square)],
            .rook,
        );
    }

    const expected_rook_magic: [64]Board.BoardType = .{ 9259401108760043536, 594475563268280320, 144124055069409314, 72068610631143424, 72076285769418752, 108090789187420288, 324265770315940096, 9367487500631408770, 2305983747248373760, 14051934662285148160, 4612249107028804096, 72198881416318976, 9223653529145639440, 4648277836300289160, 1153204079162360320, 167055404054872320, 4791830278402211904, 13511073762115648, 10090880114674926608, 18332157638541321, 4902733343504729120, 141287311278592, 876517475657892112, 573945342918788, 35804994879488, 9223688714458236480, 72906490033217536, 844463587201024, 290486580455932032, 144117389246857344, 18295937913061888, 5406589228312428673, 9223442407754825769, 4503874543034369, 48431846559596576, 9295438429141602308, 18018869587216385, 3377768473690120, 2233852801025, 4785075694604450, 36030189125550082, 9227875774059790342, 9223407221495332992, 18332157638541321, 1155182100580696192, 35201820918080, 4612820723034226768, 45041495719411716, 603668391936256, 18084767791612032, 4612249107028804096, 15276649741245483264, 288371148000592256, 14628817498200408128, 1127068272296960, 4657003602455658752, 88510690829569, 2535612326510617, 144124055069409314, 2377918195506874625, 4611967527763988485, 1181631959278293026, 9232412290735146116, 261213468561048706 };

    try std.testing.expectEqualSlices(Board.BoardType, &expected_rook_magic, &rook_magic);
}