Coop с AI/базой (соло-старт), реванш, чистые URL, доработки клиента
Сервер (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 и т.п. продолжают работать.
This commit is contained in:
+35
-10
@@ -2,72 +2,97 @@
|
||||
|
||||
#include "Lobby.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct us_timer_t;
|
||||
|
||||
// Authoritative-игра для одной комнаты в режиме pvp (этап 4 из TZ.md).
|
||||
// Coop с AI — этап 5, наследует эту же тик-логику.
|
||||
// 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 player_id;
|
||||
WS *ws;
|
||||
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;
|
||||
};
|
||||
|
||||
// room.players.size() должен быть == 2 на момент создания.
|
||||
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::array<Tank, 2> tanks_;
|
||||
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 buildMap();
|
||||
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 endGame(int winnerIndex); // -1 = ничья
|
||||
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user