Zappy Server
C++ game server: world, rules, scheduler, network
Loading...
Searching...
No Matches
World.cpp
Go to the documentation of this file.
1#include "World.hpp"
2
3#include <algorithm>
4#include <set>
5
6World::World(int width, int height, const std::vector<std::string>& teamNames, unsigned int seed)
7 : _width(width), _height(height), _teamNames(teamNames), _rng(seed)
8{
9 _tiles = std::vector<Tile>(width * height);
10}
11
12Tile& World::at(int x, int y)
13{
14 int nx = ((x % _width) + _width) % _width;
15 int ny = ((y % _height) + _height) % _height;
16
17 return _tiles[ny * _width + nx];
18}
19
20const Tile& World::at(int x, int y) const
21{
22 int nx = ((x % _width) + _width) % _width;
23 int ny = ((y % _height) + _height) % _height;
24
25 return _tiles[ny * _width + nx];
26}
27
28std::vector<std::pair<int, int>> World::spawnResources()
29{
30 std::set<int> changedIndices;
31
32 for (int i = 0; i < Resources::TYPE_COUNT; i++) {
33 auto type = static_cast<ResourceType>(i);
34
35 int target = std::max(1, (int)(_width * _height * Resources::density(type)));
36
37 int current = 0;
38 for (auto& tile : _tiles) current += tile.resources[type];
39
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++) {
43 int idx = dist(_rng);
44 _tiles[idx].resources[type]++;
45 changedIndices.insert(idx);
46 }
47 }
48
49 std::vector<std::pair<int, int>> changed;
50
51 changed.reserve(changedIndices.size());
52 for (int idx : changedIndices) changed.emplace_back(idx % _width, idx / _width);
53 for (auto* observer : _observers)
54 for (auto [x, y] : changed) observer->onTileChanged(x, y, at(x, y).resources);
55 return changed;
56}
57
58int World::addPlayer(int connectionId, const std::string& teamName, int x, int y,
59 Orientation orientation)
60{
61 int id = _nextPlayerId++;
62 Player p;
63
64 p.id = id;
65 p.connectionId = connectionId;
66 p.teamName = teamName;
67 p.x = x;
68 p.y = y;
69 p.orientation = orientation;
70 p.inventory.food = 10;
71 _players[id] = p;
72 at(x, y).playerIds.push_back(id);
73 for (auto* observer : _observers)
74 observer->onPlayerAdded(id, x, y, orientation, p.level, teamName);
75 return id;
76}
77
79{
80 auto it = _players.find(id);
81 if (it == _players.end()) return;
82
83 auto& p = it->second;
84 auto& ids = at(p.x, p.y).playerIds;
85
86 ids.erase(std::remove(ids.begin(), ids.end(), id), ids.end());
87 _players.erase(it);
88 for (auto* observer : _observers) observer->onPlayerRemoved(id);
89}
90
91void World::movePlayer(int id, int x, int y)
92{
93 auto& p = _players.at(id);
94 auto& oldIds = at(p.x, p.y).playerIds;
95
96 oldIds.erase(std::remove(oldIds.begin(), oldIds.end(), id), oldIds.end());
97 p.x = ((x % _width) + _width) % _width;
98 p.y = ((y % _height) + _height) % _height;
99 at(p.x, p.y).playerIds.push_back(id);
100 for (auto* observer : _observers) observer->onPlayerMoved(id, p.x, p.y, p.orientation);
101}
102
103void World::turnPlayer(int id, Orientation orientation)
104{
105 auto& p = _players.at(id);
106 p.orientation = orientation;
107 for (auto* observer : _observers) observer->onPlayerMoved(id, p.x, p.y, p.orientation);
108}
109
110bool World::takeResource(int playerId, ResourceType type)
111{
112 auto& p = _players.at(playerId);
113 auto& tile = at(p.x, p.y);
114
115 if (tile.resources[type] <= 0) return false;
116 tile.resources[type]--;
117 p.inventory[type]++;
118 for (auto* observer : _observers)
119 observer->onResourceTaken(playerId, type, p.x, p.y, tile.resources);
120 return true;
121}
122
123bool World::setResource(int playerId, ResourceType type)
124{
125 auto& p = _players.at(playerId);
126
127 if (p.inventory[type] <= 0) return false;
128 p.inventory[type]--;
129 at(p.x, p.y).resources[type]++;
130 for (auto* observer : _observers)
131 observer->onResourceDropped(playerId, type, p.x, p.y, at(p.x, p.y).resources);
132 return true;
133}
134
135bool World::consumeFood(int playerId)
136{
137 auto it = _players.find(playerId);
138 if (it == _players.end()) return false;
139
140 auto& p = it->second;
141 p.inventory.food--;
142 for (auto* observer : _observers)
143 observer->onPlayerInventoryChanged(playerId, p.x, p.y, p.inventory);
144 return p.inventory.food > 0;
145}
146
147Player& World::getPlayer(int id) { return _players.at(id); }
148
149void World::playerBroadcast(int playerId, const std::string& message)
150{
151 for (auto* observer : _observers) observer->onBroadcast(playerId, message);
152}
153
155{
156 auto& ejector = _players.at(ejectorId);
157
158 int dx = 0, dy = 0;
159 switch (ejector.orientation) {
160 case Orientation::N:
161 dy = -1;
162 break;
163 case Orientation::S:
164 dy = 1;
165 break;
166 case Orientation::E:
167 dx = 1;
168 break;
169 case Orientation::W:
170 dx = -1;
171 break;
172 }
173
174 auto& tile = at(ejector.x, ejector.y);
175
176 std::vector<int> toEject;
177 for (int pid : tile.playerIds)
178 if (pid != ejectorId) toEject.push_back(pid);
179
180 for (int pid : toEject) movePlayer(pid, _players.at(pid).x + dx, _players.at(pid).y + dy);
181
182 for (int eid : tile.eggIds) {
183 _eggs.erase(eid);
184 for (auto* observer : _observers) observer->onEggDied(eid);
185 }
186 tile.eggIds.clear();
187
188 for (int pid : toEject) {
189 for (auto* observer : _observers) observer->onPlayerEjected(pid);
190 }
191 return {toEject, dx, dy};
192}
193
194int World::_spawnEgg(const std::string& teamName, int x, int y, int parentPlayerId)
195{
196 int eid = _nextEggId++;
197
198 Egg egg{eid, parentPlayerId, x, y, teamName};
199 _eggs[eid] = egg;
200 at(x, y).eggIds.push_back(eid);
201 if (parentPlayerId < 0)
202 for (auto* observer : _observers) observer->onInitialEggSpawned(eid, teamName, x, y);
203 else
204 for (auto* observer : _observers) observer->onEggLaid(eid, parentPlayerId, x, y);
205 return eid;
206}
207
208void World::spawnInitialEggs(int countPerTeam)
209{
210 std::uniform_int_distribution<int> dx(0, _width - 1);
211 std::uniform_int_distribution<int> dy(0, _height - 1);
212 for (const auto& team : _teamNames)
213 for (int i = 0; i < countPerTeam; i++) _spawnEgg(team, dx(_rng), dy(_rng), -1);
214}
215
216int World::addEgg(int playerId)
217{
218 auto& p = _players.at(playerId);
219 return _spawnEgg(p.teamName, p.x, p.y, p.id);
220}
221
222int World::teamEggCount(const std::string& team) const
223{
224 int count = 0;
225 for (const auto& [id, egg] : _eggs)
226 if (egg.teamName == team) count++;
227 return count;
228}
229
230bool World::hatchEgg(int eggId)
231{
232 auto it = _eggs.find(eggId);
233 if (it == _eggs.end()) return false;
234
235 auto& egg = it->second;
236 auto& ids = at(egg.x, egg.y).eggIds;
237
238 ids.erase(std::remove(ids.begin(), ids.end(), eggId), ids.end());
239 _eggs.erase(it);
240 for (auto* observer : _observers) observer->onEggHatched(eggId);
241 return true;
242}
243
244std::optional<Egg> World::popEggForTeam(const std::string& teamName)
245{
246 for (auto it = _eggs.begin(); it != _eggs.end(); ++it) {
247 if (it->second.teamName == teamName) {
248 Egg egg = it->second;
249 auto& ids = at(egg.x, egg.y).eggIds;
250 ids.erase(std::remove(ids.begin(), ids.end(), egg.id), ids.end());
251 _eggs.erase(it);
252 for (auto* observer : _observers) observer->onEggHatched(egg.id);
253 return egg;
254 }
255 }
256 return std::nullopt;
257}
258
260 {1, 1, 0, 0, 0, 0, 0}, // lvl 1 -> lvl 2
261 {2, 1, 1, 1, 0, 0, 0}, // lvl 2 -> lvl 3
262 {2, 2, 0, 1, 0, 2, 0}, // lvl 3 -> lvl 4
263 {4, 1, 1, 2, 0, 1, 0}, // lvl 4 -> lvl 5
264 {4, 1, 2, 1, 3, 0, 0}, // lvl 5 -> lvl 6
265 {6, 1, 2, 3, 0, 1, 0}, // lvl 6 -> lvl 7
266 {6, 2, 2, 2, 2, 2, 1}, // lvl 7 -> lvl 8
267};
268
269static bool _checkReqs(const Tile& tile, const std::vector<int>& participants, int level,
270 const std::unordered_map<int, Player>& players)
271{
272 const auto& req = INCANTATION_REQS[level - 1];
273
274 // count participants still on tile at correct level
275 int count = 0;
276 for (int pid : participants) {
277 auto it = players.find(pid);
278 if (it == players.end()) return false;
279
280 const auto& p = it->second;
281 if (p.level != level) return false;
282
283 bool onTile = false;
284 for (int tid : tile.playerIds)
285 if (tid == pid) {
286 onTile = true;
287 break;
288 }
289 if (!onTile) return false;
290 count++;
291 }
292 if (count < req.playerCount) return false;
293
294 if (tile.resources[ResourceType::LINEMATE] < req.linemate) return false;
295 if (tile.resources[ResourceType::DERAUMERE] < req.deraumere) return false;
296 if (tile.resources[ResourceType::SIBUR] < req.sibur) return false;
297 if (tile.resources[ResourceType::MENDIANE] < req.mendiane) return false;
298 if (tile.resources[ResourceType::PHIRAS] < req.phiras) return false;
299 if (tile.resources[ResourceType::THYSTAME] < req.thystame) return false;
300
301 return true;
302}
303
304std::optional<std::vector<int>> World::startIncantation(int playerId)
305{
306 auto& initiator = _players.at(playerId);
307 int level = initiator.level;
308 if (level < 1 || level > 7) return std::nullopt;
309
310 auto& tile = at(initiator.x, initiator.y);
311 const auto& req = INCANTATION_REQS[level - 1];
312
313 std::vector<int> participants;
314 for (int pid : tile.playerIds) {
315 auto& p = _players.at(pid);
316 if (p.level == level) participants.push_back(pid);
317 }
318
319 if (static_cast<int>(participants.size()) < req.playerCount) return std::nullopt;
320
321 if (!_checkReqs(tile, participants, level, _players)) return std::nullopt;
322
323 for (int pid : participants) _players.at(pid).isIncanting = true;
324
325 for (auto* observer : _observers)
326 observer->onIncantationStart(initiator.x, initiator.y, initiator.level, participants);
327
328 return participants;
329}
330
331bool World::finalizeIncantation(int x, int y, const std::vector<int>& participantIds)
332{
333 if (participantIds.empty()) return false;
334
335 auto it = _players.find(participantIds[0]);
336 if (it == _players.end()) {
337 for (int pid : participantIds) {
338 auto p = _players.find(pid);
339 if (p != _players.end()) p->second.isIncanting = false;
340 }
341 for (auto* obs : _observers) obs->onIncantationEnd(x, y, false);
342 return false;
343 }
344
345 int level = it->second.level;
346 auto& tile = at(it->second.x, it->second.y);
347
348 if (!_checkReqs(tile, participantIds, level, _players)) {
349 for (int pid : participantIds) {
350 auto p = _players.find(pid);
351 if (p != _players.end()) p->second.isIncanting = false;
352 }
353 for (auto* obs : _observers) obs->onIncantationEnd(x, y, false);
354 return false;
355 }
356
357 const auto& req = INCANTATION_REQS[level - 1];
358 tile.resources[ResourceType::LINEMATE] -= req.linemate;
359 tile.resources[ResourceType::DERAUMERE] -= req.deraumere;
360 tile.resources[ResourceType::SIBUR] -= req.sibur;
361 tile.resources[ResourceType::MENDIANE] -= req.mendiane;
362 tile.resources[ResourceType::PHIRAS] -= req.phiras;
363 tile.resources[ResourceType::THYSTAME] -= req.thystame;
364
365 int newLevel = level + 1;
366 for (int pid : participantIds) {
367 auto p = _players.find(pid);
368 if (p != _players.end()) {
369 p->second.level = newLevel;
370 p->second.isIncanting = false;
371 }
372 }
373
374 for (auto* obs : _observers) obs->onIncantationEnd(x, y, true);
375 for (int pid : participantIds) {
376 if (_players.count(pid))
377 for (auto* obs : _observers) obs->onPlayerLevelUp(pid, newLevel);
378 }
379
380 if (!_gameEnded) {
381 auto winner = checkWin();
382 if (winner) {
383 _gameEnded = true;
384 _winner = winner;
385 for (auto* obs : _observers) obs->onGameEnd(*winner);
386 }
387 }
388
389 return true;
390}
391
392int World::teamPlayerCount(const std::string& team) const
393{
394 int count = 0;
395 for (auto& [id, p] : _players)
396 if (p.teamName == team) count++;
397 return count;
398}
399
400int World::width() const { return _width; }
401int World::height() const { return _height; }
402const std::unordered_map<int, Player>& World::getPlayers() const { return _players; }
403const std::unordered_map<int, Egg>& World::getEggs() const { return _eggs; }
404
405std::optional<std::string> World::checkWin() const
406{
407 std::unordered_map<std::string, int> level8count;
408 for (auto& [id, p] : _players)
409 if (p.level == 8) level8count[p.teamName]++;
410
411 for (auto& [team, count] : level8count)
412 if (count >= 6) return team;
413
414 return std::nullopt;
415}
416
417bool World::isGameEnded() const { return _gameEnded; }
418const std::optional<std::string>& World::winner() const { return _winner; }
419
420void World::addWorldObserver(IWorldObserver* observer) { _observers.push_back(observer); }
Orientation
ResourceType
Enumerate the different types of resources in the game.
Definition Resources.hpp:12
static std::string id(int n)
Definition Serializer.cpp:3
static bool _checkReqs(const Tile &tile, const std::vector< int > &participants, int level, const std::unordered_map< int, Player > &players)
Definition World.cpp:269
static const IncantationReq INCANTATION_REQS[7]
Definition World.cpp:259
Observer hooks for game-state changes (Observer pattern).
static float density(ResourceType type)
static constexpr int TYPE_COUNT
int deraumere
Definition Resources.hpp:21
std::unordered_map< int, Egg > _eggs
Definition World.hpp:126
std::vector< IWorldObserver * > _observers
Definition World.hpp:133
bool takeResource(int playerId, ResourceType type)
Definition World.cpp:110
void movePlayer(int id, int x, int y)
Definition World.cpp:91
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,...
Definition World.cpp:331
const std::unordered_map< int, Player > & getPlayers() const
Definition World.cpp:402
std::optional< Egg > popEggForTeam(const std::string &teamName)
Definition World.cpp:244
void turnPlayer(int id, Orientation orientation)
Definition World.cpp:103
bool hatchEgg(int eggId)
Definition World.cpp:230
std::mt19937 _rng
Definition World.hpp:128
void spawnInitialEggs(int countPerTeam)
Spawn countPerTeam eggs for every team at random tiles (server startup).
Definition World.cpp:208
int teamPlayerCount(const std::string &team) const
Definition World.cpp:392
int _nextPlayerId
Definition World.hpp:129
Tile & at(int x, int y)
Access a tile by position. Map is toroidal, coordinates wrap.
Definition World.cpp:12
void addWorldObserver(IWorldObserver *observer)
Definition World.cpp:420
std::vector< Tile > _tiles
Definition World.hpp:124
int _spawnEgg(const std::string &teamName, int x, int y, int parentPlayerId)
Definition World.cpp:194
EjectResult ejectPlayers(int ejectorId)
Definition World.cpp:154
std::unordered_map< int, Player > _players
Definition World.hpp:125
int addPlayer(int connectionId, const std::string &teamName, int x, int y, Orientation orientation)
Definition World.cpp:58
bool consumeFood(int playerId)
Consume one unit of food from playerId (starvation tick). Fires onPlayerInventoryChanged so observers...
Definition World.cpp:135
World(int width, int height, const std::vector< std::string > &teamNames, unsigned int seed)
Definition World.cpp:6
bool _gameEnded
Definition World.hpp:131
void playerBroadcast(int playerId, const std::string &message)
Fire onBroadcast to observers (GUI animation, logging). No state change.
Definition World.cpp:149
std::optional< std::string > checkWin() const
Definition World.cpp:405
std::vector< std::pair< int, int > > spawnResources()
Definition World.cpp:28
bool isGameEnded() const
True once a team has won. Game logic stops reacting to AI commands.
Definition World.cpp:417
int _width
Definition World.hpp:122
const std::unordered_map< int, Egg > & getEggs() const
Definition World.cpp:403
void removePlayer(int id)
Definition World.cpp:78
int height() const
Definition World.cpp:401
bool setResource(int playerId, ResourceType type)
Definition World.cpp:123
int _height
Definition World.hpp:123
std::vector< std::string > _teamNames
Definition World.hpp:127
std::optional< std::vector< int > > startIncantation(int playerId)
Validate and start an incantation for playerId. Returns the list of participant IDs on success,...
Definition World.cpp:304
std::optional< std::string > _winner
Definition World.hpp:132
Player & getPlayer(int id)
Definition World.cpp:147
int addEgg(int playerId)
Definition World.cpp:216
int teamEggCount(const std::string &team) const
Definition World.cpp:222
int width() const
Definition World.cpp:400
int _nextEggId
Definition World.hpp:130
const std::optional< std::string > & winner() const
Winning team name, set when isGameEnded() becomes true.
Definition World.cpp:418
Represents an egg in the game, which has an ID, position (x, y), and it's associated with a team name...
Definition Egg.hpp:9
int y
Definition Egg.hpp:13
int id
Definition Egg.hpp:10
int x
Definition Egg.hpp:12
Result of an Eject command. dx/dy encode the push direction (used to notify ejected players).
Definition World.hpp:17
Stone and player requirements for one incantation level.
Definition World.hpp:27
Represents a player in the game Each player has an ID, position (x, y), orientation,...
Definition Player.hpp:15
int id
Definition Player.hpp:16
Resources inventory
Definition Player.hpp:23
int y
Definition Player.hpp:18
int level
Definition Player.hpp:21
int connectionId
Definition Player.hpp:19
Orientation orientation
Definition Player.hpp:20
std::string teamName
Definition Player.hpp:22
int x
Definition Player.hpp:17
Represents a tile on the game map, containing resources, player IDs, and egg IDs.
Definition Tile.hpp:10
Resources resources
Definition Tile.hpp:11
std::vector< int > eggIds
Definition Tile.hpp:13
std::vector< int > playerIds
Definition Tile.hpp:12