add single state space benchmark + some tests
This commit is contained in:
74
test/bitmap.cpp
Normal file
74
test/bitmap.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
// ReSharper disable CppLocalVariableMayBeConst
|
||||
#include "puzzle.hpp"
|
||||
|
||||
#include <random>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
TEST_CASE("bitmap_is_full all bits set", "[puzzle][board]")
|
||||
{
|
||||
puzzle p1(5, 5);
|
||||
puzzle p2(3, 4);
|
||||
puzzle p3(5, 4);
|
||||
puzzle p4(3, 7);
|
||||
uint64_t bitmap = -1;
|
||||
|
||||
REQUIRE(p1.bitmap_is_full(bitmap));
|
||||
REQUIRE(p2.bitmap_is_full(bitmap));
|
||||
REQUIRE(p3.bitmap_is_full(bitmap));
|
||||
REQUIRE(p4.bitmap_is_full(bitmap));
|
||||
}
|
||||
|
||||
TEST_CASE("bitmap_is_full no bits set", "[puzzle][board]")
|
||||
{
|
||||
puzzle p1(5, 5);
|
||||
puzzle p2(3, 4);
|
||||
puzzle p3(5, 4);
|
||||
puzzle p4(3, 7);
|
||||
uint64_t bitmap = 0;
|
||||
|
||||
REQUIRE_FALSE(p1.bitmap_is_full(bitmap));
|
||||
REQUIRE_FALSE(p2.bitmap_is_full(bitmap));
|
||||
REQUIRE_FALSE(p3.bitmap_is_full(bitmap));
|
||||
REQUIRE_FALSE(p4.bitmap_is_full(bitmap));
|
||||
}
|
||||
|
||||
TEST_CASE("bitmap_is_full necessary bits set", "[puzzle][board]")
|
||||
{
|
||||
puzzle p1(5, 5);
|
||||
puzzle p2(3, 4);
|
||||
puzzle p3(5, 4);
|
||||
puzzle p4(3, 7);
|
||||
|
||||
uint64_t bitmap1 = (1ull << 25) - 1; // 5 * 5
|
||||
uint64_t bitmap2 = (1ull << 12) - 1; // 3 * 4
|
||||
uint64_t bitmap3 = (1ull << 20) - 1; // 5 * 4
|
||||
uint64_t bitmap4 = (1ull << 21) - 1; // 3 * 7
|
||||
|
||||
REQUIRE(p1.bitmap_is_full(bitmap1));
|
||||
REQUIRE(p2.bitmap_is_full(bitmap2));
|
||||
REQUIRE(p3.bitmap_is_full(bitmap3));
|
||||
REQUIRE(p4.bitmap_is_full(bitmap4));
|
||||
}
|
||||
|
||||
TEST_CASE("bitmap_is_full necessary bits not set", "[puzzle][board]")
|
||||
{
|
||||
puzzle p1(5, 5);
|
||||
puzzle p2(3, 4);
|
||||
puzzle p3(5, 4);
|
||||
puzzle p4(3, 7);
|
||||
|
||||
uint64_t bitmap1 = (1ull << 25) - 1; // 5 * 5
|
||||
uint64_t bitmap2 = (1ull << 12) - 1; // 3 * 4
|
||||
uint64_t bitmap3 = (1ull << 20) - 1; // 5 * 4
|
||||
uint64_t bitmap4 = (1ull << 21) - 1; // 3 * 7
|
||||
|
||||
bitmap1 &= ~(1ull << 12);
|
||||
bitmap2 &= ~(1ull << 6);
|
||||
bitmap3 &= ~(1ull << 8);
|
||||
bitmap4 &= ~(1ull << 18);
|
||||
|
||||
REQUIRE_FALSE(p1.bitmap_is_full(bitmap1));
|
||||
REQUIRE_FALSE(p2.bitmap_is_full(bitmap2));
|
||||
REQUIRE_FALSE(p3.bitmap_is_full(bitmap3));
|
||||
REQUIRE_FALSE(p4.bitmap_is_full(bitmap4));
|
||||
}
|
||||
266
test/bitmap_find_first_empty.cpp
Normal file
266
test/bitmap_find_first_empty.cpp
Normal file
@ -0,0 +1,266 @@
|
||||
// ReSharper disable CppLocalVariableMayBeConst
|
||||
#include "puzzle.hpp"
|
||||
|
||||
#include <random>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
static auto board_mask(const int w, const int h) -> uint64_t
|
||||
{
|
||||
const int cells = w * h;
|
||||
if (cells == 64) {
|
||||
return ~0ULL;
|
||||
}
|
||||
return (1ULL << cells) - 1ULL;
|
||||
}
|
||||
|
||||
TEST_CASE("Empty board returns (0,0)", "[puzzle][board]")
|
||||
{
|
||||
puzzle p(5, 5);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(0ULL, x, y));
|
||||
|
||||
REQUIRE(x == 0);
|
||||
REQUIRE(y == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Full board detection respects width*height only", "[puzzle][board]")
|
||||
{
|
||||
auto [w, h] = GENERATE(std::tuple{3, 3}, std::tuple{4, 4}, std::tuple{5, 6}, std::tuple{8, 8});
|
||||
|
||||
puzzle p(w, h);
|
||||
|
||||
uint64_t mask = board_mask(w, h);
|
||||
|
||||
int x = -1, y = -1;
|
||||
|
||||
REQUIRE_FALSE(p.bitmap_find_first_empty(mask, x, y));
|
||||
|
||||
// Bits outside board should not affect fullness
|
||||
REQUIRE_FALSE(p.bitmap_find_first_empty(mask | (~mask), x, y));
|
||||
}
|
||||
|
||||
TEST_CASE("Single empty cell at various positions", "[puzzle][board]")
|
||||
{
|
||||
auto [w, h] = GENERATE(std::tuple{3, 3}, std::tuple{4, 4}, std::tuple{5, 5}, std::tuple{8, 8});
|
||||
|
||||
puzzle p(w, h);
|
||||
|
||||
int cells = w * h;
|
||||
|
||||
auto empty_index = GENERATE_COPY(values<int>({ 0, cells / 2, cells - 1}));
|
||||
|
||||
uint64_t bitmap = board_mask(w, h);
|
||||
bitmap &= ~(1ULL << empty_index);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
|
||||
REQUIRE(x == empty_index % w);
|
||||
REQUIRE(y == empty_index / w);
|
||||
}
|
||||
|
||||
TEST_CASE("Bits outside board are ignored", "[puzzle][board]")
|
||||
{
|
||||
puzzle p(3, 3); // 9 cells
|
||||
|
||||
uint64_t mask = board_mask(3, 3);
|
||||
|
||||
// Board is full
|
||||
uint64_t bitmap = mask;
|
||||
|
||||
// Set extra bits outside 9 cells
|
||||
bitmap |= (1ULL << 20);
|
||||
bitmap |= (1ULL << 63);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE_FALSE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
}
|
||||
|
||||
TEST_CASE("First empty found in forward search branch", "[puzzle][branch]")
|
||||
{
|
||||
puzzle p(4, 4); // 16 cells
|
||||
|
||||
// Only MSB (within board) set
|
||||
uint64_t bitmap = (1ULL << 15);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
|
||||
REQUIRE(x == 0);
|
||||
REQUIRE(y == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Backward search branch finds gap before MSB cluster", "[puzzle][branch]")
|
||||
{
|
||||
puzzle p(4, 4); // 16 cells
|
||||
|
||||
// Set bits 15,14,13 but leave 12 empty
|
||||
uint64_t bitmap = (1ULL << 15) | (1ULL << 14) | (1ULL << 13);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
|
||||
REQUIRE(x == 0);
|
||||
REQUIRE(y == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Rectangular board coordinate mapping", "[puzzle][rect]")
|
||||
{
|
||||
puzzle p(5, 3); // 15 cells
|
||||
|
||||
int empty_index = 11;
|
||||
|
||||
uint64_t bitmap = board_mask(5, 3);
|
||||
bitmap &= ~(1ULL << empty_index);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
|
||||
REQUIRE(x == empty_index % 5);
|
||||
REQUIRE(y == empty_index / 5);
|
||||
}
|
||||
|
||||
TEST_CASE("Non-64-sized board near limit", "[puzzle][limit]")
|
||||
{
|
||||
puzzle p(7, 8); // 56 cells
|
||||
|
||||
uint64_t mask = board_mask(7, 8);
|
||||
|
||||
// Full board should return false
|
||||
int x = -1, y = -1;
|
||||
REQUIRE_FALSE(p.bitmap_find_first_empty(mask, x, y));
|
||||
|
||||
// Clear highest valid cell
|
||||
int empty_index = 55;
|
||||
mask &= ~(1ULL << empty_index);
|
||||
|
||||
REQUIRE(p.bitmap_find_first_empty(mask, x, y));
|
||||
REQUIRE(x == empty_index % 7);
|
||||
REQUIRE(y == empty_index / 7);
|
||||
}
|
||||
|
||||
// --- Oracle: find first zero bit inside board ---
|
||||
static auto oracle_find_first_empty(uint64_t bitmap, int w, int h, int& x, int& y) -> bool
|
||||
{
|
||||
int cells = w * h;
|
||||
|
||||
for (int i = 0; i < cells; ++i) {
|
||||
if ((bitmap & (1ULL << i)) == 0) {
|
||||
x = i % w;
|
||||
y = i / w;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("Oracle validation across board sizes 3x3 to 8x8", "[puzzle][oracle]")
|
||||
{
|
||||
auto [w, h] = GENERATE(std::tuple{3, 3}, std::tuple{4, 4}, std::tuple{5, 5}, std::tuple{6, 6}, std::tuple{7, 7},
|
||||
std::tuple{8, 8}, std::tuple{3, 5}, std::tuple{5, 3}, std::tuple{7, 8}, std::tuple{8, 7});
|
||||
|
||||
puzzle p(w, h);
|
||||
|
||||
uint64_t mask = board_mask(w, h);
|
||||
|
||||
std::mt19937_64 rng(12345);
|
||||
std::uniform_int_distribution<uint64_t> dist(0, UINT64_MAX);
|
||||
|
||||
for (int iteration = 0; iteration < 200; ++iteration) {
|
||||
uint64_t bitmap = dist(rng);
|
||||
|
||||
int ox = -1, oy = -1;
|
||||
bool oracle_result = oracle_find_first_empty(bitmap, w, h, ox, oy);
|
||||
|
||||
int x = -1, y = -1;
|
||||
bool result = p.bitmap_find_first_empty(bitmap, x, y);
|
||||
|
||||
REQUIRE(result == oracle_result);
|
||||
|
||||
if (result) {
|
||||
REQUIRE(x == ox);
|
||||
REQUIRE(y == oy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Bits set outside board only behaves as empty board", "[puzzle][outside]")
|
||||
{
|
||||
puzzle p(3, 3); // 9 cells
|
||||
|
||||
uint64_t bitmap = (1ULL << 40) | (1ULL << 63);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
|
||||
REQUIRE(x == 0);
|
||||
REQUIRE(y == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Last valid cell empty stresses upper bound", "[puzzle][boundary]")
|
||||
{
|
||||
auto [w, h] = GENERATE(std::tuple{4, 4}, std::tuple{5, 6}, std::tuple{7, 8}, std::tuple{8, 8});
|
||||
|
||||
puzzle p(w, h);
|
||||
|
||||
int cells = w * h;
|
||||
uint64_t bitmap = board_mask(w, h);
|
||||
|
||||
// Clear last valid bit
|
||||
bitmap &= ~(1ULL << (cells - 1));
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
|
||||
REQUIRE(x == (cells - 1) % w);
|
||||
REQUIRE(y == (cells - 1) / w);
|
||||
}
|
||||
|
||||
TEST_CASE("Board sizes between 33 and 63 cells", "[puzzle][midrange]")
|
||||
{
|
||||
auto [w, h] = GENERATE(std::tuple{6, 6}, // 36
|
||||
std::tuple{7, 6}, // 42
|
||||
std::tuple{7, 7}, // 49
|
||||
std::tuple{8, 7}, // 56
|
||||
std::tuple{7, 8} // 56
|
||||
);
|
||||
|
||||
puzzle p(w, h);
|
||||
|
||||
int cells = w * h;
|
||||
|
||||
for (int index : {31, 32, cells - 2}) {
|
||||
if (index >= cells) continue;
|
||||
|
||||
uint64_t bitmap = board_mask(w, h);
|
||||
bitmap &= ~(1ULL << index);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
|
||||
REQUIRE(x == index % w);
|
||||
REQUIRE(y == index / w);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple holes choose lowest index", "[puzzle][multiple]")
|
||||
{
|
||||
puzzle p(5, 5);
|
||||
|
||||
uint64_t bitmap = board_mask(5, 5);
|
||||
|
||||
// Clear several positions
|
||||
bitmap &= ~(1ULL << 3);
|
||||
bitmap &= ~(1ULL << 7);
|
||||
bitmap &= ~(1ULL << 12);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
|
||||
// Oracle expectation: index 3
|
||||
REQUIRE(x == 3 % 5);
|
||||
REQUIRE(y == 3 / 5);
|
||||
}
|
||||
1092
test/puzzle.cpp
Normal file
1092
test/puzzle.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user