Zappy Server
C++ game server: world, rules, scheduler, network
Loading...
Searching...
No Matches
CommandDispatcher.cpp
Go to the documentation of this file.
2
3#include <chrono>
4#include <variant>
5
9
11 const ServerConfig& config, Scheduler& scheduler)
12 : _clients(clients),
13 _world(world),
14 _notifier(notifier),
15 _scheduler(scheduler),
16 _config(config),
17 _clock(config.freq),
18 _handshakeHandler(
19 clients, world, notifier, config, _clock,
20 [this](int connectionId, int playerId) { this->_onAiJoined(connectionId, playerId); })
21{
22}
23
24std::chrono::microseconds CommandDispatcher::gameElapsed() const { return _clock.elapsed(); }
25
26double CommandDispatcher::gameTicks() const { return _clock.ticks(); }
27
28std::optional<GameClock::Stamp> CommandDispatcher::teamJoin(const std::string& team) const
29{
30 return _clock.joinOf(team);
31}
32
33void CommandDispatcher::_onAiJoined(int connectionId, int playerId)
34{
35 if (_world.getPlayers().count(playerId)) _clock.recordJoin(_world.getPlayer(playerId).teamName);
36 _startStarvationTimer(connectionId, playerId);
37}
38
40{
42}
43
44void CommandDispatcher::dispatch(int connectionId, const std::string& line)
45{
46 auto& conn = _clients.getConnection(connectionId);
47 if (conn.type() == ClientType::PENDING)
48 _handshakeHandler.onLine(connectionId, line);
49 else if (conn.type() == ClientType::AI)
50 _dispatchAi(connectionId, line);
51 else if (conn.type() == ClientType::GUI)
52 _dispatchGui(connectionId, line);
53}
54
55void CommandDispatcher::onDisconnect(int connectionId)
56{
57 auto& conn = _clients.getConnection(connectionId);
58 if (conn.type() == ClientType::GUI) {
59 _notifier.removeGui(connectionId);
60 } else if (conn.type() == ClientType::AI) {
61 int playerId = conn.playerId();
62 if (_world.getPlayers().count(playerId)) {
63 _world.removePlayer(playerId);
64 }
65 }
66 _queues.erase(connectionId);
67 _hasActive.erase(connectionId);
68}
69
70void CommandDispatcher::_dispatchGui(int connectionId, const std::string& line)
71{
72 auto req = GuiParser::parse(line);
73 if (!req) {
74 _clients.send(connectionId, "suc\n");
75 return;
76 }
77
78 std::visit(overloaded{
79 [&](Gui::Msz) { _handleMsz(connectionId); },
80 [&](Gui::Bct c) { _handleBct(connectionId, c.x, c.y); },
81 [&](Gui::Mct) { _handleMct(connectionId); },
82 [&](Gui::Tna) { _handleTna(connectionId); },
83 [&](Gui::Ppo p) { _handlePpo(connectionId, p.id); },
84 [&](Gui::Plv p) { _handlePlv(connectionId, p.id); },
85 [&](Gui::Pin p) { _handlePin(connectionId, p.id); },
86 [&](Gui::Sgt) { _handleSgt(connectionId); },
87 [&](Gui::Sst s) { _handleSst(s.freq); },
88 [&](Gui::Stu) { _handleStu(connectionId); },
89 [&](auto&) { _clients.send(connectionId, "suc\n"); },
90 },
91 *req);
92}
93
94void CommandDispatcher::_dispatchAi(int connectionId, const std::string& line)
95{
96 // Game over: world is frozen, AI commands are ignored (players stay put so the
97 // GUI keeps showing the final state).
98 if (_world.isGameEnded()) return;
99
100 auto cmd = AiParser::parse(line);
101 if (!cmd) {
102 _clients.send(connectionId, "ko\n");
103 return;
104 }
105
106 if (_queues[connectionId].size() >= 10) return;
107 _queues[connectionId].push_back(*cmd);
108 if (!_hasActive[connectionId]) _executeNext(connectionId);
109}
110
112{
113 auto& queue = _queues[connectionId];
114 if (queue.empty()) {
115 _hasActive[connectionId] = false;
116 return;
117 }
118
119 _hasActive[connectionId] = true;
120 Ai::Command cmd = queue.front();
121 queue.pop_front();
122
123 std::visit(overloaded{
124 [&](Ai::Forward) { _handleForward(connectionId); },
125 [&](Ai::Right) { _handleRight(connectionId); },
126 [&](Ai::Left) { _handleLeft(connectionId); },
127 [&](Ai::Look) { _handleLook(connectionId); },
128 [&](Ai::Inventory) { _handleInventory(connectionId); },
129 [&](Ai::Broadcast& b) { _handleBroadcast(connectionId, b.message); },
130 [&](Ai::Fork) { _handleFork(connectionId); },
131 [&](Ai::Eject) { _handleEject(connectionId); },
132 [&](Ai::Take& t) { _handleTake(connectionId, t.resource); },
133 [&](Ai::Set& s) { _handleSet(connectionId, s.resource); },
134 [&](Ai::Incantation) { _handleIncantation(connectionId); },
135 [&](Ai::ConnectNbr) { _handleConnectNbr(connectionId); },
136 },
137 cmd);
138}
139
141{
142 int playerId = _clients.getConnection(connectionId).playerId();
143 if (_world.getPlayers().count(playerId)) {
144 auto& player = _world.getPlayer(playerId);
145 int slots = _world.teamEggCount(player.teamName);
146 _clients.send(connectionId, std::to_string(slots) + "\n");
147 }
148 _executeNext(connectionId);
149}
150
151void CommandDispatcher::_startStarvationTimer(int connectionId, int playerId)
152{
153 _scheduler.schedule(std::chrono::milliseconds(STARVATION_INTERVAL_MS) / _clock.freq(),
154 [this, connectionId, playerId] {
155 if (!_world.getPlayers().count(playerId)) return;
156 if (!_world.consumeFood(playerId)) {
157 _world.removePlayer(playerId);
158 _clients.send(connectionId, "dead\n");
159 _pendingDisconnects.push_back(connectionId);
160 } else {
161 _startStarvationTimer(connectionId, playerId);
162 }
163 });
164}
165
167{
168 std::vector<int> result;
169 std::swap(result, _pendingDisconnects);
170 return result;
171}
static std::optional< Ai::Command > parse(std::string_view line)
Parse one line from an AI client (no trailing newline).
Definition AiParser.cpp:3
Owns every client socket and runs the poll() loop.
void send(int connectionId, const std::string &msg)
Queue msg to be sent to a client (flushed during poll()).
Connection & getConnection(int connectionId)
Access the underlying Connection (e.g. to set its type/playerId).
void _handleMct(int connectionId)
void _handleFork(int connectionId)
std::chrono::microseconds gameElapsed() const
Wall-clock time since server start (single source for stu and the win banner).
void _handleSst(int freq)
void _handlePlv(int connectionId, int playerId)
void _dispatchGui(int connectionId, const std::string &line)
void _startStarvationTimer(int connectionId, int playerId)
HandshakeHandler _handshakeHandler
static constexpr int STARVATION_INTERVAL_MS
one food is consumed every 126 time units (1 unit = 1/freq seconds)
double gameTicks() const
Total game ticks elapsed (freq integrated over time). For the win banner.
void onNewConnection(int connectionId)
New socket connected: start its handshake.
void _onAiJoined(int connectionId, int playerId)
Fired on AI promotion: stamp team join, then start its food timer.
void _handleIncantation(int connectionId)
void _handleTake(int connectionId, ResourceType resource)
void _handlePin(int connectionId, int playerId)
void _handleLeft(int connectionId)
void _handleLook(int connectionId)
CommandDispatcher(ClientManager &clients, World &world, GuiNotifier &notifier, const ServerConfig &config, Scheduler &scheduler)
void _handleStu(int connectionId)
void _handleSgt(int connectionId)
void _handleMsz(int connectionId)
void onDisconnect(int connectionId)
Socket dropped: clean up its queue and player.
void _executeNext(int connectionId)
Run the next queued AI command (or go idle if none left).
void dispatch(int connectionId, const std::string &line)
Route one line from a client.
ClientManager & _clients
void _handleBct(int connectionId, int x, int y)
std::unordered_map< int, bool > _hasActive
std::optional< GameClock::Stamp > teamJoin(const std::string &team) const
When team's first player joined, or nullopt if it never did.
void _handleForward(int connectionId)
void _dispatchAi(int connectionId, const std::string &line)
void _handleConnectNbr(int connectionId)
void _handleInventory(int connectionId)
std::vector< int > _pendingDisconnects
std::vector< int > drainPendingDisconnects()
Take the list of ids the server should disconnect this cycle.
std::unordered_map< int, std::deque< Ai::Command > > _queues
void _handleTna(int connectionId)
void _handleBroadcast(int connectionId, const std::string &msg)
void _handleRight(int connectionId)
void _handlePpo(int connectionId, int playerId)
void _handleSet(int connectionId, ResourceType resource)
void _handleEject(int connectionId)
int playerId() const
Player this connection drives (-1 for GUI / not yet assigned).
std::optional< Stamp > joinOf(const std::string &team) const
The join stamp for team, or nullopt if the team never joined.
Definition GameClock.cpp:37
void recordJoin(const std::string &team)
Definition GameClock.cpp:31
double ticks() const
Total ticks elapsed: integral of freq over time. Fractional by nature.
Definition GameClock.cpp:20
int freq() const
Definition GameClock.hpp:32
std::chrono::microseconds elapsed() const
Wall-clock time since construction.
Definition GameClock.cpp:8
void removeGui(int connectionId)
static std::optional< Gui::Request > parse(std::string_view line)
Parse one line from a GUI client (no trailing newline).
Definition GuiParser.cpp:14
void onNewConnection(int connectionId)
New socket: send the "WELCOME" greeting.
void onLine(int connectionId, const std::string &line)
The client's reply (its team name).
Priority queue of timed callbacks. Drives all game timing.
Definition Scheduler.hpp:23
void schedule(std::chrono::milliseconds delay, std::function< void()> cb)
Definition Scheduler.cpp:3
Pure game state - no networking, no scheduling.
Definition World.hpp:47
const std::unordered_map< int, Player > & getPlayers() const
Definition World.cpp:402
bool isGameEnded() const
True once a team has won. Game logic stops reacting to AI commands.
Definition World.cpp:417
void removePlayer(int id)
Definition World.cpp:78
Player & getPlayer(int id)
Definition World.cpp:147
int teamEggCount(const std::string &team) const
Definition World.cpp:222
std::variant< Forward, Right, Left, Look, Inventory, Broadcast, ConnectNbr, Fork, Eject, Take, Set, Incantation > Command
Definition AiParser.hpp:41
std::string teamName
Definition Player.hpp:22
Represents the configuration for the server application, described by:
Definition Args.hpp:17