squad-proto/engine/tileset.h
z.kirill 7d6266aaf3 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-13 21:07:19 +03:00

70 lines
4.2 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
// -----------------------------------------------------------------------------
// Тайлы пола и стен — по набору на каждую тему отделки.
//
// Живёт в движке, а не в render/sprites.h, ровно по одной причине: этим
// набором рисует и игра, и редактор комнат. Редактор линкуется только с
// движком и про бойцов, тварей и предметы не знает, поэтому тащить ради двух
// спрайтов весь SpriteSet он не может и не должен.
//
// Выбор варианта плиты — чистая функция от координат тайла, без состояния и без
// генератора: пол обязан быть одинаковым в любом прогоне, иначе запечённая
// кровь легла бы после перезапуска на другую плиту (render/blood.h).
// -----------------------------------------------------------------------------
#include <cstdint>
#include "engine/hash.h"
#include "engine/theme.h"
#include "engine/framebuffer.h"
#include "engine/config.h"
class SpriteLibrary;
// Сколько разных «рисунков» может лежать на плите пола: ровная, треснувшая,
// решётка, засыпанная трухой. Шахматка при этом остаётся — вариант ложится
// ПОВЕРХ неё, а не вместо: без чередования двух тонов изометрическая сетка
// перестаёт читаться, и пол превращается в кашу.
constexpr int FLOOR_VARIANTS = 4;
struct TileSet
{
// [тема][чётность шахматки][вариант рисунка].
Sprite floor[THEME_COUNT][2][FLOOR_VARIANTS];
Sprite wall[THEME_COUNT][2]; // industrial / biomass-infested, 32x32
// Процедурный набор. Не зависит ни от одного файла на диске: игра и
// редактор обязаны запускаться на голом репозитории.
void Generate();
// Заменить базовую тему на тайлы из библиотеки SpriteForge и пересобрать
// по ней остальные. Всё или ничего: при любой осечке набор остаётся тем,
// что дал Generate(), и возвращается false. Вызывать только после Generate().
bool LoadFromForge(const SpriteLibrary& lib);
const Sprite& Floor(int tx, int ty, uint8_t theme) const
{
// Раскладка намеренно неравномерная: ровная плита занимает больше
// половины карты. Равные доли превращают пол в пёстрый ковёр, на
// котором не видно ни крови, ни тварей.
static const int kPick[8] = {0, 0, 0, 0, 0, 1, 2, 3};
const uint32_t h = Hash2(uint32_t(tx) * 2654435761u, uint32_t(ty));
return floor[Clamp(theme)][(tx + ty) & 1][kPick[h & 7u]];
}
const Sprite& Wall(int tx, int ty, uint8_t theme) const
{
// Редкие заражённые блоки, детерминированно по координате.
const uint32_t h = Hash2(uint32_t(tx) * 2246822519u, uint32_t(ty));
return wall[Clamp(theme)][(h & 7u) == 0u ? 1 : 0];
}
private:
// Комната может сослаться на тему, которой в этой сборке движка ещё нет.
// Падать из-за этого нельзя: неизвестная тема рисуется базовой.
static int Clamp(uint8_t theme) { return (theme < THEME_COUNT) ? int(theme) : 0; }
// Темы 1..N-1 — перекраска темы 0. Вызывается и после Generate(), и после
// LoadFromForge(): иначе привезённые тайлы остались бы только в базовой.
void BuildThemes();
};