Десять вех одним состоянием: разделять их пофайлово нечем, потому что
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>
53 lines
2.8 KiB
C++
53 lines
2.8 KiB
C++
#pragma once
|
||
// -----------------------------------------------------------------------------
|
||
// Карта тайлов: EMPTY / WALL. Декартов мир, тайл = 1.0 юнита.
|
||
// Здесь же DDA-рейкаст (Amanatides & Woo) и circle-vs-AABB разрешение коллизий.
|
||
// Об изометрии этот модуль не знает.
|
||
// -----------------------------------------------------------------------------
|
||
#include <cstdint>
|
||
|
||
#include "engine/math.h"
|
||
#include "engine/config.h"
|
||
|
||
enum class Tile : uint8_t
|
||
{
|
||
EMPTY = 0,
|
||
WALL = 1,
|
||
};
|
||
|
||
struct Tilemap
|
||
{
|
||
Tile tiles[MAP_W * MAP_H] = {};
|
||
|
||
// Тема отделки тайла: какой НАБОР спрайтов пола и стен на нём лежит.
|
||
// Слой отдельный от tiles намеренно — проходимость и внешний вид это два
|
||
// разных вопроса, и смешав их в одном значении, мы получили бы отдельный
|
||
// тип тайла на каждое сочетание. Симуляция этот слой не читает вообще:
|
||
// рейкаст, коллизии и правила видят только Tile.
|
||
uint8_t theme[MAP_W * MAP_H] = {};
|
||
|
||
void Generate(); // единственный захардкоженный уровень
|
||
|
||
static bool InBounds(int x, int y) { return unsigned(x) < unsigned(MAP_W) && unsigned(y) < unsigned(MAP_H); }
|
||
|
||
Tile At(int x, int y) const { return InBounds(x, y) ? tiles[y * MAP_W + x] : Tile::WALL; }
|
||
void Set(int x, int y, Tile t) { if (InBounds(x, y)) tiles[y * MAP_W + x] = t; }
|
||
|
||
uint8_t ThemeAt(int x, int y) const { return InBounds(x, y) ? theme[y * MAP_W + x] : 0; }
|
||
void SetTheme(int x, int y, uint8_t t) { if (InBounds(x, y)) theme[y * MAP_W + x] = t; }
|
||
|
||
// Вне карты считаем стеной — так рейкасты и коллизии не убегают наружу.
|
||
bool IsWall(int x, int y) const { return At(x, y) == Tile::WALL; }
|
||
bool IsWallAt(Vec2 p) const { return IsWall(int(std::floor(p.x)), int(std::floor(p.y))); }
|
||
|
||
// Amanatides & Woo. true, если отрезок a->b пересекает стену. Без аллокаций.
|
||
// tHit (если нужен) — параметр входа в стену вдоль отрезка, [0..1].
|
||
bool RaycastBlocked(Vec2 a, Vec2 b, float* tHit = nullptr) const;
|
||
|
||
// Выталкивание круга из стен (несколько итераций — из-за углов).
|
||
void ResolveCircle(Vec2& p, float r) const;
|
||
|
||
// Есть ли стена в круге радиуса r вокруг p (для отбраковки кандидатов).
|
||
bool CircleHitsWall(Vec2 p, float r) const;
|
||
};
|