/
yaseeeeechka
/
CenterControl
Обзор
Документация
Войти
/
yaseeeeechka
/
CenterControl
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
CenterControl_TCP/include/server/GameManager.h
316 строк
10 KB
Yaroslava
New structure of Repo. TCP impl
11 дек 2025, 21:12
11 дек 2025, 21:12
6fdf9d7
Код
Авторство
О чём код?
#ifndef CENTERCONTROL_GAMEMANAGER_H #define CENTERCONTROL_GAMEMANAGER_H #include "server/Utils.h" class GameManager { public: static GameManager &Instance() { static GameManager inst; return inst; } struct PlayerState { SpawnLocation spawn; bool ready = false; float x = 0.0f; float y = 0.0f; // счёт int score = 0; // нокбэк (плавный толчок) bool in_knockback = false; float knockback_start_x = 0.0f; float knockback_start_y = 0.0f; float knockback_target_x = 0.0f; float knockback_target_y = 0.0f; std::chrono::steady_clock::time_point knockback_start; }; struct GameState { std::unordered_map<std::string, PlayerState> players; // user -> state bool active = false; bool countdown_started = false; std::chrono::steady_clock::time_point countdown_start; // для начисления очков по времени std::chrono::steady_clock::time_point last_score_update; }; bool HasGame(const std::string &roomName) { std::lock_guard<std::mutex> l(m_); return games_.find(roomName) != games_.end(); } bool StartGame(const std::string &roomName, const std::vector<std::string> &players) { std::lock_guard<std::mutex> l(m_); auto &gs = games_[roomName]; if (gs.active) return false; if (players.size() < 2) return false; gs.players.clear(); std::vector<SpawnLocation> spawns = { SpawnLocation::Bottom, SpawnLocation::Left, SpawnLocation::Top, SpawnLocation::Right }; size_t i = 0; for (const auto &u: players) { if (i >= spawns.size()) break; PlayerState ps; ps.spawn = spawns[i]; ps.ready = false; auto [sx, sy] = GetSpawnCoords(spawns[i]); ps.x = sx; ps.y = sy; gs.players[u] = ps; ps.score = 0; ps.in_knockback = false; ++i; } if (gs.players.empty()) { games_.erase(roomName); return false; } gs.active = true; gs.countdown_started = false; gs.last_score_update = std::chrono::steady_clock::now(); return true; } std::vector<PlayerStateDTO> GetPlayersState(const std::string &roomName) { std::lock_guard<std::mutex> l(m_); std::vector<PlayerStateDTO> out; auto it = games_.find(roomName); if (it == games_.end()) return out; out.reserve(it->second.players.size()); for (const auto &[user, ps]: it->second.players) { PlayerStateDTO dto; dto.user = user; dto.x = ps.x; dto.y = ps.y; dto.score = ps.score; // новый параметр out.push_back(std::move(dto)); } return out; } void LeaveGame(const std::string &roomName, const std::string &user) { std::lock_guard<std::mutex> l(m_); auto it = games_.find(roomName); if (it == games_.end()) return; auto &players = it->second.players; auto pit = players.find(user); if (pit != players.end()) { SaveUserScore(user, pit->second.score); // <--- сохраняем результат players.erase(pit); } // если после выхода осталось 0 или 1 игрок — считаем, что игра завершена if (players.size() <= 1) { if (players.size() == 1) { auto &last = *players.begin(); SaveUserScore(last.first, last.second.score); // последний тоже получает сохранение } games_.erase(it); } } void PlayerReady(const std::string &roomName, const std::string &user) { std::lock_guard<std::mutex> l(m_); auto it = games_.find(roomName); if (it == games_.end()) return; auto pit = it->second.players.find(user); if (pit == it->second.players.end()) return; pit->second.ready = true; if (!it->second.countdown_started) { bool allReady = !it->second.players.empty(); for (auto &kv: it->second.players) { if (!kv.second.ready) { allReady = false; break; } } if (allReady) { it->second.countdown_started = true; it->second.countdown_start = std::chrono::steady_clock::now(); } } } struct QueryResult { bool gameExists = false; bool inGame = false; SpawnLocation spawn = SpawnLocation::Bottom; uint8_t countdown = 255; // 255 = ожидание игроков }; QueryResult QueryState(const std::string &roomName, const std::string &user) { std::lock_guard<std::mutex> l(m_); QueryResult res; auto it = games_.find(roomName); if (it == games_.end()) return res; UpdateScoresAndKnockbacks(it->second); res.gameExists = true; auto pit = it->second.players.find(user); if (pit != it->second.players.end()) { res.inGame = true; res.spawn = pit->second.spawn; } auto &gs = it->second; if (!gs.countdown_started) { res.countdown = 255; // ждём, пока все нажмут READY } else { using namespace std::chrono; auto now = steady_clock::now(); auto sec = duration_cast<seconds>(now - gs.countdown_start).count(); if (sec < 1) res.countdown = 3; else if (sec < 2) res.countdown = 2; else if (sec < 3) res.countdown = 1; else res.countdown = 0; } return res; } void MovePlayer(const std::string &roomName, const std::string &user, MOVE_DIR dir) { std::lock_guard<std::mutex> l(m_); auto it = games_.find(roomName); if (it == games_.end()) return; auto pit = it->second.players.find(user); if (pit == it->second.players.end()) return; auto &ps = pit->second; switch (dir) { case MOVE_DIR::UP: ps.y -= kStepLen; break; case MOVE_DIR::DOWN: ps.y += kStepLen; break; case MOVE_DIR::LEFT: ps.x -= kStepLen; break; case MOVE_DIR::RIGHT: ps.x += kStepLen; break; } // клампим в пределах игрового поля (константы уже есть в GameFieldConfig) ps.x = std::clamp(ps.x, kMinX, kMaxX); ps.y = std::clamp(ps.y, kMinY, kMaxY); } void Push(const std::string &roomName, const std::string &user) { std::lock_guard<std::mutex> l(m_); auto it = games_.find(roomName); if (it == games_.end()) return; // сначала обновим очки и нокбэки UpdateScoresAndKnockbacks(it->second); auto pit = it->second.players.find(user); if (pit == it->second.players.end()) return; auto &pusher = pit->second; for (auto &kv : it->second.players) { if (kv.first == user) continue; // себя не толкаем auto &ps = kv.second; float dx = ps.x - pusher.x; float dy = ps.y - pusher.y; float dist = std::sqrt(dx * dx + dy * dy); if (dist < 1e-3f) continue; if (dist <= kPushRadius) { float nx = dx / dist; float ny = dy / dist; float targetX = ps.x + nx * kPushRadius; float targetY = ps.y + ny * kPushRadius; targetX = std::clamp(targetX, kMinX, kMaxX); targetY = std::clamp(targetY, kMinY, kMaxY); ps.knockback_start_x = ps.x; ps.knockback_start_y = ps.y; ps.knockback_target_x = targetX; ps.knockback_target_y = targetY; ps.knockback_start = std::chrono::steady_clock::now(); ps.in_knockback = true; } } } private: GameManager() = default; void UpdateScoresAndKnockbacks(GameState &gs) { using namespace std::chrono; auto now = steady_clock::now(); // --- Начисление очков внутри центрального круга --- if (gs.last_score_update.time_since_epoch().count() == 0) { gs.last_score_update = now; } else { auto elapsed = duration_cast<milliseconds>(now - gs.last_score_update); if (elapsed >= milliseconds(1000)) { int fullSeconds = static_cast<int>(elapsed.count() / 1000); gs.last_score_update += seconds(fullSeconds); for (auto &kv : gs.players) { auto &ps = kv.second; float dx = ps.x - kCenterCircleX; float dy = ps.y - kCenterCircleY; float dist2 = dx * dx + dy * dy; if (dist2 <= kCenterCircleRadius * kCenterCircleRadius) { ps.score += fullSeconds; } } } } // --- Плавный нокбэк --- for (auto &kv : gs.players) { auto &ps = kv.second; if (!ps.in_knockback) continue; auto elapsed = duration_cast<milliseconds>(now - ps.knockback_start); float t = static_cast<float>(elapsed.count()) / (kKnockbackDurationSec * 1000.0f); if (t >= 1.0f) t = 1.0f; ps.x = ps.knockback_start_x + (ps.knockback_target_x - ps.knockback_start_x) * t; ps.y = ps.knockback_start_y + (ps.knockback_target_y - ps.knockback_start_y) * t; if (t >= 1.0f) { ps.in_knockback = false; } } } std::mutex m_; std::unordered_map<std::string, GameState> games_; }; #endif //CENTERCONTROL_GAMEMANAGER_H