Commit 5a0c784e authored by Hubert Denkmair's avatar Hubert Denkmair
Browse files

hold all food in spatialmap

parent f334ff28
......@@ -22,11 +22,9 @@ void Field::createStaticFood(std::size_t count)
real_t x = (*m_positionXDistribution)(*m_rndGen);
real_t y = (*m_positionYDistribution)(*m_rndGen);
std::shared_ptr<Food> newFood =
std::make_shared<Food>(Food::Type::STATIC, Vector2D(x, y), value);
m_updateTracker->foodSpawned(newFood);
m_staticFood.insert( newFood );
Food food {true, Vector2D(x,y), value};
m_updateTracker->foodSpawned(food);
m_foodMap.addElement(food);
}
}
......@@ -52,19 +50,6 @@ void Field::setupRandomness(void)
std::make_unique< std::uniform_real_distribution<real_t> >(0, 1);
}
void Field::updateFoodMap()
{
m_foodMap.clear();
for (auto &f : m_staticFood)
{
m_foodMap.addElement(f);
}
for (auto &f : m_dynamicFood)
{
m_foodMap.addElement(f);
}
}
void Field::updateSnakeSegmentMap()
{
m_segmentInfoMap.clear();
......@@ -105,75 +90,57 @@ void Field::newBot(const std::string &name)
m_bots.insert(bot);
}
void Field::updateFood(void)
void Field::decayFood(void)
{
// step 1: handle static food.
// when static food decays, it is recreated at different coordinates
std::size_t foodToGenerate = 0;
auto sfi = m_staticFood.begin();
while(sfi != m_staticFood.end()) {
(*sfi)->decay();
if((*sfi)->hasDecayed()) {
m_updateTracker->foodDecayed(*sfi);
sfi = m_staticFood.erase(sfi);
foodToGenerate++;
} else {
sfi++;
m_foodMap.processAllElements([this](Food& item)
{
if (item.decay()) {
m_updateTracker->foodDecayed(item);
if (item.shallRegenerate())
{
createStaticFood(1);
}
}
}
createStaticFood(foodToGenerate);
return true;
});
}
// step 2: handle dynamic food
// when dynamic food decays, it is removed permanently
auto dfi = m_dynamicFood.begin();
while(dfi != m_dynamicFood.end()) {
(*dfi)->decay();
if((*dfi)->hasDecayed()) {
m_updateTracker->foodDecayed(*dfi);
dfi = m_dynamicFood.erase(dfi);
} else {
dfi++;
}
void Field::removeFood()
{
for (auto& tile: m_foodMap.getTiles())
{
tile.erase(
std::remove_if(tile.begin(), tile.end(), [](const Food& item) {
return item.shallBeRemoved();
}),
tile.end()
);
}
// step 3: refresh food spatial map for faster access
updateFoodMap();
}
void Field::consumeFood(void)
{
size_t newStaticFood = 0;
for (auto &b: m_bots) {
std::size_t foodToGenerate = 0;
m_foodMap.processElements(
b->getSnake()->getHeadPosition(),
b->getSnake()->getSegmentRadius() * config::SNAKE_CONSUME_RANGE,
[this, &b, &foodToGenerate](const FoodInfo& fi)
[this, &b, &newStaticFood](Food& fi)
{
if (!b->getSnake()->canConsume(fi.food)) { return true; }
b->getSnake()->consume(fi.food);
m_updateTracker->foodConsumed(fi.food, b);
if (m_staticFood.count(fi.food) > 0)
{
m_staticFood.erase(fi.food);
foodToGenerate++;
}
else if (m_dynamicFood.count(fi.food) > 0)
if (b->getSnake()->tryConsume(fi))
{
m_dynamicFood.erase(fi.food);
m_updateTracker->foodConsumed(fi, b);
fi.markForRemove();
if (fi.shallRegenerate())
{
newStaticFood++;
}
}
return true;
}
);
createStaticFood(foodToGenerate); // TODO should this be done outside the bot loop?
}
createStaticFood(newStaticFood);
updateMaxSegmentRadius();
}
......@@ -214,16 +181,6 @@ const Field::BotSet& Field::getBots(void) const
return m_bots;
}
const Field::FoodSet& Field::getStaticFood(void) const
{
return m_staticFood;
}
const Field::FoodSet& Field::getDynamicFood(void) const
{
return m_dynamicFood;
}
void Field::createDynamicFood(real_t totalValue, const Vector2D &center, real_t radius)
{
// create at least 1 food item
......@@ -240,11 +197,9 @@ void Field::createDynamicFood(real_t totalValue, const Vector2D &center, real_t
Vector2D pos = wrapCoords(center + offset);
std::shared_ptr<Food> newFood =
std::make_shared<Food>(Food::Type::DYNAMIC, pos, value);
m_updateTracker->foodSpawned(newFood);
m_dynamicFood.insert( newFood );
Food food {false, pos, value};
m_updateTracker->foodSpawned(food);
m_foodMap.addElement(food);
}
}
......@@ -317,24 +272,6 @@ void Field::debugVisualization(void)
// empty cells are dots
std::fill(rep.begin(), rep.end(), '.');
// draw food
for(auto &f: m_staticFood) {
const Vector2D &pos = f->pos();
char c;
if(f->getValue() > 10) {
c = 'X';
} else {
c = '0' + static_cast<int>(f->getValue());
}
size_t x = static_cast<size_t>(pos.x());
size_t y = static_cast<size_t>(pos.y());
rep[y * intW + x] = c;
}
// draw snakes (head = #, rest = +)
for(auto &b: m_bots) {
std::shared_ptr<Snake> snake = b->getSnake();
......
......@@ -22,7 +22,6 @@ class Field
{
public:
typedef std::set< std::shared_ptr<Bot> > BotSet;
typedef std::set< std::shared_ptr<Food> > FoodSet;
public:
struct SnakeSegmentInfo {
......@@ -36,12 +35,7 @@ class Field
};
typedef SpatialMap<SnakeSegmentInfo, config::SPATIAL_MAP_TILES_X, config::SPATIAL_MAP_TILES_Y> SegmentInfoMap;
struct FoodInfo {
std::shared_ptr<Food> food;
FoodInfo(const std::shared_ptr<Food> &f) : food(f) {}
const Vector2D& pos() const { return food->pos(); }
};
typedef SpatialMap<FoodInfo, config::SPATIAL_MAP_TILES_X, config::SPATIAL_MAP_TILES_Y> FoodInfoMap;
typedef SpatialMap<Food, config::SPATIAL_MAP_TILES_X, config::SPATIAL_MAP_TILES_Y> FoodMap;
private:
const real_t m_width;
......@@ -50,8 +44,6 @@ class Field
real_t m_maxSegmentRadius = 0;
BotSet m_bots;
FoodSet m_staticFood; //!< Food placed randomly in the field.
FoodSet m_dynamicFood; //!< Food generated dynamically by dying snakes.
std::unique_ptr<std::mt19937> m_rndGen;
......@@ -64,13 +56,12 @@ class Field
std::shared_ptr<UpdateTracker> m_updateTracker;
FoodInfoMap m_foodMap;
FoodMap m_foodMap;
SegmentInfoMap m_segmentInfoMap;
void setupRandomness(void);
void createStaticFood(std::size_t count);
void updateFoodMap(void);
void updateSnakeSegmentMap(void);
void updateMaxSegmentRadius(void);
......@@ -84,11 +75,11 @@ class Field
void newBot(const std::string &name);
/*!
* Update all food pieces.
* Decay all food.
*
* This includes decaying them and replacing them when decayed.
* This includes replacing static food when decayed.
*/
void updateFood(void);
void decayFood(void);
/*!
* Make all Snakes consume food in their eating range.
......@@ -97,6 +88,11 @@ class Field
*/
void consumeFood(void);
/*!
* \brief remove decayed and consumed food
*/
void removeFood(void);
/*!
* Move all bots and check collisions.
*/
......@@ -107,16 +103,6 @@ class Field
*/
const BotSet& getBots(void) const;
/*!
* Get the set of static food.
*/
const FoodSet& getStaticFood(void) const;
/*!
* Get the set of dynamic food.
*/
const FoodSet& getDynamicFood(void) const;
/*!
* Add dynamic food equally distributed in the given circle.
*
......@@ -169,6 +155,6 @@ class Field
*/
real_t getMaxSegmentRadius(void) const;
const FoodInfoMap& getFoodInfoMap() const { return m_foodMap; }
const FoodMap& getFoodMap() const { return m_foodMap; }
const SegmentInfoMap& getSegmentInfoMap() const { return m_segmentInfoMap; }
};
......@@ -2,19 +2,22 @@
#include "Food.h"
Food::Food(Type type, const Vector2D &pos, real_t value)
Food::Food(bool shallRegenerate, const Vector2D &pos, real_t value)
: PositionObject(pos)
, m_type(type)
, m_value(value)
, m_shallRegenerate(shallRegenerate)
, m_shallBeRemoved(false)
{
}
void Food::decay(void)
bool Food::decay(void)
{
m_value -= config::FOOD_DECAY_STEP;
m_shallBeRemoved |= m_value <= 0;
return m_shallBeRemoved;
}
bool Food::hasDecayed(void)
bool Food::hasDecayed(void) const
{
return m_value <= 0;
}
......@@ -17,18 +17,20 @@ class Field;
class Food : public IdentifyableObject, public PositionObject
{
public:
enum class Type { STATIC, DYNAMIC };
/*!
* Creates a new food pice at the given position and of the given value.
*/
Food(Type type, const Vector2D &pos, real_t value);
Food(bool shallRegenerate, const Vector2D &pos, real_t value);
void decay(void);
bool hasDecayed(void);
bool decay(void);
bool hasDecayed(void) const;
real_t getValue() const { return m_value; }
bool shallRegenerate() const { return m_shallRegenerate; }
bool shallBeRemoved() const { return m_shallBeRemoved; }
void markForRemove() { m_shallBeRemoved = true; }
private:
Type m_type;
real_t m_value;
bool m_shallRegenerate;
bool m_shallBeRemoved;
};
......@@ -87,8 +87,9 @@ bool Game::OnTimerInterval()
static uint32_t frameNumber = 0;
//std::cout << "Frame number #" << frameNumber++ << std::endl;
m_field->updateFood();
m_field->decayFood();
m_field->consumeFood();
m_field->removeFood();
m_field->moveAllBots();
......
......@@ -125,21 +125,21 @@ std::vector<LuaFoodInfo>& LuaBot::apiFindFood(real_t radius, real_t min_size)
m_luaFoodInfoTable.clear();
auto field = m_bot.getField();
field->getFoodInfoMap().processElements(
field->getFoodMap().processElements(
head_pos,
radius,
[this, field, head_pos, heading_rad, min_size](const Field::FoodInfo& foodinfo)
[this, field, head_pos, heading_rad, min_size](const Food& food)
{
if (foodinfo.food->getValue()>=min_size)
if (food.getValue()>=min_size)
{
Vector2D relPos = field->unwrapRelativeCoords(foodinfo.pos() - head_pos);
Vector2D relPos = field->unwrapRelativeCoords(food.pos() - head_pos);
real_t direction = static_cast<real_t>(atan2(relPos.y(), relPos.x())) - heading_rad;
while (direction<0) { direction += 2*M_PI; }
while (direction>2*M_PI) { direction -= 2*M_PI; }
m_luaFoodInfoTable.emplace_back(
relPos.x(),
relPos.y(),
foodinfo.food->getValue(),
food.getValue(),
direction,
relPos.norm()
);
......
......@@ -52,7 +52,7 @@ namespace MsgPackProtocol
struct WorldUpdateMessage
{
Field::BotSet bots;
Field::FoodSet food;
std::vector<Food> food;
};
struct BotSpawnMessage
......@@ -81,7 +81,7 @@ namespace MsgPackProtocol
struct FoodSpawnMessage
{
std::vector< std::shared_ptr<Food> > new_food;
std::vector<Food> new_food;
};
struct FoodConsumeItem
......
......@@ -24,24 +24,18 @@ MsgPackUpdateTracker::MsgPackUpdateTracker()
reset();
}
void MsgPackUpdateTracker::foodConsumed(
const std::shared_ptr<Food> &food,
void MsgPackUpdateTracker::foodConsumed(const Food &food,
const std::shared_ptr<Bot> &by_bot)
{
MsgPackProtocol::FoodConsumeItem item;
item.bot_id = by_bot->getGUID();
item.food_id = food->getGUID();
m_foodConsumeMessage->items.push_back(item);
m_foodConsumeMessage->items.push_back({food.getGUID(), by_bot->getGUID()});
}
void MsgPackUpdateTracker::foodDecayed(const std::shared_ptr<Food> &food)
void MsgPackUpdateTracker::foodDecayed(const Food &food)
{
m_foodDecayMessage->food_ids.push_back(food->getGUID());
m_foodDecayMessage->food_ids.push_back(food.getGUID());
}
void MsgPackUpdateTracker::foodSpawned(const std::shared_ptr<Food> &food)
void MsgPackUpdateTracker::foodSpawned(const Food &food)
{
m_foodSpawnMessage->new_food.push_back(food);
}
......@@ -101,10 +95,12 @@ void MsgPackUpdateTracker::worldState(const std::shared_ptr<Field> &field)
MsgPackProtocol::WorldUpdateMessage msg;
msg.bots = field->getBots();
msg.food = field->getStaticFood();
const Field::FoodSet &dynFood = field->getDynamicFood();
msg.food.insert(dynFood.begin(), dynFood.end());
msg.food.reserve(1024);
field->getFoodMap().processAllElements([&msg](const Food& food) {
msg.food.push_back(food);
return true;
});
msgpack::sbuffer buf;
msgpack::pack(buf, msg);
......
......@@ -31,12 +31,12 @@ class MsgPackUpdateTracker : public UpdateTracker
/* Implemented functions */
void foodConsumed(
const std::shared_ptr<Food> &food,
const Food &food,
const std::shared_ptr<Bot> &by_bot);
void foodDecayed(const std::shared_ptr<Food> &food);
void foodDecayed(const Food &food);
void foodSpawned(const std::shared_ptr<Food> &food);
void foodSpawned(const Food &food);
void botSpawned(const std::shared_ptr<Bot> &bot);
......
......@@ -64,9 +64,9 @@ real_t Snake::maxRotationPerStep(void)
return 10.0 / (m_segmentRadius/10.0 + 1);
}
void Snake::consume(const std::shared_ptr<Food>& food)
void Snake::consume(const Food& food)
{
m_mass += food->getValue();
m_mass += food.getValue();
ensureSizeMatchesMass();
}
......@@ -179,10 +179,10 @@ real_t Snake::getSegmentRadius(void) const
return m_segmentRadius;
}
bool Snake::canConsume(const std::shared_ptr<Food> &food)
bool Snake::canConsume(const Food &food)
{
const Vector2D &headPos = m_segments[0]->pos();
const Vector2D &foodPos = food->pos();
const Vector2D &foodPos = food.pos();
Vector2D unwrappedFoodPos = m_field->unwrapCoords(foodPos, headPos);
real_t maxRange = m_segmentRadius * config::SNAKE_CONSUME_RANGE;
......
......@@ -62,7 +62,7 @@ class Snake
/*!
* Consume the given food piece.
*/
void consume(const std::shared_ptr<Food>& food);
void consume(const Food &food);
/*!
* Move the snake by one step if boost==false or SNAKE_BOOST_STEPS if boost==true.
......@@ -92,7 +92,17 @@ class Snake
/*!
* Check if this Snake can consume the given Food.
*/
bool canConsume(const std::shared_ptr<Food> &food);
bool canConsume(const Food &food);
bool tryConsume(const Food &food)
{
if (!canConsume(food))
{
return false;
}
consume(food);
return true;
}
/*!
* Convert this Snake to Food. This is normally the last action before the
......
......@@ -8,6 +8,8 @@
template <class T, size_t TILES_X, size_t TILES_Y> class SpatialMap
{
public:
typedef std::vector<T> TileVector;
SpatialMap(size_t fieldSizeX, size_t fieldSizeY, size_t reserveCount)
: m_fieldSizeX(fieldSizeX)
, m_fieldSizeY(fieldSizeY)
......@@ -44,6 +46,32 @@ template <class T, size_t TILES_X, size_t TILES_Y> class SpatialMap
int x2 = static_cast<int>(bottomRight.x() / m_tileSizeX);
int y2 = static_cast<int>(bottomRight.y() / m_tileSizeY);
for (int y=y1; y<=y2; y++)
{
for (int x=x1; x<=x2; x++)
{
for (auto& item: getConstTileVector(x, y))
{
if (!callback(item))
{
return;
}
}
}
}
}
typedef std::function<bool (T&)> NonConstProcessCallback;
void processElements(const Vector2D& center, real_t radius, NonConstProcessCallback callback)
{
const Vector2D topLeft = center - Vector2D { radius, radius };
const Vector2D bottomRight = center + Vector2D { radius, radius };
int x1 = static_cast<int>(topLeft.x() / m_tileSizeX);
int y1 = static_cast<int>(topLeft.y() / m_tileSizeY);
int x2 = static_cast<int>(bottomRight.x() / m_tileSizeX);
int y2 = static_cast<int>(bottomRight.y() / m_tileSizeY);
for (int y=y1; y<=y2; y++)
{
for (int x=x1; x<=x2; x++)
......@@ -59,13 +87,47 @@ template <class T, size_t TILES_X, size_t TILES_Y> class SpatialMap
}
}
void processAllElements(ProcessCallback callback) const
{
for (auto &v: m_tiles)
{
for (auto& item: v)
{
if (!callback(item))
{
return;
}
}
}
}
void processAllElements(NonConstProcessCallback callback)
{
for (auto &v: m_tiles)
{
for (auto& item: v)
{
if (!callback(item))
{
return;
}
}
}
}
std::array<TileVector, TILES_X*TILES_Y>& getTiles() { return m_tiles; }
private:
size_t m_fieldSizeX, m_fieldSizeY;
real_t m_tileSizeX, m_tileSizeY;
typedef std::vector<T> TileVector;
std::array<TileVector, TILES_X*TILES_Y> m_tiles;
const TileVector& getTileVector(int tileX, int tileY) const
TileVector& getTileVector(int tileX, int tileY)
{
return m_tiles[wrap<TILES_Y>(tileY)*TILES_X + wrap<TILES_X>(tileX)];
}
const TileVector& getConstTileVector(int tileX, int tileY) const
{
return m_tiles[wrap<TILES_Y>(tileY)*TILES_X + wrap<TILES_X>(tileX)];
}
......
......@@ -20,7 +20,7 @@ class UpdateTracker
* \param by_bot Pointer to the bot consuming the food.
*/
virtual void foodConsumed(
const std::shared_ptr<Food> &food,
const Food &food,
const std::shared_ptr<Bot> &by_bot) = 0;