add multithread game logic. add tests. edit cmake
This commit is contained in:
+352
-12
@@ -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, "
|
||||
|
||||
Reference in New Issue
Block a user