Игровая логика PvP: сервер (C++) + клиент (Quintus.js)

Сервер: Game — authoritative-тик 20 Гц через uSockets timer, движение/коллизии
танков и пуль, разрушаемые кирпичные стены, respawn, победа/поражение/ничья,
технический проигрыш при дисконнекте. game.ready запускает матч, когда оба
игрока в pvp-комнате готовы.

Клиент: заведён Quintus.js (MIT, vendored) — Tank/Bullet/Wall спрайты,
рендер по снапшотам game.state, keyboard-инпут (WASD/стрелки/space),
экран результата матча.

Найден и исправлен use-after-free: колбэк onFinished_ синхронно удалял
Game изнутри его же метода — перенесено на uWS::Loop::defer.
This commit is contained in:
2026-07-28 12:05:01 +05:00
parent a9e4828dd9
commit 2d0be9c287
17 changed files with 6632 additions and 5 deletions
+12
View File
@@ -1,6 +1,7 @@
#pragma once
#include <App.h>
#include <memory>
#include <nlohmann/json.hpp>
#include <string>
#include <unordered_map>
@@ -17,10 +18,13 @@ struct PerSocketData {
using WS = uWS::WebSocket<false, true, PerSocketData>;
class Game;
struct RoomPlayer {
std::string player_id;
std::string nickname;
WS *ws;
bool ready = false;
};
struct Room {
@@ -33,6 +37,9 @@ struct Room {
// Лобби и реестр комнат (этап 3 из TZ.md). Игровая логика — этапы 4-5.
class Lobby {
public:
Lobby();
~Lobby();
void onOpen(WS *ws);
void onMessage(WS *ws, std::string_view message);
void onClose(WS *ws);
@@ -41,6 +48,7 @@ private:
static constexpr size_t kMaxPlayersPerRoom = 2;
std::unordered_map<std::string, Room> rooms_;
std::unordered_map<std::string, std::unique_ptr<Game>> games_;
std::unordered_set<WS *> sockets_;
int next_player_id_ = 1;
int next_room_id_ = 1;
@@ -50,7 +58,11 @@ private:
void handleCreateRoom(WS *ws, const json &payload);
void handleJoinRoom(WS *ws, const json &payload);
void handleLeaveRoom(WS *ws);
void handleGameReady(WS *ws);
void handleGameInput(WS *ws, const json &payload);
void removePlayerFromRoom(WS *ws);
void maybeStartGame(const std::string &room_id);
void endActiveGameIfAny(const std::string &room_id, WS *leavingWs);
void broadcastRoomsToLobby();
void broadcastRoomUpdated(const Room &room, WS *exclude = nullptr);