Сервер (Game.hpp/cpp): - coop-режим: AI-противники (волна 8, до 3 одновременно), простое преследование базы с рандомизацией, база с укрытием из кирпича. - Комната стартует соло (1 игрок), второй может подключиться позже. - Fix: хитбокс пули по базе брал радиус танка вместо пули. - Fix: AI при столкновении со стеной теперь сразу перерешает направление, а не долбит в одну точку до 3с — без этого база падала за секунды. Клиент: - Танк: гусеницы, башня, заметный ствол (было — незаметный нубик). - Рендер базы (укрытие/флаг), AI-танки — отдельным оттенком зелёного. - Экран победы/поражения — крупный glitch-текст на весь оверлей вместо строки текста; кнопки "играть ещё" (без reload) и "в лобби". - Блок кнопок управления (D-pad + огонь) справа от игрового поля. - Лобби: coop стартует по кнопке "готов" в одиночку. nginx: try_files с fallback на .html — чистые URL (/projects, /game) без расширения, старые /projects.html и т.п. продолжают работать.
103 lines
3.4 KiB
C++
103 lines
3.4 KiB
C++
#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_;
|
||
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;
|
||
};
|