From 92f2dc77f2ed119e509a1f168f899e6589854714 Mon Sep 17 00:00:00 2001 From: Florian Egger Date: Mon, 20 Apr 2026 12:31:15 +0200 Subject: [PATCH] Add tests and update CMake to build them Add comprehensive unit tests in tests/test_all.cpp Refactor CMakeLists: set C++17 and CMAKE_CXX_STANDARD_REQUIRED, introduce a SOURCES list used by executables, add a deep_miner_tests target, and set cmake_minimum_required to 3.15 --- CMakeLists.txt | 36 +-- tests/test_all.cpp | 616 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 636 insertions(+), 16 deletions(-) create mode 100644 tests/test_all.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c3fedb..7555711 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,22 +1,26 @@ -cmake_minimum_required(VERSION 4.2) +cmake_minimum_required(VERSION 3.15) project(deep_miner) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) include_directories(include) +set(SOURCES + src/BaseRobot.cpp + src/SortBot.cpp + src/World.cpp + src/DigDeepBot.cpp + src/Game.cpp + src/RandomBot.cpp +) + add_executable(deep_miner - include/BaseRobot.h - include/Robot.h - include/SortBot.h - include/World.h - src/BaseRobot.cpp - src/SortBot.cpp - src/World.cpp - include/Game.h - include/DigDeepBot.h - include/RandomBot.h - src/DigDeepBot.cpp - src/Game.cpp - src/RandomBot.cpp - main.cpp) + ${SOURCES} + main.cpp +) + +add_executable(deep_miner_tests + ${SOURCES} + tests/test_all.cpp +) diff --git a/tests/test_all.cpp b/tests/test_all.cpp new file mode 100644 index 0000000..a088513 --- /dev/null +++ b/tests/test_all.cpp @@ -0,0 +1,616 @@ +#include "../include/World.h" +#include "../include/BaseRobot.h" +#include "../include/SortBot.h" +#include "../include/DigDeepBot.h" +#include "../include/RandomBot.h" + +#include +#include +#include + +static int s_passed = 0; +static int s_failed = 0; + +#define TEST(name, body) \ + do { \ + try { \ + body \ + std::cout << " [PASS] " name "\n"; \ + ++s_passed; \ + } catch ( const std::exception& e ) { \ + std::cout << " [FAIL] " name ": " << e.what() << "\n"; \ + ++s_failed; \ + } catch ( ... ) { \ + std::cout << " [FAIL] " name ": unknown exception\n"; \ + ++s_failed; \ + } \ + } while(0) + +#define ASSERT(expr) \ + if ( !(expr) ) throw std::runtime_error ( "ASSERT failed: " #expr ) + +#define ASSERT_EQ(a, b) \ + if ( (a) != (b) ) { \ + std::ostringstream _msg; \ + _msg << #a << " == " << #b << " (" << (a) << " != " << (b) << ")"; \ + throw std::runtime_error ( "ASSERT_EQ failed: " + _msg.str() ); \ + } + +/* Redirect std::cout to a sink for the duration of the scope. + Useful for robot methods that print as a side-effect. */ +struct SilentOut { + std::streambuf* saved; + SilentOut() { saved = std::cout.rdbuf ( nullptr ); } + ~SilentOut() { std::cout.rdbuf ( saved ); } +}; + +/* Create a World with every cell zeroed so tests start from a known state. + init() is called by the constructor; we then overwrite all cells to 0. */ +static World makeEmptyWorld ( int x = 5, int y = 5, int z = 10 ) { + World w ( x, y, z ); + for ( int xi = 0; xi < x; ++xi ) + for ( int yi = 0; yi < y; ++yi ) + for ( int zi = 0; zi < z; ++zi ) + w.setValue ( xi, yi, zi, 0 ); + return w; +} + +/* World: getValue / setValue */ + +static void test_world_get_set () { + std::cout << "\n── World: getValue / setValue ──\n"; + + TEST ( "set and get round-trips correctly", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 1, 2, 3, 42 ); + ASSERT_EQ ( w.getValue ( 1, 2, 3 ), 42 ); + } ); + + TEST ( "initial zeroed cell returns 0", { + World w = makeEmptyWorld ( 3, 3, 5 ); + ASSERT_EQ ( w.getValue ( 0, 0, 0 ), 0 ); + } ); + + TEST ( "overwrite cell with new value", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, 5 ); + w.setValue ( 0, 0, 0, 9 ); + ASSERT_EQ ( w.getValue ( 0, 0, 0 ), 9 ); + } ); + + TEST ( "getValue throws on x out of range", { + World w = makeEmptyWorld ( 3, 3, 5 ); + bool threw = false; + try { w.getValue ( 3, 0, 0 ); } catch ( const std::out_of_range& ) { threw = true; } + ASSERT ( threw ); + } ); + + TEST ( "getValue throws on y out of range", { + World w = makeEmptyWorld ( 3, 3, 5 ); + bool threw = false; + try { w.getValue ( 0, 3, 0 ); } catch ( const std::out_of_range& ) { threw = true; } + ASSERT ( threw ); + } ); + + TEST ( "getValue throws on z out of range", { + World w = makeEmptyWorld ( 3, 3, 5 ); + bool threw = false; + try { w.getValue ( 0, 0, 5 ); } catch ( const std::out_of_range& ) { threw = true; } + ASSERT ( threw ); + } ); + + TEST ( "setValue throws on negative x", { + World w = makeEmptyWorld ( 3, 3, 5 ); + bool threw = false; + try { w.setValue ( -1, 0, 0, 5 ); } catch ( const std::out_of_range& ) { threw = true; } + ASSERT ( threw ); + } ); + + TEST ( "setValue throws on negative z", { + World w = makeEmptyWorld ( 3, 3, 5 ); + bool threw = false; + try { w.setValue ( 0, 0, -1, 5 ); } catch ( const std::out_of_range& ) { threw = true; } + ASSERT ( threw ); + } ); +} + +/* World: getSurfaceLevel */ + +static void test_world_surface_level () { + std::cout << "\n── World: getSurfaceLevel ──\n"; + + TEST ( "empty column returns -1", { + World w = makeEmptyWorld ( 3, 3, 5 ); + ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), -1 ); + } ); + + TEST ( "single block at z=0 returns 0", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, 7 ); + ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), 0 ); + } ); + + TEST ( "returns highest positive z", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, 3 ); + w.setValue ( 0, 0, 2, 5 ); + w.setValue ( 0, 0, 4, 8 ); + ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), 4 ); + } ); + + TEST ( "ignores negative effect cells", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 3, -2 ); // effect at z=3 + w.setValue ( 0, 0, 1, 6 ); // mineable block at z=1 + ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), 1 ); + } ); + + TEST ( "effect above positive block is ignored; block is still surface", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 1, 4 ); + w.setValue ( 0, 0, 3, -1 ); // effect above + ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), 1 ); + } ); + + TEST ( "all-effect column returns -1", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, -1 ); + w.setValue ( 0, 0, 1, -2 ); + w.setValue ( 0, 0, 2, -3 ); + ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), -1 ); + } ); + + TEST ( "does not bleed across columns", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 4, 9 ); + ASSERT_EQ ( w.getSurfaceLevel ( 1, 0 ), -1 ); + } ); +} + +/* World: mine */ + +static void test_world_mine () { + std::cout << "\n── World: mine ──\n"; + + TEST ( "returns 0 on empty column", { + World w = makeEmptyWorld ( 3, 3, 5 ); + ASSERT_EQ ( w.mine ( 0, 0 ), 0 ); + } ); + + TEST ( "returns surface value and zeroes that cell", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 2, 9 ); + ASSERT_EQ ( w.mine ( 0, 0 ), 9 ); + ASSERT_EQ ( w.getValue ( 0, 0, 2 ), 0 ); + } ); + + TEST ( "lower block becomes new surface after mining", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 1, 4 ); + w.setValue ( 0, 0, 3, 7 ); + w.mine ( 0, 0 ); // mines z=3 + ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), 1 ); + } ); + + TEST ( "column empty after mining sole block", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, 3 ); + w.mine ( 0, 0 ); + ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), -1 ); + } ); +} + +/* World: checkEffects */ + +static void test_world_check_effects () { + std::cout << "\n── World: checkEffects ──\n"; + + TEST ( "returns 0 when no effects in column", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, 5 ); + ASSERT_EQ ( w.checkEffects ( 0, 0 ), 0 ); + } ); + + TEST ( "returns the effect value when present", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 2, -2 ); + ASSERT_EQ ( w.checkEffects ( 0, 0 ), -2 ); + } ); + + TEST ( "consumes the effect cell (becomes 0)", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 1, -3 ); + w.checkEffects ( 0, 0 ); + ASSERT_EQ ( w.getValue ( 0, 0, 1 ), 0 ); + } ); + + TEST ( "second effect still present after first is consumed", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, -1 ); + w.setValue ( 0, 0, 2, -2 ); + w.checkEffects ( 0, 0 ); + const int remaining = w.checkEffects ( 0, 0 ); + ASSERT ( remaining < 0 ); + } ); + + TEST ( "returns 0 for fully empty column", { + World w = makeEmptyWorld ( 3, 3, 5 ); + ASSERT_EQ ( w.checkEffects ( 0, 0 ), 0 ); + } ); +} + +/* World: rearrange (effect semantics) */ + +static void test_world_rearrange_effects () { + std::cout << "\n── World: rearrange (effect semantics) ──\n"; + + TEST ( "effect cells stay at their original z-position across rearrangements", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, -1 ); // effect at z=0 + w.setValue ( 0, 0, 1, 5 ); // minable blocks + w.setValue ( 0, 0, 2, 3 ); + for ( int i = 0; i < 20; ++i ) { + SilentOut s; + w.rearrange (); + ASSERT_EQ ( w.getValue ( 0, 0, 0 ), -1 ); + } + } ); + + TEST ( "positive values are preserved (same sum) after rearrange", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 1, 3 ); + w.setValue ( 0, 0, 3, 7 ); + SilentOut s; + w.rearrange (); + int sum = 0; + for ( int z = 0; z < w.getSizeZ (); ++z ) + if ( w.getValue ( 0, 0, z ) > 0 ) + sum += w.getValue ( 0, 0, z ); + ASSERT_EQ ( sum, 10 ); + } ); + + TEST ( "effect value is preserved after rearrange", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 2, -3 ); + w.setValue ( 0, 0, 4, 6 ); + SilentOut s; + w.rearrange (); + ASSERT_EQ ( w.getValue ( 0, 0, 2 ), -3 ); + } ); +} + +/* BaseRobot: move */ + +static void test_baserobot_move () { + std::cout << "\n── BaseRobot: move ──\n"; + + TEST ( "direction 1 moves right (+x)", { + World w = makeEmptyWorld (); + SortBot bot ( 0, 0 ); + bot.move ( 1, w ); + ASSERT_EQ ( bot.getX (), 1 ); + } ); + + TEST ( "direction 2 moves left (-x)", { + World w = makeEmptyWorld (); + SortBot bot ( 2, 2 ); + bot.move ( 2, w ); + ASSERT_EQ ( bot.getX (), 1 ); + } ); + + TEST ( "direction 3 moves down (+y)", { + World w = makeEmptyWorld (); + SortBot bot ( 0, 0 ); + bot.move ( 3, w ); + ASSERT_EQ ( bot.getY (), 1 ); + } ); + + TEST ( "direction 4 moves up (-y)", { + World w = makeEmptyWorld (); + SortBot bot ( 2, 2 ); + bot.move ( 4, w ); + ASSERT_EQ ( bot.getY (), 1 ); + } ); + + TEST ( "direction 0 stays in place", { + World w = makeEmptyWorld (); + SortBot bot ( 2, 3 ); + bot.move ( 0, w ); + ASSERT_EQ ( bot.getX (), 2 ); + ASSERT_EQ ( bot.getY (), 3 ); + } ); + + TEST ( "clamps at right boundary (x = sizeX - 1)", { + World w = makeEmptyWorld (); + SortBot bot ( 4, 0 ); + bot.move ( 1, w ); + ASSERT_EQ ( bot.getX (), 4 ); + } ); + + TEST ( "clamps at left boundary (x = 0)", { + World w = makeEmptyWorld (); + SortBot bot ( 0, 0 ); + bot.move ( 2, w ); + ASSERT_EQ ( bot.getX (), 0 ); + } ); + + TEST ( "clamps at bottom boundary (y = sizeY - 1)", { + World w = makeEmptyWorld (); + SortBot bot ( 0, 4 ); + bot.move ( 3, w ); + ASSERT_EQ ( bot.getY (), 4 ); + } ); + + TEST ( "clamps at top boundary (y = 0)", { + World w = makeEmptyWorld (); + SortBot bot ( 0, 0 ); + bot.move ( 4, w ); + ASSERT_EQ ( bot.getY (), 0 ); + } ); +} + +/* BaseRobot: decideNextMove */ + +static void test_baserobot_decide () { + std::cout << "\n── BaseRobot: decideNextMove ──\n"; + + TEST ( "returns 0 (stay) when world is fully empty", { + World w = makeEmptyWorld (); + SortBot bot ( 2, 2 ); + ASSERT_EQ ( bot.decideNextMove ( w ), 0 ); + } ); + + TEST ( "chooses neighbour with highest surface value", { + World w = makeEmptyWorld (); + w.setValue ( 3, 2, 0, 9 ); // right neighbour of (2,2): value 9 + w.setValue ( 1, 2, 0, 2 ); // left neighbour of (2,2): value 2 + SortBot bot ( 2, 2 ); + ASSERT_EQ ( bot.decideNextMove ( w ), 1 ); // direction 1 = right (+x) + } ); + + TEST ( "stays on current cell if it has highest value", { + World w = makeEmptyWorld (); + w.setValue ( 2, 2, 0, 9 ); // current position: highest + w.setValue ( 3, 2, 0, 3 ); + w.setValue ( 1, 2, 0, 2 ); + SortBot bot ( 2, 2 ); + ASSERT_EQ ( bot.decideNextMove ( w ), 0 ); // direction 0 = stay + } ); + + TEST ( "fallback: steps right toward the only non-empty column", { + World w = makeEmptyWorld (); + w.setValue ( 4, 4, 0, 5 ); // only non-empty column is at (4,4) + SortBot bot ( 0, 0 ); // no mineable neighbours + ASSERT_EQ ( bot.decideNextMove ( w ), 1 ); // tx=4 > x_=0, so direction 1 (right) + } ); + + TEST ( "fallback: steps down when target is directly below", { + World w = makeEmptyWorld (); + w.setValue ( 0, 4, 0, 5 ); // only non-empty column is at (0,4) + SortBot bot ( 0, 0 ); + ASSERT_EQ ( bot.decideNextMove ( w ), 3 ); // tx==x_ and ty=4 > y_=0, so direction 3 (down) + } ); +} + +/* DigDeepBot: mine */ + +static void test_digdeepbot_mine () { + std::cout << "\n── DigDeepBot: mine ──\n"; + + TEST ( "returns 0 on empty column", { + World w = makeEmptyWorld (); + DigDeepBot bot ( 0, 0 ); + ASSERT_EQ ( bot.mine ( w ), 0 ); + } ); + + TEST ( "mines the only block in the column", { + World w = makeEmptyWorld (); + w.setValue ( 0, 0, 2, 5 ); + DigDeepBot bot ( 0, 0 ); + ASSERT_EQ ( bot.mine ( w ), 5 ); + ASSERT_EQ ( w.getValue ( 0, 0, 2 ), 0 ); + } ); + + TEST ( "mines top 3 blocks and returns their sum", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, 1 ); // z=0: below limit, should remain + w.setValue ( 0, 0, 1, 2 ); // z=1: mined 3rd + w.setValue ( 0, 0, 2, 3 ); // z=2: mined 2nd + w.setValue ( 0, 0, 3, 4 ); // z=3: mined 1st (top) + DigDeepBot bot ( 0, 0 ); + ASSERT_EQ ( bot.mine ( w ), 4 + 3 + 2 ); + ASSERT_EQ ( w.getValue ( 0, 0, 0 ), 1 ); // z=0 untouched + } ); + + TEST ( "mines fewer than 3 if column has only 2 blocks", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 1, 3 ); + w.setValue ( 0, 0, 3, 6 ); + DigDeepBot bot ( 0, 0 ); + ASSERT_EQ ( bot.mine ( w ), 6 + 3 ); + } ); + + TEST ( "skips effect cells (negative values)", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 2, -1 ); // effect; must not be counted + w.setValue ( 0, 0, 3, 7 ); // positive block above + DigDeepBot bot ( 0, 0 ); + ASSERT_EQ ( bot.mine ( w ), 7 ); + } ); + + TEST ( "score accumulates correctly across two mines", { + World w = makeEmptyWorld (); + w.setValue ( 0, 0, 0, 4 ); + w.setValue ( 0, 0, 1, 6 ); + DigDeepBot bot ( 0, 0 ); + bot.mine ( w ); // score = 10 + w.setValue ( 0, 0, 5, 3 ); + bot.mine ( w ); // score = 13 + ASSERT_EQ ( bot.getScore (), 13 ); + } ); +} + +/* SortBot: mine */ + +static void test_sortbot_mine () { + std::cout << "\n── SortBot: mine ──\n"; + + TEST ( "returns 0 on empty column", { + World w = makeEmptyWorld (); + SortBot bot ( 0, 0 ); + ASSERT_EQ ( bot.mine ( w ), 0 ); + } ); + + TEST ( "returns the single block value", { + World w = makeEmptyWorld (); + w.setValue ( 0, 0, 0, 7 ); + SortBot bot ( 0, 0 ); + ASSERT_EQ ( bot.mine ( w ), 7 ); + } ); + + TEST ( "always mines the highest value in the column", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, 3 ); + w.setValue ( 0, 0, 2, 7 ); // highest + w.setValue ( 0, 0, 4, 1 ); + SortBot bot ( 0, 0 ); + ASSERT_EQ ( bot.mine ( w ), 7 ); + } ); + + TEST ( "column has correct remaining sum after mining highest", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, 2 ); + w.setValue ( 0, 0, 1, 5 ); + w.setValue ( 0, 0, 2, 8 ); // mined + SortBot bot ( 0, 0 ); + bot.mine ( w ); + int remaining = 0; + for ( int z = 0; z < w.getSizeZ (); ++z ) + if ( w.getValue ( 0, 0, z ) > 0 ) + remaining += w.getValue ( 0, 0, z ); + ASSERT_EQ ( remaining, 2 + 5 ); + } ); + + TEST ( "score accumulates correctly", { + World w = makeEmptyWorld ( 3, 3, 5 ); + w.setValue ( 0, 0, 0, 4 ); + w.setValue ( 0, 0, 1, 9 ); + SortBot bot ( 0, 0 ); + bot.mine ( w ); // mines 9 + w.setValue ( 0, 0, 2, 6 ); + bot.mine ( w ); // mines 6 (new highest) + ASSERT_EQ ( bot.getScore (), 15 ); + } ); +} + +/* RandomBot: mine */ + +static void test_randombot_mine () { + std::cout << "\n── RandomBot: mine ──\n"; + + TEST ( "returns 0 on empty column regardless of limit", { + World w = makeEmptyWorld (); + RandomBot bot ( 0, 0 ); + for ( int i = 0; i < 10; ++i ) + ASSERT_EQ ( bot.mine ( w ), 0 ); + } ); + + TEST ( "mined value is always non-negative", { + World w = makeEmptyWorld ( 3, 3, 5 ); + for ( int z = 0; z < 5; ++z ) + w.setValue ( 0, 0, z, z + 1 ); + RandomBot bot ( 0, 0 ); + for ( int i = 0; i < 20; ++i ) { + /* Restore column before each mine so the test is repeatable. */ + for ( int z = 0; z < 5; ++z ) + w.setValue ( 0, 0, z, z + 1 ); + ASSERT ( bot.mine ( w ) >= 0 ); + } + } ); + + TEST ( "never mines more than 9 blocks per turn", { + World w = makeEmptyWorld ( 3, 3, 10 ); + for ( int z = 0; z < 10; ++z ) + w.setValue ( 0, 0, z, 1 ); // 10 blocks each worth 1 + RandomBot bot ( 0, 0 ); + const int mined = bot.mine ( w ); + ASSERT ( mined <= 9 ); // limit drawn from [0, 9] + } ); + + TEST ( "score is always non-negative after mining", { + World w = makeEmptyWorld ( 3, 3, 5 ); + for ( int z = 0; z < 5; ++z ) + w.setValue ( 0, 0, z, 3 ); + RandomBot bot ( 0, 0 ); + bot.mine ( w ); + ASSERT ( bot.getScore () >= 0 ); + } ); +} + +/* BaseRobot: score / position utilities */ + +static void test_baserobot_misc () { + std::cout << "\n── BaseRobot: score / position utilities ──\n"; + + TEST ( "initial score is 0", { + SortBot bot ( 0, 0 ); + ASSERT_EQ ( bot.getScore (), 0 ); + } ); + + TEST ( "addScore adds positive points", { + SortBot bot ( 0, 0 ); + bot.addScore ( 10 ); + ASSERT_EQ ( bot.getScore (), 10 ); + } ); + + TEST ( "addScore with negative value decreases score", { + SortBot bot ( 0, 0 ); + bot.addScore ( 20 ); + bot.addScore ( -10 ); + ASSERT_EQ ( bot.getScore (), 10 ); + } ); + + TEST ( "setPosition updates x and y", { + SortBot bot ( 0, 0 ); + bot.setPosition ( 3, 4 ); + ASSERT_EQ ( bot.getX (), 3 ); + ASSERT_EQ ( bot.getY (), 4 ); + } ); + + TEST ( "getName returns correct name for SortBot", { + SortBot bot ( 0, 0 ); + ASSERT ( bot.getName () == "SortBot" ); + } ); + + TEST ( "getName returns correct name for DigDeepBot", { + DigDeepBot bot ( 0, 0 ); + ASSERT ( bot.getName () == "DigDeepBot" ); + } ); + + TEST ( "getName returns correct name for RandomBot", { + RandomBot bot ( 0, 0 ); + ASSERT ( bot.getName () == "RandomBot" ); + } ); +} + +int main () { + std::cout << "=== Deep Miner Tests ===\n"; + + test_world_get_set (); + test_world_surface_level (); + test_world_mine (); + test_world_check_effects (); + test_world_rearrange_effects (); + test_baserobot_move (); + test_baserobot_decide (); + test_digdeepbot_mine (); + test_sortbot_mine (); + test_randombot_mine (); + test_baserobot_misc (); + + std::cout << "\n=== Results: " + << s_passed << " passed, " + << s_failed << " failed ===\n"; + + return s_failed > 0 ? 1 : 0; +}