squad-proto/engine/text_parse.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

108 lines
4.4 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
// -----------------------------------------------------------------------------
// Разбор строчных текстовых форматов движка: каталог сущностей и файлы комнат.
//
// Свой микро-парсер, а не YAML/JSON: оба формата — это «слово и несколько
// значений в строке», и внешняя библиотека ради них тянула бы в движок
// зависимость тяжелее всего остального движка вместе взятого. Оба файла
// правятся руками и лежат в git, поэтому формат обязан быть построчным и
// diff-читаемым — это требование к формату, а не следствие лени.
// -----------------------------------------------------------------------------
#include <cstdlib>
#include <string>
#include <vector>
namespace text
{
// Комментарий — от '#' до конца строки. Внутри значений '#' не встречается:
// в тайловой сетке комнаты решётка означает стену, но сетка читается целыми
// строками и через этот разбор не проходит.
inline std::string StripComment(const std::string& line)
{
const size_t hash = line.find('#');
return (hash == std::string::npos) ? line : line.substr(0, hash);
}
inline std::string Trim(const std::string& s)
{
size_t a = 0;
size_t b = s.size();
while (a < b && (s[a] == ' ' || s[a] == '\t' || s[a] == '\r')) ++a;
while (b > a && (s[b - 1] == ' ' || s[b - 1] == '\t' || s[b - 1] == '\r')) --b;
return s.substr(a, b - a);
}
// Разбить по пробелам. Пустых лексем не возвращает.
inline std::vector<std::string> Split(const std::string& s)
{
std::vector<std::string> out;
size_t i = 0;
while (i < s.size())
{
while (i < s.size() && (s[i] == ' ' || s[i] == '\t' || s[i] == '\r')) ++i;
const size_t start = i;
while (i < s.size() && s[i] != ' ' && s[i] != '\t' && s[i] != '\r') ++i;
if (i > start) out.push_back(s.substr(start, i - start));
}
return out;
}
// Разбить надвое по разделителю. false — разделителя нет.
//
// Разделителей два, и они РАЗНЫЕ намеренно: `ключ=значение` — это свойство
// записи (каталог сущностей), `имя:вес` — элемент таблицы (веса точки спавна).
// Один символ на оба случая читался бы одинаково в двух разных ролях, и
// таблица весов внутри строки свойств стала бы неразличима.
inline bool SplitAt(const std::string& s, char sep, std::string& key, std::string& value)
{
const size_t at = s.find(sep);
if (at == std::string::npos) return false;
key = s.substr(0, at);
value = s.substr(at + 1);
return true;
}
// `имя:вес` — элемент таблицы.
inline bool SplitPair(const std::string& s, std::string& key, std::string& value)
{
return SplitAt(s, ':', key, value);
}
// `ключ=значение` — свойство записи.
inline bool SplitKeyValue(const std::string& s, std::string& key, std::string& value)
{
return SplitAt(s, '=', key, value);
}
inline int ToInt(const std::string& s, int fallback = 0)
{
if (s.empty()) return fallback;
char* end = nullptr;
const long v = std::strtol(s.c_str(), &end, 10);
return (end && *end == '\0') ? int(v) : fallback;
}
inline float ToFloat(const std::string& s, float fallback = 0.0f)
{
if (s.empty()) return fallback;
char* end = nullptr;
const double v = std::strtod(s.c_str(), &end);
return (end && *end == '\0') ? float(v) : fallback;
}
// Склеить хвост лексем обратно в строку — для полей, где значение с пробелами
// (подпись в палитре, теги комнаты).
inline std::string JoinFrom(const std::vector<std::string>& parts, size_t first)
{
std::string out;
for (size_t i = first; i < parts.size(); ++i)
{
if (!out.empty()) out += ' ';
out += parts[i];
}
return out;
}
} // namespace text