squad-proto/engine/app.cpp

108 lines
2.7 KiB
C++
Raw Permalink Normal View History

Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
#include "engine/app.h"
#include <raylib.h>
#include "engine/config.h"
namespace
{
// Безрамочное окно на весь монитор. Отдельно, потому что порядок здесь важен:
// raylib разворачивает окно по ТЕКУЩЕМУ монитору, поэтому сначала размер по
// монитору, потом снятие рамки.
void GoBorderless()
{
const int mon = GetCurrentMonitor();
const int w = GetMonitorWidth(mon);
const int h = GetMonitorHeight(mon);
if (w > 0 && h > 0) SetWindowSize(w, h);
ToggleBorderlessWindowed();
}
} // namespace
bool App::Init(const AppConfig& cfg)
{
if (cfg.vsync) SetConfigFlags(FLAG_VSYNC_HINT);
const int w = (cfg.frameW > 0) ? cfg.frameW : INTERNAL_W;
const int h = (cfg.frameH > 0) ? cfg.frameH : INTERNAL_H;
InitWindow(w * WINDOW_SCALE, h * WINDOW_SCALE, cfg.title);
// Esc не закрывает окно сам. Приложение решает, что он значит: в игре это
// пауза, в редакторе — закрыть терминал. Выход по случайному нажатию
// посреди вылазки — не то, за что игрок скажет спасибо.
SetExitKey(KEY_NULL);
if (cfg.borderless) GoBorderless();
fb.Init(w, h);
screen.Init(w, h);
return IsWindowReady();
}
void App::Shutdown()
{
screen.Shutdown();
CloseWindow();
}
bool App::NextFrame()
{
if (quit || WindowShouldClose()) return false;
if (IsKeyPressed(KEY_F11)) ToggleBorderlessWindowed();
winW = GetScreenWidth();
winH = GetScreenHeight();
fit = ScreenFitRect(fb.w, fb.h, winW, winH);
frameDt = GetFrameTime();
return true;
}
int App::Advance(bool halted)
{
if (halted)
{
accumulator = 0.0;
return 0;
}
accumulator += double(frameDt);
int steps = 0;
while (accumulator >= FIXED_DT && steps < MAX_STEPS_PER_FRAME)
{
accumulator -= FIXED_DT;
++steps;
}
// Не успели за кадр — остаток выбрасывается целиком. Копить его значит
// копить долг, который никогда не отдать: следующий кадр будет таким же.
if (accumulator > FIXED_DT * MAX_STEPS_PER_FRAME) accumulator = 0.0;
return steps;
}
float App::Alpha() const
{
return float(accumulator / FIXED_DT);
}
void App::Present()
{
BeginDrawing();
ClearBackground(BLACK);
screen.Present(fb, winW, winH);
}
void App::Finish()
{
EndDrawing();
}
void App::Screenshot(const char* file) const
{
if (file) TakeScreenshot(file);
}