#include "game/map/tilemap_writer.hpp" #include #include #include #include #include #include #include namespace TilemapWriter { namespace { // Retorna la posició de l'inici de la línia que conté "data:" a // columna 0. La detecció requereix que els 5 caràcters siguin al // principi d'una línia i que el següent siga un terminador típic // (fi de línia, espai, tabulador o comentari). Així evitem trigger // amb claus com "data_version:" o valors escalars que comencin // amb "data:". auto findDataLine(const std::string& content) -> std::size_t { std::size_t pos = 0; while (pos < content.size()) { const bool AT_LINE_START = (pos == 0 || content[pos - 1] == '\n'); if (AT_LINE_START && content.compare(pos, 5, "data:") == 0) { const std::size_t AFTER = pos + 5; if (AFTER >= content.size() || content[AFTER] == '\n' || content[AFTER] == '\r' || content[AFTER] == ' ' || content[AFTER] == '\t' || content[AFTER] == '#') { return pos; } } const auto NL = content.find('\n', pos); if (NL == std::string::npos) { break; } pos = NL + 1; } return std::string::npos; } // Avança mentre trobe línies indentades (espai/tab). S'atura al // primer caràcter a columna 0 (nova clau top-level) o davant una // línia buida (separadors de seccions). Així els comentaris i el // blanc que precedeixen a la pròxima secció queden fora del bloc. auto advancePastIndented(const std::string& content, std::size_t pos) -> std::size_t { while (pos < content.size()) { const char C = content[pos]; if (C != ' ' && C != '\t') { break; } const auto NL = content.find('\n', pos); pos = (NL == std::string::npos) ? content.size() : NL + 1; } return pos; } auto buildBlock(const std::vector>& data) -> std::string { int max_width = 1; for (const auto& row : data) { for (const int V : row) { const int W = static_cast(std::to_string(V).size()); max_width = std::max(max_width, W); } } std::string out; for (const auto& row : data) { out += " - ["; for (std::size_t i = 0; i < row.size(); ++i) { if (i > 0) { out += ","; } const auto S = std::to_string(row[i]); const int PAD = max_width - static_cast(S.size()); out.append(static_cast(PAD), ' '); out += S; } out += "]\n"; } return out; } } // namespace auto saveDataBlock(const std::string& path, const std::vector>& data) -> bool { std::ifstream in(path, std::ios::binary); if (!in) { return false; } std::ostringstream ss; ss << in.rdbuf(); const std::string CONTENT = ss.str(); in.close(); const auto DATA_POS = findDataLine(CONTENT); if (DATA_POS == std::string::npos) { return false; } const auto LINE_END = CONTENT.find('\n', DATA_POS); const auto AFTER_DATA = (LINE_END == std::string::npos) ? CONTENT.size() : LINE_END + 1; const auto BLOCK_END = advancePastIndented(CONTENT, AFTER_DATA); const std::string NEW_CONTENT = CONTENT.substr(0, AFTER_DATA) + buildBlock(data) + CONTENT.substr(BLOCK_END); const auto TMP_PATH = path + ".tmp"; { std::ofstream out(TMP_PATH, std::ios::binary | std::ios::trunc); if (!out) { return false; } out.write(NEW_CONTENT.data(), static_cast(NEW_CONTENT.size())); out.close(); if (!out) { std::error_code ec; std::filesystem::remove(TMP_PATH, ec); return false; } } std::error_code ec; std::filesystem::rename(TMP_PATH, path, ec); if (ec) { std::filesystem::remove(TMP_PATH, ec); return false; } return true; } } // namespace TilemapWriter