add multithread game logic. add tests. edit cmake

This commit is contained in:
2026-04-27 12:13:05 +02:00
parent 727caa7b90
commit 29868a704c
90 changed files with 19017 additions and 196 deletions
+352 -12
View File
@@ -3,10 +3,12 @@
#include "../include/SortBot.h"
#include "../include/DigDeepBot.h"
#include "../include/RandomBot.h"
#include "../include/SmartBot.h"
#include "../include/LookaheadBot.h"
#include <iostream>
#include <sstream>
#include <streambuf>
#include <numeric>
static int s_passed = 0;
static int s_failed = 0;
@@ -55,7 +57,7 @@ static World makeEmptyWorld ( int x = 5, int y = 5, int z = 10 ) {
return w;
}
/* World: getValue / setValue */
/* ── World: getValue / setValue ── */
static void test_world_get_set () {
std::cout << "\n── World: getValue / setValue ──\n";
@@ -114,7 +116,7 @@ static void test_world_get_set () {
} );
}
/* World: getSurfaceLevel */
/* ── World: getSurfaceLevel ── */
static void test_world_surface_level () {
std::cout << "\n── World: getSurfaceLevel ──\n";
@@ -167,7 +169,7 @@ static void test_world_surface_level () {
} );
}
/* World: mine */
/* ── World: mine ── */
static void test_world_mine () {
std::cout << "\n── World: mine ──\n";
@@ -200,7 +202,7 @@ static void test_world_mine () {
} );
}
/* World: checkEffects */
/* ── World: checkEffects ── */
static void test_world_check_effects () {
std::cout << "\n── World: checkEffects ──\n";
@@ -239,7 +241,7 @@ static void test_world_check_effects () {
} );
}
/* World: rearrange (effect semantics) */
/* ── World: rearrange (effect semantics) ── */
static void test_world_rearrange_effects () {
std::cout << "\n── World: rearrange (effect semantics) ──\n";
@@ -279,7 +281,7 @@ static void test_world_rearrange_effects () {
} );
}
/* BaseRobot: move */
/* ── BaseRobot: move ── */
static void test_baserobot_move () {
std::cout << "\n── BaseRobot: move ──\n";
@@ -349,7 +351,7 @@ static void test_baserobot_move () {
} );
}
/* BaseRobot: decideNextMove */
/* ── BaseRobot: decideNextMove ── */
static void test_baserobot_decide () {
std::cout << "\n── BaseRobot: decideNextMove ──\n";
@@ -392,7 +394,7 @@ static void test_baserobot_decide () {
} );
}
/* DigDeepBot: mine */
/* ── DigDeepBot: mine ── */
static void test_digdeepbot_mine () {
std::cout << "\n── DigDeepBot: mine ──\n";
@@ -450,7 +452,7 @@ static void test_digdeepbot_mine () {
} );
}
/* SortBot: mine */
/* ── SortBot: mine ── */
static void test_sortbot_mine () {
std::cout << "\n── SortBot: mine ──\n";
@@ -503,7 +505,7 @@ static void test_sortbot_mine () {
} );
}
/* RandomBot: mine */
/* ── RandomBot: mine ── */
static void test_randombot_mine () {
std::cout << "\n── RandomBot: mine ──\n";
@@ -547,7 +549,7 @@ static void test_randombot_mine () {
} );
}
/* BaseRobot: score / position utilities */
/* ── BaseRobot: score / position utilities ── */
static void test_baserobot_misc () {
std::cout << "\n── BaseRobot: score / position utilities ──\n";
@@ -593,6 +595,338 @@ static void test_baserobot_misc () {
} );
}
/* ── World extra: hasPositiveValues, remainingPositiveSum, positiveAverage,
topPositiveSum, setColumn/getColumn, mineAllPositive,
sortPositiveValues ── */
static void test_world_extra () {
std::cout << "\n── World: hasPositiveValues / remainingPositiveSum ──\n";
TEST ( "empty world has no positive values", {
World w = makeEmptyWorld ( 3, 3, 5 );
ASSERT ( !w.hasPositiveValues () );
} );
TEST ( "world with one positive block has positive values", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 7 );
ASSERT ( w.hasPositiveValues () );
} );
TEST ( "remainingPositiveSum sums all positives across columns", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 3 );
w.setValue ( 1, 1, 0, 5 );
w.setValue ( 2, 2, 0, 8 );
ASSERT_EQ ( w.remainingPositiveSum (), 16 );
} );
TEST ( "remainingPositiveSum ignores negative effect cells", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, -2 );
w.setValue ( 0, 0, 1, 4 );
ASSERT_EQ ( w.remainingPositiveSum (), 4 );
} );
std::cout << "\n── World: positiveAverage ──\n";
TEST ( "returns 0.0 for column with no positives", {
World w = makeEmptyWorld ( 3, 3, 5 );
ASSERT_EQ ( w.positiveAverage ( 0, 0 ), 0.0 );
} );
TEST ( "averages only positive values, skips effects", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, -1 ); // effect; excluded from average
w.setValue ( 0, 0, 1, 4 );
w.setValue ( 0, 0, 2, 6 );
ASSERT_EQ ( w.positiveAverage ( 0, 0 ), 5.0 ); // (4+6)/2
} );
TEST ( "single positive value is its own average", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 9 );
ASSERT_EQ ( w.positiveAverage ( 0, 0 ), 9.0 );
} );
std::cout << "\n── World: topPositiveSum ──\n";
TEST ( "returns 0 for empty column", {
World w = makeEmptyWorld ( 3, 3, 5 );
ASSERT_EQ ( w.topPositiveSum ( 0, 0, 3 ), 0 );
} );
TEST ( "returns sum of top N positive blocks", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 2 );
w.setValue ( 0, 0, 1, 5 );
w.setValue ( 0, 0, 2, 8 );
ASSERT_EQ ( w.topPositiveSum ( 0, 0, 2 ), 13 ); // top 2 from surface: 8+5
} );
TEST ( "blocks=0 returns 0", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 7 );
ASSERT_EQ ( w.topPositiveSum ( 0, 0, 0 ), 0 );
} );
TEST ( "blocks > column size returns all positives", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 3 );
w.setValue ( 0, 0, 1, 4 );
ASSERT_EQ ( w.topPositiveSum ( 0, 0, 10 ), 7 );
} );
std::cout << "\n── World: setColumn / getColumn ──\n";
TEST ( "getColumn returns exactly what setColumn set", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setColumn ( 0, 0, { 3, 5, 7 } );
ASSERT ( w.getColumn ( 0, 0 ) == ( std::vector<int>{ 3, 5, 7 } ) );
} );
TEST ( "setColumn throws when vector too long", {
World w = makeEmptyWorld ( 3, 3, 5 ); // sizeZ_ = 5
bool threw = false;
try { w.setColumn ( 0, 0, { 1, 2, 3, 4, 5, 6 } ); } // size 6 > sizeZ_ 5
catch ( const std::out_of_range& ) { threw = true; }
ASSERT ( threw );
} );
TEST ( "setColumn of empty vector clears the column", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 7 );
w.setColumn ( 0, 0, {} );
ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), -1 );
} );
std::cout << "\n── World: mineAllPositive ──\n";
TEST ( "returns 0 on empty column", {
World w = makeEmptyWorld ( 3, 3, 5 );
ASSERT_EQ ( w.mineAllPositive ( 0, 0 ), 0 );
} );
TEST ( "returns sum of all positive blocks", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 3 );
w.setValue ( 0, 0, 1, 7 );
w.setValue ( 0, 0, 2, 4 );
ASSERT_EQ ( w.mineAllPositive ( 0, 0 ), 14 );
} );
TEST ( "column empty after mineAllPositive with no effects", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 3 );
w.setValue ( 0, 0, 1, 7 );
w.setValue ( 0, 0, 2, 4 );
w.mineAllPositive ( 0, 0 );
ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), -1 );
} );
TEST ( "effect cells remain after mineAllPositive", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, -2 );
w.setValue ( 0, 0, 1, 5 );
ASSERT_EQ ( w.mineAllPositive ( 0, 0 ), 5 );
ASSERT_EQ ( w.checkEffects ( 0, 0 ), -2 );
} );
TEST ( "clear() empties every column", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 4 );
w.setValue ( 1, 1, 2, 7 );
w.clear ();
ASSERT_EQ ( w.getSurfaceLevel ( 0, 0 ), -1 );
ASSERT_EQ ( w.getSurfaceLevel ( 1, 1 ), -1 );
} );
std::cout << "\n── World: sortPositiveValues ──\n";
TEST ( "sort puts highest positive at the surface (top index)", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 7 );
w.setValue ( 0, 0, 1, 2 );
w.setValue ( 0, 0, 2, 5 );
// col = [7,2,5]; sorted ascending in-place -> [2,5,7]; col.back()=7
w.sortPositiveValues ( 0, 0 );
ASSERT_EQ ( w.getSurfaceValue ( 0, 0 ), 7 );
} );
TEST ( "negative cells are not moved by sort", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, -3 ); // effect stays at z=0
w.setValue ( 0, 0, 1, 6 );
w.setValue ( 0, 0, 2, 1 );
w.sortPositiveValues ( 0, 0 );
ASSERT_EQ ( w.getValue ( 0, 0, 0 ), -3 );
} );
TEST ( "total sum unchanged after sort", {
World w = makeEmptyWorld ( 3, 3, 5 );
w.setValue ( 0, 0, 0, 4 );
w.setValue ( 0, 0, 1, 8 );
w.setValue ( 0, 0, 2, 2 );
w.sortPositiveValues ( 0, 0 );
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, 14 );
} );
}
/* ── BaseRobot: hp / takeDamage / isAlive ── */
static void test_baserobot_hp () {
std::cout << "\n── BaseRobot: hp / takeDamage / isAlive ──\n";
TEST ( "initial HP is MaxHp (100)", {
SortBot bot ( 0, 0 );
ASSERT_EQ ( bot.getHp (), 100 );
} );
TEST ( "isAlive() true when HP > 0", {
SortBot bot ( 0, 0 );
ASSERT ( bot.isAlive () );
} );
TEST ( "takeDamage reduces HP", {
SortBot bot ( 0, 0 );
bot.takeDamage ( 30 );
ASSERT_EQ ( bot.getHp (), 70 );
} );
TEST ( "takeDamage clamps HP to 0, never negative", {
SortBot bot ( 0, 0 );
bot.takeDamage ( 200 );
ASSERT_EQ ( bot.getHp (), 0 );
} );
TEST ( "isAlive() false when HP reaches 0", {
SortBot bot ( 0, 0 );
bot.takeDamage ( 200 );
ASSERT ( !bot.isAlive () );
} );
TEST ( "takeDamage(0) leaves HP unchanged", {
SortBot bot ( 0, 0 );
bot.takeDamage ( 0 );
ASSERT_EQ ( bot.getHp (), 100 );
} );
}
/* ── SmartBot: mine ── */
static void test_smartbot_mine () {
std::cout << "\n── SmartBot: mine ──\n";
TEST ( "returns 0 when positiveAverage is at or below threshold", {
World w = makeEmptyWorld ();
w.setValue ( 0, 0, 0, 3 ); // average = 3.0 <= threshold 5 → skip
SmartBot bot ( 0, 0, 5 );
ASSERT_EQ ( bot.mine ( w ), 0 );
} );
TEST ( "mines all positives when average exceeds threshold", {
World w = makeEmptyWorld ();
w.setValue ( 0, 0, 0, 6 );
w.setValue ( 0, 0, 1, 8 ); // average = 7.0 > threshold 5
SmartBot bot ( 0, 0, 5 );
ASSERT_EQ ( bot.mine ( w ), 14 );
} );
TEST ( "score accumulates after successful mine", {
World w = makeEmptyWorld ();
w.setValue ( 0, 0, 0, 6 );
w.setValue ( 0, 0, 1, 8 );
SmartBot bot ( 0, 0, 5 );
bot.mine ( w );
ASSERT_EQ ( bot.getScore (), 14 );
} );
TEST ( "does nothing on empty column (average=0)", {
World w = makeEmptyWorld ();
SmartBot bot ( 0, 0, 5 );
ASSERT_EQ ( bot.mine ( w ), 0 );
} );
}
/* ── SmartBot: decideNextMove ── */
static void test_smartbot_decide () {
std::cout << "\n── SmartBot: decideNextMove ──\n";
TEST ( "returns 0 on empty world", {
World w = makeEmptyWorld ();
SmartBot bot ( 2, 2 );
ASSERT_EQ ( bot.decideNextMove ( w ), 0 );
} );
TEST ( "prefers neighbour with highest average", {
World w = makeEmptyWorld ();
w.setValue ( 3, 2, 0, 9 ); // right of (2,2): positiveAverage = 9.0
w.setValue ( 1, 2, 0, 2 ); // left of (2,2): positiveAverage = 2.0
SmartBot bot ( 2, 2, 0 ); // threshold=0; decideNextMove picks by average
ASSERT_EQ ( bot.decideNextMove ( w ), 1 ); // direction 1 = right (+x)
} );
}
/* ── LookaheadBot: mine ── */
static void test_lookaheadbot_mine () {
std::cout << "\n── LookaheadBot: mine ──\n";
TEST ( "returns 0 on empty column", {
World w = makeEmptyWorld ();
LookaheadBot bot ( 0, 0 );
ASSERT_EQ ( bot.mine ( w ), 0 );
} );
TEST ( "mines up to 3 positive blocks", {
World w = makeEmptyWorld ();
w.setValue ( 0, 0, 0, 1 );
w.setValue ( 0, 0, 1, 2 );
w.setValue ( 0, 0, 2, 3 );
w.setValue ( 0, 0, 3, 4 ); // 4 blocks; only top 3 are mined
LookaheadBot bot ( 0, 0 );
ASSERT_EQ ( bot.mine ( w ), 4 + 3 + 2 ); // top 3: 9
} );
TEST ( "score accumulates", {
World w = makeEmptyWorld ();
w.setValue ( 0, 0, 0, 1 );
w.setValue ( 0, 0, 1, 2 );
w.setValue ( 0, 0, 2, 3 );
w.setValue ( 0, 0, 3, 4 );
LookaheadBot bot ( 0, 0 );
bot.mine ( w );
ASSERT_EQ ( bot.getScore (), 9 );
} );
}
/* ── LookaheadBot: decideNextMove ── */
static void test_lookaheadbot_decide () {
std::cout << "\n── LookaheadBot: decideNextMove ──\n";
TEST ( "returns 0 on empty world", {
World w = makeEmptyWorld ();
LookaheadBot bot ( 2, 2 );
ASSERT_EQ ( bot.decideNextMove ( w ), 0 );
} );
TEST ( "moves toward column with higher topPositiveSum", {
World w = makeEmptyWorld ();
w.setValue ( 3, 2, 0, 9 );
w.setValue ( 3, 2, 1, 8 );
w.setValue ( 3, 2, 2, 7 ); // right of (2,2): topPositiveSum(3)=24
w.setValue ( 1, 2, 0, 2 ); // left of (2,2): topPositiveSum(3)=2
LookaheadBot bot ( 2, 2 );
ASSERT_EQ ( bot.decideNextMove ( w ), 1 ); // direction 1 = right (+x)
} );
}
int main () {
std::cout << "=== Deep Miner Tests ===\n";
@@ -607,6 +941,12 @@ int main () {
test_sortbot_mine ();
test_randombot_mine ();
test_baserobot_misc ();
test_world_extra ();
test_baserobot_hp ();
test_smartbot_mine ();
test_smartbot_decide ();
test_lookaheadbot_mine ();
test_lookaheadbot_decide ();
std::cout << "\n=== Results: "
<< s_passed << " passed, "