Files
cactoz.su/gameserver/src/Game.hpp
T
cacto f2a5657740 Звук (Web Audio API) + несколько раскладок coop-карты
Клиент: audio.js синтезирует эффекты без внешних файлов (выстрел,
взрыв, разрушение кирпича, победа/поражение) — вписывается в
"чистый HTML/CSS/JS" стек. AudioContext разблокируется по клику
"готов" (браузеры блокируют автовоспроизведение без жеста).

Сервер: 3 варианта раскладки coop-карты (случайный выбор при старте
матча) вместо одной фиксированной.

Fix: сервер разрушал кирпичи в своей копии карты, но никогда не сообщал
об этом клиенту — стена оставалась видимой навсегда, хотя сквозь неё уже
можно было стрелять. Теперь game.state включает destroyed_tiles,
клиент убирает соответствующий спрайт стены.
2026-08-06 12:42:52 +05:00

104 lines
3.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include "Lobby.hpp"
#include <functional>
#include <optional>
#include <string>
#include <vector>
struct us_timer_t;
// Authoritative-игра для одной комнаты: pvp (этап 4) и coop с AI (этап 5).
class Game {
public:
static constexpr int kTickMs = 50; // 20 Гц
static constexpr int kMapWidth = 15;
static constexpr int kMapHeight = 15;
static constexpr int kStartLives = 3;
static constexpr int kMaxEnemiesTotal = 8; // размер волны в coop
static constexpr int kMaxConcurrentEnemies = 3;
static constexpr int kEnemySpawnCooldownTicks = 60; // 3с
enum Tile { kEmpty = 0, kBrick = 1, kSteel = 2 };
enum class Dir { Up, Right, Down, Left };
struct Tank {
std::string id; // player_id для людей, "ai-N" для AI
WS *ws = nullptr; // nullptr для AI
bool is_ai = false;
double x, y;
Dir dir = Dir::Up;
int lives = kStartLives;
int fire_cooldown = 0;
bool up = false, down = false, left = false, right = false, fire = false;
bool alive = true;
// состояние простого AI (используется только если is_ai)
Dir ai_dir = Dir::Down;
int ai_redecide_ticks = 0;
};
struct Bullet {
int id;
std::string owner_id;
bool owner_is_ai;
double x, y;
Dir dir;
};
struct Base {
double x, y;
bool alive = true;
};
// room.players.size() — 2 для pvp, 1 или 2 для coop.
Game(Room &room, std::function<void()> onFinished);
~Game();
void start();
void handleInput(const std::string &player_id, const json &payload);
// pvp: технический проигрыш отключившегося, второй побеждает.
// coop: игрок выбывает; если людей не осталось — поражение.
void handleDisconnect(WS *ws);
json buildStartPayload(const std::string &mode) const;
private:
std::string mode_;
std::vector<std::vector<int>> map_;
std::vector<Tank> tanks_; // сначала люди, затем (в coop) появляющиеся AI
std::vector<Bullet> bullets_;
std::optional<Base> base_;
std::vector<std::pair<double, double>> enemy_spawn_points_;
std::vector<std::pair<int, int>> destroyed_tiles_; // за последний тик — для звука/синка стен на клиенте
int enemies_spawned_total_ = 0;
int enemy_spawn_cooldown_ = 0;
int next_bullet_id_ = 1;
int next_enemy_id_ = 1;
int tick_count_ = 0;
bool finished_ = false;
us_timer_t *timer_ = nullptr;
std::function<void()> onFinished_;
void buildPvpMap();
void buildCoopMap();
void tick();
void applyInput(Tank &tank);
void updateAi(Tank &tank);
void spawnEnemiesIfNeeded();
void updateBullets();
bool tileSolid(int tx, int ty) const;
bool rectHitsSolid(double x, double y) const;
bool rectHitsTank(double x, double y, const Tank &self) const;
bool rectHitsBase(double x, double y, double half) const;
void fireBullet(Tank &tank);
void endGamePvp(int winnerIndex); // -1 = ничья
void endGameCoop(bool win, const std::string &reason);
void checkCoopEndConditions();
void broadcastState();
void sendTo(WS *ws, const std::string &type, json payload);
static json dirToJson(Dir d);
json serializeTank(const Tank &t) const;
json serializeBullet(const Bullet &b) const;
};