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:
+7
-6
@@ -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;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,4 @@ class RandomBot : public BaseRobot {
|
||||
public:
|
||||
RandomBot ( int startX, int startY );
|
||||
int mine ( World &world ) override;
|
||||
|
||||
private:
|
||||
int randomNumber ();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
+47
-72
@@ -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 ) } );
|
||||
};
|
||||
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 ( 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; }
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
void BaseRobot::takeDamage( int damage ) {
|
||||
hp_ -= damage;
|
||||
if ( hp_ < 0 ) hp_ = 0;
|
||||
}
|
||||
|
||||
+4
-9
@@ -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
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
+42
-79
@@ -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 );
|
||||
}
|
||||
|
||||
/* 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";
|
||||
}
|
||||
|
||||
std::cout << " ";
|
||||
for ( int y = 0; y < sizeY_; ++y ) {
|
||||
std::cout << std::setw ( 4 ) << y;
|
||||
}
|
||||
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";
|
||||
std::sort( positives.begin(), positives.end() );
|
||||
auto it = positives.begin();
|
||||
for ( int& v : col ) {
|
||||
if ( v > 0 ) v = *it++;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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::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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( int x = 0; x < sizeX_; ++x ) {
|
||||
void World::display() const {
|
||||
std::cout << "\nWorld surface values\n";
|
||||
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 );
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
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) */
|
||||
|
||||
Reference in New Issue
Block a user