Add HP and SmartBot; refactor bots and World

Introduce health (hp) and damage handling to Robot/BaseRobot
interfaces. Add SmartBot implementation. Refactor bot mining and
movement to use new World APIs (mine, mineAllPositive,
sortPositiveValues). Simplify RandomBot, DigDeepBot, and SortBot
logic, rename/overhaul World rearrange/display, and fix several
indexing/logic bugs and minor cleanups.
This commit is contained in:
2026-04-26 20:41:36 +02:00
parent 2a79d42bd6
commit 42876c1a93
11 changed files with 152 additions and 227 deletions
+7 -6
View File
@@ -1,31 +1,32 @@
#pragma once
#include "Robot.h"
#include <string>
/* Base robot implementation shared by all concrete robot types. */
class BaseRobot : public Robot {
public:
BaseRobot(std::string name, int startX, int startY);
~BaseRobot() override = default;
void move(int direction, const World& world) override;
int decideNextMove(const World& world) const override;
void setPosition(int x, int y) override {
x_ = x;
y_ = y;
}
void setPosition(int x, int y) override;
int getScore() const override { return score_; }
void addScore(int points) override { score_ += points; }
int getX() const override { return x_; }
int getY() const override { return y_; }
std::string getName() const override { return name_; }
int getHp() const override { return hp_;}
bool isAlive() const override { return hp_ > 0; }
void takeDamage( int damage ) override;
protected:
int x_;
int y_;
int score_;
std::string name_;
int hp_;
static constexpr int MaxHp = 100;
};
-3
View File
@@ -9,7 +9,4 @@ class RandomBot : public BaseRobot {
public:
RandomBot ( int startX, int startY );
int mine ( World &world ) override;
private:
int randomNumber ();
};
+3
View File
@@ -25,6 +25,9 @@ class Robot {
virtual int getX () const = 0;
virtual int getY () const = 0;
virtual std::string getName () const = 0;
virtual int getHp() const = 0;
virtual bool isAlive() const = 0;
virtual void takeDamage( int damage ) = 0;
protected:
Robot () = default;
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "../include/BaseRobot.h"
class SmartBot : public BaseRobot {
public:
explicit SmartBot( int startX, int startY, int threshold = 5 );
int mine ( World& world ) override;
int decideNextMove( const World& world ) const override;
private:
int threshold_;
};
+1 -1
View File
@@ -32,7 +32,7 @@ class World {
double positiveAverage ( int x, int y ) const;
int topPositiveSum ( int x, int y, int blocks ) const;
void sortPositiveValuesInColumnAscending ( int x, int y );
void sortPositiveValues ( int x, int y );
void rearrange ();
void display () const;
+44 -69
View File
@@ -2,7 +2,9 @@
#include "../include/World.h"
#include <algorithm>
#include <climits>
#include <limits>
#include <cstdlib>
#include <utility>
BaseRobot::BaseRobot ( std::string name, int startX, int startY )
: x_ ( startX ), y_ ( startY ), score_ ( 0 ), name_ ( std::move ( name ) ) {}
@@ -10,80 +12,53 @@ BaseRobot::BaseRobot ( std::string name, int startX, int startY )
/* Move one step in the requested direction while clamping to world bounds.
Complexity: O(1) */
void BaseRobot::move ( int direction, const World &world ) {
switch ( direction ) {
case 1:
x_ = std::min ( x_ + 1, world.getSizeX() - 1 );
break; // right
case 2:
x_ = std::max ( x_ - 1, 0 );
break; // left
case 3:
y_ = std::min ( y_ + 1, world.getSizeY() - 1 );
break; // down
case 4:
y_ = std::max ( y_ - 1, 0 );
break; // up
default:
break; // no move
}
int nx = x_, ny = y_;
if ( direction == 1 ) ++nx;
else if ( direction == 2 ) --nx;
else if ( direction == 3 ) ++ny;
else if ( direction == 4 ) --ny;
x_ = std::clamp( nx, 0, world.getSizeX() -1 );
y_ = std::clamp( ny, 0, world.getSizeY() -1 );
}
/* Choose the next move by maximizing immediate surface value among
current/adjacent cells; if none are mineable nearby, step toward the
nearest non-empty column in the grid.
Complexity: O(1) for local evaluation, worst O(sizeX * sizeY) fallback scan */
int BaseRobot::decideNextMove ( const World &world ) const {
struct Option {
int dir, val;
};
std::vector<Option> options;
options.reserve ( 5 );
const int dirs[5][3] = {{ 0, 0, 0 }, { 1, 0, 1 }, { -1, 0, 2 }, { 0, 1, 3 }, { 0, -1, 4 }};
int bestDir = 0, bestValue = -1;
for ( const auto& d : dirs ) {
int nx = x_ + d[0], ny = y_ + d[1];
if ( nx < 0 || nx >= world.getSizeX() || ny < 0 || ny >= world.getSizeY() ) continue;
int v = world.getSurfaceValue( nx, ny );
if ( v > bestValue ) {
bestValue = v;
bestDir = d[2];
}
}
if ( bestValue > 0 ) return bestDir;
auto evaluate = [ & ] ( int dx, int dy, int dir ) {
int nx = x_ + dx, ny = y_ + dy;
if ( nx < 0 || nx >= world.getSizeX() )
return;
if ( ny < 0 || ny >= world.getSizeY() )
return;
int surf = world.getSurfaceLevel ( nx, ny );
if ( surf < 0 )
return;
options.push_back ( { dir, world.getValue ( nx, ny, surf ) } );
};
evaluate ( 0, 0, 0 );
evaluate ( 1, 0, 1 );
evaluate ( -1, 0, 2 );
evaluate ( 0, 1, 3 );
evaluate ( 0, -1, 4 );
if ( options.empty() ) {
int bestDist = INT_MAX, tx = x_, ty = y_;
for ( int cx = 0; cx < world.getSizeX(); ++cx ) {
for ( int cy = 0; cy < world.getSizeY(); ++cy ) {
if ( world.getSurfaceLevel ( cx, cy ) >= 0 ) {
int dist = std::abs ( cx - x_ ) + std::abs ( cy - y_ );
if ( dist < bestDist ) {
bestDist = dist;
tx = cx;
ty = cy;
int bestDistance = std::numeric_limits<int>::max();
int targetX = x_, targetY = y_;
for ( int x = 0; x < world.getSizeX(); ++x ) {
for ( int y = 0; y < world.getSizeY(); ++y ) {
if ( world.topPositiveSum( x, y, 1 ) > 0 ) {
int dist = std::abs( x - x_ ) + std::abs( y - y_ );
if ( dist < bestDistance ) {
bestDistance = dist;
targetX = x;
targetY = y;
}
}
}
}
if ( tx == x_ && ty == y_ )
return 0; // grid is truly empty
if ( tx > x_ )
return 1;
if ( tx < x_ )
return 2;
if ( ty > y_ )
return 3;
return 4;
}
auto best = std::max_element ( options.begin(), options.end(),
[] ( const Option &a, const Option &b ) { return a.val < b.val; } );
return best->dir;
if ( bestDistance == std::numeric_limits<int>::max() ) return 0;
if ( targetX < x_ ) return 1;
if ( targetX > x_ ) return 2;
if ( targetY < y_ ) return 3;
if ( targetY < y_ ) return 4;
return 0;
}
void BaseRobot::setPosition( int x, int y ) { x_ = x; y_ = y; }
void BaseRobot::takeDamage( int damage ) {
hp_ -= damage;
if ( hp_ < 0 ) hp_ = 0;
}
+4 -9
View File
@@ -8,17 +8,12 @@ int DigDeepBot::mine ( World &world ) {
from top depth to bottom depth.
Complexity: O(z) where z = column depth */
int total = 0;
int grabbed = 0;
for ( int z = world.getSizeZ() - 1; z >= 0 && grabbed < 3; --z ) {
const int value = world.getValue ( x_, y_, z );
if ( value > 0 ) {
total += value;
world.setValue ( x_, y_, z, 0 );
++grabbed;
for ( int i = 0; i < 3; ++i ) {
int mined = world.mine( x_, y_ );
if ( mined <= 0 ) break;
total += mined;
}
}
score_ += total;
return total;
}
+7 -19
View File
@@ -9,27 +9,15 @@ int RandomBot::mine ( World &world ) {
/* Mine up to a random number of positive blocks from the current column,
scanning from top to bottom and stopping once the random limit is reached.
Complexity: O(z) where z = world height */
static thread_local std::mt19937 rng( std::random_device{}() );
std::uniform_int_distribution<int> countDist( 0, 0 );
int attempts = countDist( rng );
int total = 0;
int grabbed = 0;
const int limit = randomNumber();
for ( int z = world.getSizeZ() - 1; z >= 0 && grabbed < limit; --z ) {
const int value = world.getValue ( x_, y_, z );
if ( value > 0 ) {
total += value;
world.setValue ( x_, y_, z, 0 );
++grabbed;
for ( int i = 0; i < attempts; ++i ) {
int mined = world.mine( x_, y_ );
if ( mined <= 0 ) break;
total += mined;
}
}
score_ += total;
return total;
}
int RandomBot::randomNumber () {
/* Return a uniformly distributed mining limit in [0, 9].
Complexity: O(1) */
static std::mt19937 rng ( std::random_device {}() );
std::uniform_int_distribution<int> distr ( 0, 9 );
return distr ( rng );
}
+28
View File
@@ -0,0 +1,28 @@
#include "../include/SmartBot.h"
#include "../include/World.h"
SmartBot::SmartBot( int startX, int startY, int threshold )
: BaseRobot( "SmartBot", startX, startY ), threshold_( threshold ) {}
int SmartBot::mine( World& world ) {
if ( world.positiveAverage( x_, y_ ) <= threshold_ ) return 0;
int total = world.mineAllPositive( x_, y_ );
score_ += total;
return total;
}
int SmartBot::decideNextMove( const World& world ) const {
const int dirs[5][3] = {{0,0,0},{1,0,1},{-1,0,2},{0,1,3},{0,-1,4}};
int bestDir = 0;
double bestAverage = -1;
for ( const auto& d : dirs ) {
int nx = x_ + d[0], ny = y_ + d[1];
if ( nx < 0 || nx >= world.getSizeX() || ny < 0 || y_ >= world.getSizeY() ) continue;
double avg = world.positiveAverage( nx, ny );
if ( avg > bestAverage ) {
bestAverage = avg;
bestDir = d[2];
}
}
return bestDir;
}
+2 -38
View File
@@ -1,47 +1,11 @@
#include "../include/SortBot.h"
#include "../include/World.h"
#include <algorithm>
#include <vector>
SortBot::SortBot ( int startX, int startY ) : BaseRobot ( "SortBot", startX, startY ) {}
int SortBot::mine ( World &world ) {
/* Collect positive blocks in the current column, sort them so the
highest value becomes the surface block, then mine that block.
Complexity: O(z log z) where z = world depth at this column */
std::vector<std::pair<int, int>> values;
values.reserve ( static_cast<std::size_t> ( world.getSizeZ() ) );
for ( int z = 0; z < world.getSizeZ(); ++z ) {
const int value = world.getValue ( x_, y_, z );
if ( value > 0 ) {
values.emplace_back ( value, z );
}
}
if ( values.empty() ) {
return 0;
}
std::sort ( values.begin(), values.end(), [] ( const auto &a, const auto &b ) { return a.first < b.first; } );
for ( const auto &[ value, z ] : values ) {
( void )value;
world.setValue ( x_, y_, z, 0 );
}
for ( std::size_t i = 0; i < values.size(); ++i ) {
world.setValue ( x_, y_, static_cast<int> ( i ), values[ i ].first );
}
const int surface = world.getSurfaceLevel ( x_, y_ );
if ( surface < 0 ) {
return 0;
}
const int mined = world.getValue ( x_, y_, surface );
world.setValue ( x_, y_, surface, 0 );
world.sortPositiveValues( x_, y_ );
int mined = world.mine( x_, y_ );
score_ += mined;
return mined;
}
+43 -80
View File
@@ -7,8 +7,6 @@
#include <random>
#include <stdexcept>
/* Construct a 3D grid world and initialize all cells.
Complexity: O(x × y × z) */
World::World ( int x, int y, int z )
: sizeX_ ( x ), sizeY_ ( y ), sizeZ_ ( z ),
grid_ ( x, std::vector<std::vector<int>> ( y ) ) {
@@ -16,9 +14,6 @@ World::World ( int x, int y, int z )
init();
}
/* Fill the world with random positive values and occasional effect cells.
Effects are encoded as negative values in [-3, -1].
Complexity: O(x × y × z) */
void World::init () {
std::mt19937 rng ( std::random_device {}() );
std::uniform_int_distribution<int> valueDist ( 1, 9 );
@@ -175,7 +170,7 @@ double World::positiveAverage(int x, int y ) const {
int World::topPositiveSum(int x, int y, int blocks ) const {
validateXY( x, y );
int sum = 0, count =0;
const auto& col = grid_[x][z];
const auto& col = grid_[x][y];
for ( auto it = col.rbegin(); it != col.rend() && count < blocks; ++it ) {
if ( *it > 0 ) {
sum += *it;
@@ -185,90 +180,58 @@ int World::topPositiveSum(int x, int y, int blocks ) const {
return sum;
}
void World::sortPositiveValuesInColumnAscending( int x, int y ) {
void World::sortPositiveValues( int x, int y ) {
validateXY ( x, y );
auto
auto& col = grid_[x][y];
std::vector<int> positives;
for ( auto& v : col ) {
if ( v > 0 ) positives.push_back( v );
}
std::sort( positives.begin(), positives.end() );
auto it = positives.begin();
for ( int& v : col ) {
if ( v > 0 ) v = *it++;
}
}
/* Print a 2D surface view with optional player/computer markers.
Marker legend is shown when at least one robot position is provided.
Complexity: O(x × y × z) due to per-cell surface lookup */
void World::display ( int p1x, int p1y, int p2x, int p2y ) const {
std::cout << "=== SURFACE VIEW ===\n";
if ( p1x >= 0 || p2x >= 0 ) {
std::cout << " P = Player C = Computer ! = both\n";
void World::rearrange() {
std::mt19937 rng{ std::random_device{}() };
std::uniform_int_distribution<int> opDist( 0, 2 );
for ( auto& row : grid_ ) {
for ( auto& col : row ) {
std::vector<int> positives;
for ( int v : col ) {
if ( v > 0 ) positives.push_back( v );
}
int op = opDist( rng );
if ( op == 0 ) std::shuffle( positives.begin(), positives.end(), rng );
else if ( op == 1 ) std::sort( positives.begin(), positives.end());
else std::sort( positives.rbegin(), positives.rend() );
auto it = positives.begin();
for ( int& v : col ) {
if ( v > 0 ) v = *it++;
}
}
}
}
std::cout << " ";
void World::display() const {
std::cout << "\nWorld surface values\n";
for ( int y = 0; y < sizeY_; ++y ) {
std::cout << std::setw ( 4 ) << y;
for ( int x = 0; x < sizeX_; ++x ) {
int v = getSurfaceValue( x, y );
if ( v == 0 ) std::cout << std::setw( 4 ) << "--";
else std::cout << std::setw( 4 ) << v;
}
std::cout << "\n";
for ( int x = 0; x < sizeX_; ++x ) {
std::cout << std::setw ( 2 ) << x << " ";
for ( int y = 0; y < sizeY_; ++y ) {
const int surface = getSurfaceLevel ( x, y );
const int val = ( surface >= 0 ? grid_[ x ][ y ][ surface ] : 0 );
const bool hasP1 = ( x == p1x && y == p1y );
const bool hasP2 = ( x == p2x && y == p2y );
if ( hasP1 && hasP2 ) {
std::cout << std::setw ( 3 ) << val << '!';
} else if ( hasP1 ) {
std::cout << std::setw ( 3 ) << val << 'P';
} else if ( hasP2 ) {
std::cout << std::setw ( 3 ) << val << 'C';
} else {
std::cout << std::setw ( 4 ) << val;
}
}
std::cout << "\n";
std::cout << "=========================\n";
}
}
/* Rearrange each column by applying one random operation:
shuffle, ascending sort, or descending sort.
Zero cells remain untouched; only non-zero entries are rearranged in place.
Complexity: O(x × y × z log z) in the worst case */
void World::rearrange () {
std::mt19937 rng ( std::random_device {}() );
std::uniform_int_distribution<int> opDist ( 0, 2 );
for ( int x = 0; x < sizeX_; ++x ) {
for ( int y = 0; y < sizeY_; ++y ) {
std::vector<int> values;
std::vector<int> positions;
for ( int z = 0; z < sizeZ_; ++z ) {
if ( grid_[ x ][ y ][ z ] > 0 ) { // only minable blocks; effect cells stay in place
values.push_back ( grid_[ x ][ y ][ z ] );
positions.push_back ( z );
}
}
switch ( opDist ( rng ) ) {
case 0:
std::shuffle ( values.begin(), values.end(), rng );
break;
case 1:
std::sort ( values.begin(), values.end() );
break;
case 2:
std::sort ( values.begin(), values.end(), std::greater<int>() );
break;
}
for ( std::size_t i = 0; i < positions.size(); ++i ) {
grid_[ x ][ y ][ positions[ i ] ] = values[ i ];
}
}
void World::display( const std::vector<std::unique_ptr<Robot>>& robots ) const {
display();
for ( const auto& r : robots ) {
std::cout << " " << r->getName() << " @ (" << r->getX() << "," << r->getY() << ")"
<< " score=" << r->getScore() << " hp=" << r->getHp()
<< (r->isAlive() ? "" : " [DEAD]") << "\n";
}
}
/* Find and consume the first negative effect value in column (x, y).
Returns 0 when no effect exists in that column.
Complexity: O(z) */