Explored rooms
Source: src/dungeon_explored.c · 202 lines · 20 functions.
Module responsibility
Tracks which map cells have been visited and persists the exploration record with validation and checksums.
Module entry points
These functions are exposed to the rest of the Dungeon system. Open one to follow its decisions and helper calls.
How to read these diagrams
Each function has an expandable structural diagram derived from the current C source. Decisions split into True and False, loop bodies are indented, and arrows show statement order.
These are source diagrams before preprocessing, not runtime traces. Callback execution, task scheduling and macro expansion are not inferred.
Functions in this file
updateChecksum() · source line 18
static void updateChecksum(uint32_t *checksum, uint8_t value)
- Execute
*checksum = (*checksum ^ value) * CHECKSUM_PRIME;
Read original function source
static void updateChecksum(uint32_t *checksum, uint8_t value) { *checksum = (*checksum ^ value) * CHECKSUM_PRIME; }updateChecksumU32() · source line 19
static void updateChecksumU32(uint32_t *checksum, uint32_t value)
Calls into this documentation:
- Execute
updateChecksum(checksum, (uint8_t)value); - Execute
updateChecksum(checksum, (uint8_t)(value >> 8)); - Execute
updateChecksum(checksum, (uint8_t)(value >> 16)); - Execute
updateChecksum(checksum, (uint8_t)(value >> 24));
Read original function source
static void updateChecksumU32(uint32_t *checksum, uint32_t value)
{
updateChecksum(checksum, (uint8_t)value); updateChecksum(checksum, (uint8_t)(value >> 8));
updateChecksum(checksum, (uint8_t)(value >> 16)); updateChecksum(checksum, (uint8_t)(value >> 24));
}writeByte() · source line 25
static bool writeByte(FILE *file, uint8_t value, DungeonExploredResult *result)
- Decision
fwrite(&value, 1, 1, file) != 1UTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
++result->bytes; - Exit this function
return true;
Read original function source
static bool writeByte(FILE *file, uint8_t value, DungeonExploredResult *result)
{
if (fwrite(&value, 1, 1, file) != 1U) return false;
++result->bytes;
return true;
}writeU16() · source line 32
static bool writeU16(FILE *file, uint16_t value, DungeonExploredResult *result)
Calls into this documentation:
- Exit this function
return writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result);
Read original function source
static bool writeU16(FILE *file, uint16_t value, DungeonExploredResult *result)
{
return writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result);
}writeU32() · source line 37
static bool writeU32(FILE *file, uint32_t value, DungeonExploredResult *result)
Calls into this documentation:
- Exit this function
return writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result) && writeByte(file, (uint8_t)(value >> 16), result) && writeByte(file, (uint8_t)(value >> 24), result);
Read original function source
static bool writeU32(FILE *file, uint32_t value, DungeonExploredResult *result)
{
return writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result) &&
writeByte(file, (uint8_t)(value >> 16), result) && writeByte(file, (uint8_t)(value >> 24), result);
}readByte() · source line 43
static bool readByte(FILE *file, uint8_t *value, DungeonExploredResult *result)
- Decision
fread(value, 1, 1, file) != 1UTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
++result->bytes; - Exit this function
return true;
Read original function source
static bool readByte(FILE *file, uint8_t *value, DungeonExploredResult *result)
{
if (fread(value, 1, 1, file) != 1U) return false;
++result->bytes;
return true;
}readU16() · source line 50
static bool readU16(FILE *file, uint16_t *value, DungeonExploredResult *result)
Calls into this documentation:
- Execute
uint8_t low, high; - Decision
!readByte(file, &low, result) || !readByte(file, &high, result)True- Exit this function
return false;
FalseContinue after this decision.
- Execute
*value = (uint16_t)low | ((uint16_t)high << 8); - Exit this function
return true;
Read original function source
static bool readU16(FILE *file, uint16_t *value, DungeonExploredResult *result)
{
uint8_t low, high;
if (!readByte(file, &low, result) || !readByte(file, &high, result)) return false;
*value = (uint16_t)low | ((uint16_t)high << 8);
return true;
}readU32() · source line 58
static bool readU32(FILE *file, uint32_t *value, DungeonExploredResult *result)
Calls into this documentation:
- Execute
uint8_t b0, b1, b2, b3; - Decision
!readByte(file, &b0, result) || !readByte(file, &b1, result) || !readByte(file, &b2, result) || !readByte(file, &b3, result)True- Exit this function
return false;
FalseContinue after this decision.
- Execute
*value = (uint32_t)b0 | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 16) | ((uint32_t)b3 << 24); - Exit this function
return true;
Read original function source
static bool readU32(FILE *file, uint32_t *value, DungeonExploredResult *result)
{
uint8_t b0, b1, b2, b3;
if (!readByte(file, &b0, result) || !readByte(file, &b1, result) || !readByte(file, &b2, result) || !readByte(file, &b3, result)) return false;
*value = (uint32_t)b0 | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 16) | ((uint32_t)b3 << 24);
return true;
}validPosition() · source line 66
static bool validPosition(const DungeonPosition *position)
- Exit this function
return position != NULL && position->floor < DUNGEON_FLOOR_COUNT && position->x < DUNGEON_WIDTH && position->y < DUNGEON_HEIGHT;
Read original function source
static bool validPosition(const DungeonPosition *position)
{
return position != NULL && position->floor < DUNGEON_FLOOR_COUNT && position->x < DUNGEON_WIDTH && position->y < DUNGEON_HEIGHT;
}exploredIndex() · source line 71
static uint16_t exploredIndex(uint8_t x, uint8_t y)
- Exit this function
return (uint16_t)y * DUNGEON_WIDTH + x;
Read original function source
static uint16_t exploredIndex(uint8_t x, uint8_t y) { return (uint16_t)y * DUNGEON_WIDTH + x; }setExplored() · source line 73
static void setExplored(const DungeonPosition *position)
Calls into this documentation:
- Execute
const uint16_t index = exploredIndex(position->x, position->y); - Execute
explored[position->floor][index / 8U] |= (uint8_t)(1U << (index % 8U));
Read original function source
static void setExplored(const DungeonPosition *position)
{
const uint16_t index = exploredIndex(position->x, position->y);
explored[position->floor][index / 8U] |= (uint8_t)(1U << (index % 8U));
}calculateChecksum() · source line 79
static uint32_t calculateChecksum(uint32_t world_checksum)
Calls into this documentation:
- Execute
uint32_t checksum = CHECKSUM_START; - Execute
updateChecksumU32(&checksum, world_checksum); - FOR loop
unsigned floor = 0; floor < DUNGEON_FLOOR_COUNT; ++floorRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned index = 0; index < EXPLORED_BYTES_PER_FLOOR; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
updateChecksum(&checksum, explored[floor][index]);
- Exit this function
return checksum;
Read original function source
static uint32_t calculateChecksum(uint32_t world_checksum)
{
uint32_t checksum = CHECKSUM_START;
updateChecksumU32(&checksum, world_checksum);
for (unsigned floor = 0; floor < DUNGEON_FLOOR_COUNT; ++floor)
for (unsigned index = 0; index < EXPLORED_BYTES_PER_FLOOR; ++index)
updateChecksum(&checksum, explored[floor][index]);
return checksum;
}saveExplored() · source line 89
static bool saveExplored(DungeonExploredResult *result)
Calls into this documentation:
- Execute
memset(result, 0, sizeof(*result)); - Execute
result->error = DUNGEON_EXPLORED_OPEN_WRITE_FAILED; - Execute
errno = 0; - Execute
FILE *file = fopen(EXPLORED_FILE, "wb"); - Decision
file == NULLTrue- Execute
result->errno_value = errno; - Exit this function
return false;
FalseContinue after this decision.
- Execute
const uint8_t magic[] = { 'M', 'E', 'X', 'P' }; - Execute
const uint32_t world_checksum = getDungeonWorldChecksum(); - Execute
const uint32_t checksum = calculateChecksum(world_checksum); - Execute
bool written = fwrite(magic, 1, sizeof(magic), file) == sizeof(magic); - Execute
result->bytes += written ? sizeof(magic) : 0U; - Execute
written = written && writeU16(file, EXPLORED_VERSION, result) && writeU16(file, EXPLORED_PAYLOAD_SIZE, result) && writeU32(file, checksum, result) && writeU32(file, world_checksum, result) && fwrite(explored, 1, EXPLORED_BYTES, file) == EXPLORED_BYTES; - Decision
writtenTrue- Execute
result->bytes += EXPLORED_BYTES;
FalseContinue after this decision.
- Execute
const int close_result = fclose(file); - Decision
!written || close_result != 0 || result->bytes != EXPLORED_FILE_SIZETrue- Execute
result->error = DUNGEON_EXPLORED_WRITE_FAILED; - Execute
result->errno_value = errno; - Exit this function
return false;
FalseContinue after this decision.
- Execute
result->error = DUNGEON_EXPLORED_OK; - Exit this function
return true;
Read original function source
static bool saveExplored(DungeonExploredResult *result)
{
memset(result, 0, sizeof(*result));
result->error = DUNGEON_EXPLORED_OPEN_WRITE_FAILED;
errno = 0;
FILE *file = fopen(EXPLORED_FILE, "wb");
if (file == NULL) { result->errno_value = errno; return false; }
const uint8_t magic[] = { 'M', 'E', 'X', 'P' };
const uint32_t world_checksum = getDungeonWorldChecksum();
const uint32_t checksum = calculateChecksum(world_checksum);
bool written = fwrite(magic, 1, sizeof(magic), file) == sizeof(magic);
result->bytes += written ? sizeof(magic) : 0U;
written = written && writeU16(file, EXPLORED_VERSION, result) && writeU16(file, EXPLORED_PAYLOAD_SIZE, result) &&
writeU32(file, checksum, result) && writeU32(file, world_checksum, result) &&
fwrite(explored, 1, EXPLORED_BYTES, file) == EXPLORED_BYTES;
if (written) result->bytes += EXPLORED_BYTES;
const int close_result = fclose(file);
if (!written || close_result != 0 || result->bytes != EXPLORED_FILE_SIZE) { result->error = DUNGEON_EXPLORED_WRITE_FAILED; result->errno_value = errno; return false; }
result->error = DUNGEON_EXPLORED_OK;
return true;
}loadExplored() · source line 111
static bool loadExplored(DungeonExploredResult *result)
Calls into this documentation:
- Execute
memset(result, 0, sizeof(*result)); - Execute
result->error = DUNGEON_EXPLORED_OPEN_READ_FAILED; - Execute
FILE *file = fopen(EXPLORED_FILE, "rb"); - Decision
file == NULLTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
fseek(file, 0L, SEEK_END) != 0 || ftell(file) != (long)EXPLORED_FILE_SIZETrue- Execute
result->error = DUNGEON_EXPLORED_BAD_FILE_SIZE; - Execute
fclose(file); - Exit this function
return false;
FalseContinue after this decision.
- Execute
rewind(file); - Execute
uint8_t magic[4]; - Execute
uint16_t version, payload; - Execute
uint32_t checksum, world_checksum; - Execute
bool read = fread(magic, 1, sizeof(magic), file) == sizeof(magic); - Execute
result->bytes += read ? sizeof(magic) : 0U; - Execute
read = read && readU16(file, &version, result) && readU16(file, &payload, result) && readU32(file, &checksum, result) && readU32(file, &world_checksum, result) && fread(explored, 1, EXPLORED_BYTES, file) == EXPLORED_BYTES; - Decision
readTrue- Execute
result->bytes += EXPLORED_BYTES;
FalseContinue after this decision.
- Execute
fclose(file); - Decision
!read || result->bytes != EXPLORED_FILE_SIZETrue- Execute
result->error = DUNGEON_EXPLORED_READ_FAILED; - Exit this function
return false;
FalseContinue after this decision.
- Decision
memcmp(magic, "MEXP", sizeof(magic)) != 0True- Execute
result->error = DUNGEON_EXPLORED_BAD_SIGNATURE; - Exit this function
return false;
FalseContinue after this decision.
- Decision
version != EXPLORED_VERSIONTrue- Execute
result->error = DUNGEON_EXPLORED_BAD_VERSION; - Exit this function
return false;
FalseContinue after this decision.
- Decision
payload != EXPLORED_PAYLOAD_SIZETrue- Execute
result->error = DUNGEON_EXPLORED_BAD_FILE_SIZE; - Exit this function
return false;
FalseContinue after this decision.
- Decision
world_checksum != getDungeonWorldChecksum()True- Execute
result->error = DUNGEON_EXPLORED_WRONG_DUNGEON; - Exit this function
return false;
FalseContinue after this decision.
- Decision
checksum != calculateChecksum(world_checksum)True- Execute
result->error = DUNGEON_EXPLORED_CHECKSUM_FAILED; - Exit this function
return false;
FalseContinue after this decision.
- Execute
result->error = DUNGEON_EXPLORED_OK; - Exit this function
return true;
Read original function source
static bool loadExplored(DungeonExploredResult *result)
{
memset(result, 0, sizeof(*result));
result->error = DUNGEON_EXPLORED_OPEN_READ_FAILED;
FILE *file = fopen(EXPLORED_FILE, "rb");
if (file == NULL) return false;
if (fseek(file, 0L, SEEK_END) != 0 || ftell(file) != (long)EXPLORED_FILE_SIZE) { result->error = DUNGEON_EXPLORED_BAD_FILE_SIZE; fclose(file); return false; }
rewind(file);
uint8_t magic[4]; uint16_t version, payload; uint32_t checksum, world_checksum;
bool read = fread(magic, 1, sizeof(magic), file) == sizeof(magic);
result->bytes += read ? sizeof(magic) : 0U;
read = read && readU16(file, &version, result) && readU16(file, &payload, result) && readU32(file, &checksum, result) && readU32(file, &world_checksum, result) &&
fread(explored, 1, EXPLORED_BYTES, file) == EXPLORED_BYTES;
if (read) result->bytes += EXPLORED_BYTES;
fclose(file);
if (!read || result->bytes != EXPLORED_FILE_SIZE) { result->error = DUNGEON_EXPLORED_READ_FAILED; return false; }
if (memcmp(magic, "MEXP", sizeof(magic)) != 0) { result->error = DUNGEON_EXPLORED_BAD_SIGNATURE; return false; }
if (version != EXPLORED_VERSION) { result->error = DUNGEON_EXPLORED_BAD_VERSION; return false; }
if (payload != EXPLORED_PAYLOAD_SIZE) { result->error = DUNGEON_EXPLORED_BAD_FILE_SIZE; return false; }
if (world_checksum != getDungeonWorldChecksum()) { result->error = DUNGEON_EXPLORED_WRONG_DUNGEON; return false; }
if (checksum != calculateChecksum(world_checksum)) { result->error = DUNGEON_EXPLORED_CHECKSUM_FAILED; return false; }
result->error = DUNGEON_EXPLORED_OK;
return true;
}initDungeonExplored() · source line 136
void initDungeonExplored(void)
- Execute
memset(explored, 0, sizeof(explored));
Read original function source
void initDungeonExplored(void) { memset(explored, 0, sizeof(explored)); }prepareDungeonExplored() · source line 138
bool prepareDungeonExplored(const DungeonPosition *position, DungeonExploredResult *result)
Calls into this documentation:
- Decision
result == NULL || !validPosition(position)True- Decision
result != NULLTrue- Execute
memset(result, 0, sizeof(*result)); - Execute
result->error = DUNGEON_EXPLORED_INVALID_POSITION;
FalseContinue after this decision.
- Exit this function
return false;
FalseContinue after this decision.
- Decision
loadExplored(result)True- Decision
isDungeonExplored(position->floor, position->x, position->y)True- Exit this function
return true;
FalseContinue after this decision.
- Execute
setExplored(position); - Exit this function
return saveExplored(result);
FalseContinue after this decision.
- Decision
result->error != DUNGEON_EXPLORED_OPEN_READ_FAILEDTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
initDungeonExplored(); - Execute
setExplored(position); - Decision
!saveExplored(result)True- Exit this function
return false;
FalseContinue after this decision.
- Execute
result->created = true; - Exit this function
return true;
Read original function source
bool prepareDungeonExplored(const DungeonPosition *position, DungeonExploredResult *result)
{
if (result == NULL || !validPosition(position)) {
if (result != NULL) { memset(result, 0, sizeof(*result)); result->error = DUNGEON_EXPLORED_INVALID_POSITION; }
return false;
}
if (loadExplored(result)) {
if (isDungeonExplored(position->floor, position->x, position->y)) return true;
setExplored(position);
return saveExplored(result);
}
if (result->error != DUNGEON_EXPLORED_OPEN_READ_FAILED) return false;
initDungeonExplored();
setExplored(position);
if (!saveExplored(result)) return false;
result->created = true;
return true;
}resetDungeonExplored() · source line 157
bool resetDungeonExplored(const DungeonPosition *position, DungeonExploredResult *result)
Calls into this documentation:
- Decision
result == NULL || !validPosition(position)True- Decision
result != NULLTrue- Execute
memset(result, 0, sizeof(*result)); - Execute
result->error = DUNGEON_EXPLORED_INVALID_POSITION;
FalseContinue after this decision.
- Exit this function
return false;
FalseContinue after this decision.
- Execute
initDungeonExplored(); - Execute
setExplored(position); - Exit this function
return saveExplored(result);
Read original function source
bool resetDungeonExplored(const DungeonPosition *position, DungeonExploredResult *result)
{
if (result == NULL || !validPosition(position)) {
if (result != NULL) { memset(result, 0, sizeof(*result)); result->error = DUNGEON_EXPLORED_INVALID_POSITION; }
return false;
}
initDungeonExplored();
setExplored(position);
return saveExplored(result);
}markDungeonExplored() · source line 168
bool markDungeonExplored(const DungeonPosition *position, DungeonExploredResult *result)
Calls into this documentation:
- Decision
result == NULL || !validPosition(position)True- Decision
result != NULLTrue- Execute
memset(result, 0, sizeof(*result)); - Execute
result->error = DUNGEON_EXPLORED_INVALID_POSITION;
FalseContinue after this decision.
- Exit this function
return false;
FalseContinue after this decision.
- Decision
isDungeonExplored(position->floor, position->x, position->y)True- Execute
memset(result, 0, sizeof(*result)); - Execute
result->error = DUNGEON_EXPLORED_OK; - Exit this function
return true;
FalseContinue after this decision.
- Execute
setExplored(position); - Exit this function
return saveExplored(result);
Read original function source
bool markDungeonExplored(const DungeonPosition *position, DungeonExploredResult *result)
{
if (result == NULL || !validPosition(position)) {
if (result != NULL) { memset(result, 0, sizeof(*result)); result->error = DUNGEON_EXPLORED_INVALID_POSITION; }
return false;
}
if (isDungeonExplored(position->floor, position->x, position->y)) { memset(result, 0, sizeof(*result)); result->error = DUNGEON_EXPLORED_OK; return true; }
setExplored(position);
return saveExplored(result);
}isDungeonExplored() · source line 179
bool isDungeonExplored(uint8_t floor, uint8_t x, uint8_t y)
Calls into this documentation:
- Decision
floor >= DUNGEON_FLOOR_COUNT || x >= DUNGEON_WIDTH || y >= DUNGEON_HEIGHTTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
const uint16_t index = exploredIndex(x, y); - Exit this function
return (explored[floor][index / 8U] & (uint8_t)(1U << (index % 8U))) != 0U;
Read original function source
bool isDungeonExplored(uint8_t floor, uint8_t x, uint8_t y)
{
if (floor >= DUNGEON_FLOOR_COUNT || x >= DUNGEON_WIDTH || y >= DUNGEON_HEIGHT) return false;
const uint16_t index = exploredIndex(x, y);
return (explored[floor][index / 8U] & (uint8_t)(1U << (index % 8U))) != 0U;
}dungeonExploredErrorText() · source line 186
const char *dungeonExploredErrorText(DungeonExploredError error)
- Select a case
errorEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case DUNGEON_EXPLORED_OK: - Exit this function
return "OK"; - Case / label
case DUNGEON_EXPLORED_OPEN_READ_FAILED: - Exit this function
return "Cannot open explored map"; - Case / label
case DUNGEON_EXPLORED_OPEN_WRITE_FAILED: - Exit this function
return "Cannot create explored map"; - Case / label
case DUNGEON_EXPLORED_READ_FAILED: - Exit this function
return "Explored map read failed"; - Case / label
case DUNGEON_EXPLORED_WRITE_FAILED: - Exit this function
return "Explored map save failed"; - Case / label
case DUNGEON_EXPLORED_BAD_SIGNATURE: - Exit this function
return "Bad explored map signature"; - Case / label
case DUNGEON_EXPLORED_BAD_VERSION: - Exit this function
return "Bad explored map version"; - Case / label
case DUNGEON_EXPLORED_BAD_FILE_SIZE: - Exit this function
return "Bad explored map size"; - Case / label
case DUNGEON_EXPLORED_CHECKSUM_FAILED: - Exit this function
return "Explored map checksum failed"; - Case / label
case DUNGEON_EXPLORED_WRONG_DUNGEON: - Exit this function
return "Explored map is for another dungeon"; - Case / label
case DUNGEON_EXPLORED_INVALID_POSITION: - Exit this function
return "Bad explored map position"; - Case / label
default: - Exit this function
return "Explored map error";
Read original function source
const char *dungeonExploredErrorText(DungeonExploredError error)
{
switch (error) {
case DUNGEON_EXPLORED_OK: return "OK";
case DUNGEON_EXPLORED_OPEN_READ_FAILED: return "Cannot open explored map";
case DUNGEON_EXPLORED_OPEN_WRITE_FAILED: return "Cannot create explored map";
case DUNGEON_EXPLORED_READ_FAILED: return "Explored map read failed";
case DUNGEON_EXPLORED_WRITE_FAILED: return "Explored map save failed";
case DUNGEON_EXPLORED_BAD_SIGNATURE: return "Bad explored map signature";
case DUNGEON_EXPLORED_BAD_VERSION: return "Bad explored map version";
case DUNGEON_EXPLORED_BAD_FILE_SIZE: return "Bad explored map size";
case DUNGEON_EXPLORED_CHECKSUM_FAILED: return "Explored map checksum failed";
case DUNGEON_EXPLORED_WRONG_DUNGEON: return "Explored map is for another dungeon";
case DUNGEON_EXPLORED_INVALID_POSITION: return "Bad explored map position";
default: return "Explored map error";
}
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "dungeon_explored.h"
#include "dungeon_storage.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#define EXPLORED_FILE "/spiffs/dungeon_explored.dat"
#define EXPLORED_VERSION 2U
#define EXPLORED_BYTES_PER_FLOOR ((DUNGEON_WIDTH * DUNGEON_HEIGHT) / 8U)
#define EXPLORED_BYTES (EXPLORED_BYTES_PER_FLOOR * DUNGEON_FLOOR_COUNT)
#define EXPLORED_PAYLOAD_SIZE (4U + EXPLORED_BYTES)
#define EXPLORED_FILE_SIZE (12U + EXPLORED_PAYLOAD_SIZE)
#define CHECKSUM_START 2166136261UL
#define CHECKSUM_PRIME 16777619UL
static uint8_t explored[DUNGEON_FLOOR_COUNT][EXPLORED_BYTES_PER_FLOOR];
static void updateChecksum(uint32_t *checksum, uint8_t value) { *checksum = (*checksum ^ value) * CHECKSUM_PRIME; }
static void updateChecksumU32(uint32_t *checksum, uint32_t value)
{
updateChecksum(checksum, (uint8_t)value); updateChecksum(checksum, (uint8_t)(value >> 8));
updateChecksum(checksum, (uint8_t)(value >> 16)); updateChecksum(checksum, (uint8_t)(value >> 24));
}
static bool writeByte(FILE *file, uint8_t value, DungeonExploredResult *result)
{
if (fwrite(&value, 1, 1, file) != 1U) return false;
++result->bytes;
return true;
}
static bool writeU16(FILE *file, uint16_t value, DungeonExploredResult *result)
{
return writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result);
}
static bool writeU32(FILE *file, uint32_t value, DungeonExploredResult *result)
{
return writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result) &&
writeByte(file, (uint8_t)(value >> 16), result) && writeByte(file, (uint8_t)(value >> 24), result);
}
static bool readByte(FILE *file, uint8_t *value, DungeonExploredResult *result)
{
if (fread(value, 1, 1, file) != 1U) return false;
++result->bytes;
return true;
}
static bool readU16(FILE *file, uint16_t *value, DungeonExploredResult *result)
{
uint8_t low, high;
if (!readByte(file, &low, result) || !readByte(file, &high, result)) return false;
*value = (uint16_t)low | ((uint16_t)high << 8);
return true;
}
static bool readU32(FILE *file, uint32_t *value, DungeonExploredResult *result)
{
uint8_t b0, b1, b2, b3;
if (!readByte(file, &b0, result) || !readByte(file, &b1, result) || !readByte(file, &b2, result) || !readByte(file, &b3, result)) return false;
*value = (uint32_t)b0 | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 16) | ((uint32_t)b3 << 24);
return true;
}
static bool validPosition(const DungeonPosition *position)
{
return position != NULL && position->floor < DUNGEON_FLOOR_COUNT && position->x < DUNGEON_WIDTH && position->y < DUNGEON_HEIGHT;
}
static uint16_t exploredIndex(uint8_t x, uint8_t y) { return (uint16_t)y * DUNGEON_WIDTH + x; }
static void setExplored(const DungeonPosition *position)
{
const uint16_t index = exploredIndex(position->x, position->y);
explored[position->floor][index / 8U] |= (uint8_t)(1U << (index % 8U));
}
static uint32_t calculateChecksum(uint32_t world_checksum)
{
uint32_t checksum = CHECKSUM_START;
updateChecksumU32(&checksum, world_checksum);
for (unsigned floor = 0; floor < DUNGEON_FLOOR_COUNT; ++floor)
for (unsigned index = 0; index < EXPLORED_BYTES_PER_FLOOR; ++index)
updateChecksum(&checksum, explored[floor][index]);
return checksum;
}
static bool saveExplored(DungeonExploredResult *result)
{
memset(result, 0, sizeof(*result));
result->error = DUNGEON_EXPLORED_OPEN_WRITE_FAILED;
errno = 0;
FILE *file = fopen(EXPLORED_FILE, "wb");
if (file == NULL) { result->errno_value = errno; return false; }
const uint8_t magic[] = { 'M', 'E', 'X', 'P' };
const uint32_t world_checksum = getDungeonWorldChecksum();
const uint32_t checksum = calculateChecksum(world_checksum);
bool written = fwrite(magic, 1, sizeof(magic), file) == sizeof(magic);
result->bytes += written ? sizeof(magic) : 0U;
written = written && writeU16(file, EXPLORED_VERSION, result) && writeU16(file, EXPLORED_PAYLOAD_SIZE, result) &&
writeU32(file, checksum, result) && writeU32(file, world_checksum, result) &&
fwrite(explored, 1, EXPLORED_BYTES, file) == EXPLORED_BYTES;
if (written) result->bytes += EXPLORED_BYTES;
const int close_result = fclose(file);
if (!written || close_result != 0 || result->bytes != EXPLORED_FILE_SIZE) { result->error = DUNGEON_EXPLORED_WRITE_FAILED; result->errno_value = errno; return false; }
result->error = DUNGEON_EXPLORED_OK;
return true;
}
static bool loadExplored(DungeonExploredResult *result)
{
memset(result, 0, sizeof(*result));
result->error = DUNGEON_EXPLORED_OPEN_READ_FAILED;
FILE *file = fopen(EXPLORED_FILE, "rb");
if (file == NULL) return false;
if (fseek(file, 0L, SEEK_END) != 0 || ftell(file) != (long)EXPLORED_FILE_SIZE) { result->error = DUNGEON_EXPLORED_BAD_FILE_SIZE; fclose(file); return false; }
rewind(file);
uint8_t magic[4]; uint16_t version, payload; uint32_t checksum, world_checksum;
bool read = fread(magic, 1, sizeof(magic), file) == sizeof(magic);
result->bytes += read ? sizeof(magic) : 0U;
read = read && readU16(file, &version, result) && readU16(file, &payload, result) && readU32(file, &checksum, result) && readU32(file, &world_checksum, result) &&
fread(explored, 1, EXPLORED_BYTES, file) == EXPLORED_BYTES;
if (read) result->bytes += EXPLORED_BYTES;
fclose(file);
if (!read || result->bytes != EXPLORED_FILE_SIZE) { result->error = DUNGEON_EXPLORED_READ_FAILED; return false; }
if (memcmp(magic, "MEXP", sizeof(magic)) != 0) { result->error = DUNGEON_EXPLORED_BAD_SIGNATURE; return false; }
if (version != EXPLORED_VERSION) { result->error = DUNGEON_EXPLORED_BAD_VERSION; return false; }
if (payload != EXPLORED_PAYLOAD_SIZE) { result->error = DUNGEON_EXPLORED_BAD_FILE_SIZE; return false; }
if (world_checksum != getDungeonWorldChecksum()) { result->error = DUNGEON_EXPLORED_WRONG_DUNGEON; return false; }
if (checksum != calculateChecksum(world_checksum)) { result->error = DUNGEON_EXPLORED_CHECKSUM_FAILED; return false; }
result->error = DUNGEON_EXPLORED_OK;
return true;
}
void initDungeonExplored(void) { memset(explored, 0, sizeof(explored)); }
bool prepareDungeonExplored(const DungeonPosition *position, DungeonExploredResult *result)
{
if (result == NULL || !validPosition(position)) {
if (result != NULL) { memset(result, 0, sizeof(*result)); result->error = DUNGEON_EXPLORED_INVALID_POSITION; }
return false;
}
if (loadExplored(result)) {
if (isDungeonExplored(position->floor, position->x, position->y)) return true;
setExplored(position);
return saveExplored(result);
}
if (result->error != DUNGEON_EXPLORED_OPEN_READ_FAILED) return false;
initDungeonExplored();
setExplored(position);
if (!saveExplored(result)) return false;
result->created = true;
return true;
}
bool resetDungeonExplored(const DungeonPosition *position, DungeonExploredResult *result)
{
if (result == NULL || !validPosition(position)) {
if (result != NULL) { memset(result, 0, sizeof(*result)); result->error = DUNGEON_EXPLORED_INVALID_POSITION; }
return false;
}
initDungeonExplored();
setExplored(position);
return saveExplored(result);
}
bool markDungeonExplored(const DungeonPosition *position, DungeonExploredResult *result)
{
if (result == NULL || !validPosition(position)) {
if (result != NULL) { memset(result, 0, sizeof(*result)); result->error = DUNGEON_EXPLORED_INVALID_POSITION; }
return false;
}
if (isDungeonExplored(position->floor, position->x, position->y)) { memset(result, 0, sizeof(*result)); result->error = DUNGEON_EXPLORED_OK; return true; }
setExplored(position);
return saveExplored(result);
}
bool isDungeonExplored(uint8_t floor, uint8_t x, uint8_t y)
{
if (floor >= DUNGEON_FLOOR_COUNT || x >= DUNGEON_WIDTH || y >= DUNGEON_HEIGHT) return false;
const uint16_t index = exploredIndex(x, y);
return (explored[floor][index / 8U] & (uint8_t)(1U << (index % 8U))) != 0U;
}
const char *dungeonExploredErrorText(DungeonExploredError error)
{
switch (error) {
case DUNGEON_EXPLORED_OK: return "OK";
case DUNGEON_EXPLORED_OPEN_READ_FAILED: return "Cannot open explored map";
case DUNGEON_EXPLORED_OPEN_WRITE_FAILED: return "Cannot create explored map";
case DUNGEON_EXPLORED_READ_FAILED: return "Explored map read failed";
case DUNGEON_EXPLORED_WRITE_FAILED: return "Explored map save failed";
case DUNGEON_EXPLORED_BAD_SIGNATURE: return "Bad explored map signature";
case DUNGEON_EXPLORED_BAD_VERSION: return "Bad explored map version";
case DUNGEON_EXPLORED_BAD_FILE_SIZE: return "Bad explored map size";
case DUNGEON_EXPLORED_CHECKSUM_FAILED: return "Explored map checksum failed";
case DUNGEON_EXPLORED_WRONG_DUNGEON: return "Explored map is for another dungeon";
case DUNGEON_EXPLORED_INVALID_POSITION: return "Bad explored map position";
default: return "Explored map error";
}
}