quantik-core 1.1.0

High-performance Quantik board game engine: bitboard state, QFEN notation, canonical symmetry-reduced keys, and minimax/MCTS/beam-search engines. Rust companion to the Python quantik-core package.
Documentation
use quantik_core::bitboard::Bitboard;
use quantik_core::constants::{FLAG_CANON, VERSION};
use quantik_core::game::has_winning_line;
use quantik_core::moves::generate_legal_moves;
use quantik_core::qfen::{bb_from_qfen, bb_to_qfen};
use quantik_core::symmetry::SymmetryHandler;
use std::time::Instant;

type Fixture = (
    &'static str,
    &'static str,
    [u16; 8],
    bool,
    usize,
    &'static str,
);

fn canonical_key_bytes(bb: &Bitboard) -> [u8; 18] {
    let canon = SymmetryHandler::find_canonical(bb);
    let mut key = [0u8; 18];
    key[0] = VERSION;
    key[1] = FLAG_CANON;
    let bytes = canon.to_le_bytes();
    key[2..18].copy_from_slice(&bytes);
    key
}

fn main() {
    let fixtures: Vec<Fixture> = vec![
        (
            "empty",
            "..../..../..../....",
            [0, 0, 0, 0, 0, 0, 0, 0],
            false,
            64,
            "010200000000000000000000000000000000",
        ),
        (
            "single_corner",
            "A.../..../..../....",
            [1, 0, 0, 0, 0, 0, 0, 0],
            false,
            53,
            "010200000000000000100000000000000000",
        ),
        (
            "alternating",
            "Ab../..../..../....",
            [1, 0, 0, 0, 0, 2, 0, 0],
            false,
            50,
            "010200000000000000100000000000010000",
        ),
        (
            "winning_row",
            "AbCd/..../..../....",
            [1, 0, 4, 0, 0, 2, 0, 8],
            true,
            40,
            "010200000000000101000010100000000000",
        ),
        (
            "winning_col",
            "A.../B.../C.../D...",
            [1, 16, 256, 4096, 0, 0, 0, 0],
            true,
            0,
            "010200010010010010000000000000000000",
        ),
        (
            "midgame",
            "Ab../c.D./..../...A",
            [32769, 0, 0, 64, 0, 2, 16, 0],
            false,
            28,
            "010200000000000201800008004000000000",
        ),
        (
            "full",
            "AbcD/bCdA/cDaB/DaBc",
            [129, 18432, 32, 4616, 9216, 18, 33028, 64],
            true,
            0,
            "010200020018218084001840420000040021",
        ),
    ];

    println!("{{");
    println!("  \"language\": \"rust\",");
    println!("  \"results\": {{");
    let total = fixtures.len();

    for (idx, (name, qfen, planes, exp_win, exp_moves, exp_ck_hex)) in fixtures.iter().enumerate() {
        let bb = Bitboard::new(*planes);

        let encoded = bb_to_qfen(&bb);
        let decoded = bb_from_qfen(qfen).unwrap();
        let qfen_ok = encoded == *qfen && decoded == bb;

        let got_win = has_winning_line(&bb);
        let moves = generate_legal_moves(&bb);
        let got_moves = moves.len();

        let key = canonical_key_bytes(&bb);
        let got_ck_hex: String = key.iter().map(|b| format!("{:02x}", b)).collect();

        let iters = 10_000u64;
        let start = Instant::now();
        for _ in 0..iters {
            let _ = SymmetryHandler::find_canonical(&bb);
        }
        let canon_ns = start.elapsed().as_nanos() as f64 / iters as f64;

        let start = Instant::now();
        for _ in 0..iters {
            let _ = generate_legal_moves(&bb);
        }
        let moves_ns = start.elapsed().as_nanos() as f64 / iters as f64;

        let start = Instant::now();
        for _ in 0..100_000u64 {
            let _ = has_winning_line(&bb);
        }
        let win_ns = start.elapsed().as_nanos() as f64 / 100_000.0;

        let comma = if idx < total - 1 { "," } else { "" };
        println!("    \"{}\": {{", name);
        println!("      \"qfen_roundtrip\": {},", qfen_ok);
        println!("      \"win_match\": {},", got_win == *exp_win);
        println!("      \"moves_match\": {},", got_moves == *exp_moves);
        println!("      \"canonical_match\": {},", got_ck_hex == *exp_ck_hex);
        println!("      \"got_moves\": {},", got_moves);
        println!("      \"got_canonical_hex\": \"{}\",", got_ck_hex);
        println!("      \"bench_canonical_ns_per_op\": {:.1},", canon_ns);
        println!("      \"bench_moves_ns_per_op\": {:.1},", moves_ns);
        println!("      \"bench_win_ns_per_op\": {:.1}", win_ns);
        println!("    }}{}", comma);
    }
    println!("  }}");
    println!("}}");
}