6World::World(
int width,
int height,
const std::vector<std::string>& teamNames,
unsigned int seed)
7 : _width(width), _height(height), _teamNames(teamNames), _rng(seed)
30 std::set<int> changedIndices;
38 for (
auto& tile :
_tiles) current += tile.resources[type];
40 int deficit = target - current;
41 std::uniform_int_distribution<int> dist(0, (
int)
_tiles.size() - 1);
42 for (
int j = 0; j < deficit; j++) {
44 _tiles[idx].resources[type]++;
45 changedIndices.insert(idx);
49 std::vector<std::pair<int, int>> changed;
51 changed.reserve(changedIndices.size());
52 for (
int idx : changedIndices) changed.emplace_back(idx %
_width, idx /
_width);
54 for (
auto [x, y] : changed) observer->onTileChanged(x, y,
at(x, y).
resources);
74 observer->onPlayerAdded(
id, x, y, orientation, p.
level, teamName);
86 ids.erase(std::remove(ids.begin(), ids.end(),
id), ids.end());
88 for (
auto* observer :
_observers) observer->onPlayerRemoved(
id);
96 oldIds.erase(std::remove(oldIds.begin(), oldIds.end(),
id), oldIds.end());
100 for (
auto* observer :
_observers) observer->onPlayerMoved(
id, p.x, p.y, p.orientation);
106 p.orientation = orientation;
107 for (
auto* observer :
_observers) observer->onPlayerMoved(
id, p.x, p.y, p.orientation);
113 auto& tile =
at(p.x, p.y);
115 if (tile.resources[type] <= 0)
return false;
116 tile.resources[type]--;
119 observer->onResourceTaken(playerId, type, p.x, p.y, tile.resources);
127 if (p.inventory[type] <= 0)
return false;
131 observer->onResourceDropped(playerId, type, p.x, p.y,
at(p.x, p.y).
resources);
138 if (it ==
_players.end())
return false;
140 auto& p = it->second;
143 observer->onPlayerInventoryChanged(playerId, p.x, p.y, p.inventory);
144 return p.inventory.food > 0;
151 for (
auto* observer :
_observers) observer->onBroadcast(playerId, message);
156 auto& ejector =
_players.at(ejectorId);
159 switch (ejector.orientation) {
174 auto& tile =
at(ejector.x, ejector.y);
176 std::vector<int> toEject;
177 for (
int pid : tile.playerIds)
178 if (pid != ejectorId) toEject.push_back(pid);
182 for (
int eid : tile.eggIds) {
184 for (
auto* observer :
_observers) observer->onEggDied(eid);
188 for (
int pid : toEject) {
189 for (
auto* observer :
_observers) observer->onPlayerEjected(pid);
191 return {toEject, dx, dy};
198 Egg egg{eid, parentPlayerId, x, y, teamName};
201 if (parentPlayerId < 0)
202 for (
auto* observer :
_observers) observer->onInitialEggSpawned(eid, teamName, x, y);
204 for (
auto* observer :
_observers) observer->onEggLaid(eid, parentPlayerId, x, y);
210 std::uniform_int_distribution<int> dx(0,
_width - 1);
211 std::uniform_int_distribution<int> dy(0,
_height - 1);
219 return _spawnEgg(p.teamName, p.x, p.y, p.id);
225 for (
const auto& [
id, egg] :
_eggs)
226 if (egg.teamName == team) count++;
232 auto it =
_eggs.find(eggId);
233 if (it ==
_eggs.end())
return false;
235 auto& egg = it->second;
236 auto& ids =
at(egg.x, egg.y).
eggIds;
238 ids.erase(std::remove(ids.begin(), ids.end(), eggId), ids.end());
240 for (
auto* observer :
_observers) observer->onEggHatched(eggId);
246 for (
auto it =
_eggs.begin(); it !=
_eggs.end(); ++it) {
247 if (it->second.teamName == teamName) {
248 Egg egg = it->second;
250 ids.erase(std::remove(ids.begin(), ids.end(), egg.
id), ids.end());
252 for (
auto* observer :
_observers) observer->onEggHatched(egg.
id);
260 {1, 1, 0, 0, 0, 0, 0},
261 {2, 1, 1, 1, 0, 0, 0},
262 {2, 2, 0, 1, 0, 2, 0},
263 {4, 1, 1, 2, 0, 1, 0},
264 {4, 1, 2, 1, 3, 0, 0},
265 {6, 1, 2, 3, 0, 1, 0},
266 {6, 2, 2, 2, 2, 2, 1},
269static bool _checkReqs(
const Tile& tile,
const std::vector<int>& participants,
int level,
270 const std::unordered_map<int, Player>& players)
276 for (
int pid : participants) {
277 auto it = players.find(pid);
278 if (it == players.end())
return false;
280 const auto& p = it->second;
281 if (p.level != level)
return false;
289 if (!onTile)
return false;
292 if (count < req.playerCount)
return false;
306 auto& initiator =
_players.at(playerId);
307 int level = initiator.level;
308 if (level < 1 || level > 7)
return std::nullopt;
310 auto& tile =
at(initiator.x, initiator.y);
313 std::vector<int> participants;
314 for (
int pid : tile.playerIds) {
316 if (p.level == level) participants.push_back(pid);
319 if (
static_cast<int>(participants.size()) < req.playerCount)
return std::nullopt;
323 for (
int pid : participants)
_players.at(pid).isIncanting =
true;
326 observer->onIncantationStart(initiator.x, initiator.y, initiator.level, participants);
333 if (participantIds.empty())
return false;
335 auto it =
_players.find(participantIds[0]);
337 for (
int pid : participantIds) {
339 if (p !=
_players.end()) p->second.isIncanting =
false;
341 for (
auto* obs :
_observers) obs->onIncantationEnd(x, y,
false);
345 int level = it->second.level;
346 auto& tile =
at(it->second.x, it->second.y);
349 for (
int pid : participantIds) {
351 if (p !=
_players.end()) p->second.isIncanting =
false;
353 for (
auto* obs :
_observers) obs->onIncantationEnd(x, y,
false);
365 int newLevel = level + 1;
366 for (
int pid : participantIds) {
369 p->second.level = newLevel;
370 p->second.isIncanting =
false;
374 for (
auto* obs :
_observers) obs->onIncantationEnd(x, y,
true);
375 for (
int pid : participantIds) {
377 for (
auto* obs :
_observers) obs->onPlayerLevelUp(pid, newLevel);
396 if (p.teamName == team) count++;
407 std::unordered_map<std::string, int> level8count;
409 if (p.level == 8) level8count[p.teamName]++;
411 for (
auto& [team, count] : level8count)
412 if (count >= 6)
return team;
ResourceType
Enumerate the different types of resources in the game.
static std::string id(int n)
static bool _checkReqs(const Tile &tile, const std::vector< int > &participants, int level, const std::unordered_map< int, Player > &players)
static const IncantationReq INCANTATION_REQS[7]
Observer hooks for game-state changes (Observer pattern).
static float density(ResourceType type)
static constexpr int TYPE_COUNT
std::unordered_map< int, Egg > _eggs
std::vector< IWorldObserver * > _observers
bool takeResource(int playerId, ResourceType type)
void movePlayer(int id, int x, int y)
bool finalizeIncantation(int x, int y, const std::vector< int > &participantIds)
Finalize an incantation after the 300/f second delay. Re-checks prerequisites. On success,...
const std::unordered_map< int, Player > & getPlayers() const
std::optional< Egg > popEggForTeam(const std::string &teamName)
void turnPlayer(int id, Orientation orientation)
void spawnInitialEggs(int countPerTeam)
Spawn countPerTeam eggs for every team at random tiles (server startup).
int teamPlayerCount(const std::string &team) const
Tile & at(int x, int y)
Access a tile by position. Map is toroidal, coordinates wrap.
void addWorldObserver(IWorldObserver *observer)
std::vector< Tile > _tiles
int _spawnEgg(const std::string &teamName, int x, int y, int parentPlayerId)
EjectResult ejectPlayers(int ejectorId)
std::unordered_map< int, Player > _players
int addPlayer(int connectionId, const std::string &teamName, int x, int y, Orientation orientation)
bool consumeFood(int playerId)
Consume one unit of food from playerId (starvation tick). Fires onPlayerInventoryChanged so observers...
World(int width, int height, const std::vector< std::string > &teamNames, unsigned int seed)
void playerBroadcast(int playerId, const std::string &message)
Fire onBroadcast to observers (GUI animation, logging). No state change.
std::optional< std::string > checkWin() const
std::vector< std::pair< int, int > > spawnResources()
bool isGameEnded() const
True once a team has won. Game logic stops reacting to AI commands.
const std::unordered_map< int, Egg > & getEggs() const
void removePlayer(int id)
bool setResource(int playerId, ResourceType type)
std::vector< std::string > _teamNames
std::optional< std::vector< int > > startIncantation(int playerId)
Validate and start an incantation for playerId. Returns the list of participant IDs on success,...
std::optional< std::string > _winner
Player & getPlayer(int id)
int teamEggCount(const std::string &team) const
const std::optional< std::string > & winner() const
Winning team name, set when isGameEnded() becomes true.
Represents an egg in the game, which has an ID, position (x, y), and it's associated with a team name...
Result of an Eject command. dx/dy encode the push direction (used to notify ejected players).
Stone and player requirements for one incantation level.
Represents a player in the game Each player has an ID, position (x, y), orientation,...
Represents a tile on the game map, containing resources, player IDs, and egg IDs.
std::vector< int > eggIds
std::vector< int > playerIds