#include "DungeonModel.h" #ifndef LV_ABS #define LV_ABS(x) ((x) > 0 ? (x) : -(x)) #endif namespace PocketDungeon { uint32_t DungeonModel::nextRandom() { seed = seed * 1103515245u + 12345u; return (seed >> 16u) & 0x7FFFu; } void DungeonModel::reset(uint32_t seedValue) { seed = seedValue; state = DungeonState {}; state.floor = 1; state.hp = 5; state.gold = 0; state.gameOver = false; generateFloor(); } void DungeonModel::addEnemy(EntityType type, GameKit::GridPos pos, int8_t hp) { if (state.entityCount >= MAX_ENTITIES) return; state.entities[state.entityCount++] = Entity { type, pos, hp, true }; } void DungeonModel::generateFloor() { for (uint8_t y = 0; y < DUNGEON_ROWS; ++y) { for (uint8_t x = 0; x < DUNGEON_COLS; ++x) { const bool edge = (x == 0 || y == 0 || x == DUNGEON_COLS - 1 || y == DUNGEON_ROWS - 1); state.tiles[y][x] = edge ? Tile::Wall : Tile::Floor; } } state.playerPos = {1, 1}; state.entityCount = 0; state.tiles[2][4] = Tile::Wall; state.tiles[3][4] = Tile::Wall; state.tiles[5][2 + (state.floor % 2)] = Tile::Treasure; state.tiles[5][7] = Tile::Stairs; if (state.floor > 1) { auto x = static_cast(2 + (nextRandom() % 5)); auto y = static_cast(1 + (nextRandom() % 5)); if (!(x == 1 && y == 1) && !(x == 7 && y == 5)) state.tiles[y][x] = Tile::Wall; } addEnemy(EntityType::Slime, {5, 2}, 1 + static_cast(state.floor / 3)); addEnemy(EntityType::Bat, {3, 5}, 1); if (state.floor >= 3) addEnemy(EntityType::Slime, {7, 4}, 2); } int DungeonModel::findEnemyAt(GameKit::GridPos pos) const { for (uint8_t i = 0; i < state.entityCount; ++i) { const auto& entity = state.entities[i]; if (entity.alive && entity.pos == pos) return i; } return -1; } bool DungeonModel::isWalkable(GameKit::GridPos pos) const { if (pos.x < 0 || pos.y < 0 || pos.x >= DUNGEON_COLS || pos.y >= DUNGEON_ROWS) return false; return state.tiles[pos.y][pos.x] != Tile::Wall; } MoveResult DungeonModel::movePlayer(int dx, int dy) { if (state.gameOver) return MoveResult::Dead; GameKit::GridPos target { static_cast(state.playerPos.x + dx), static_cast(state.playerPos.y + dy) }; if (!isWalkable(target)) return MoveResult::Blocked; const int enemyIdx = findEnemyAt(target); if (enemyIdx >= 0) { Entity& enemy = state.entities[enemyIdx]; enemy.hp -= 1; if (enemy.hp <= 0) { enemy.alive = false; state.gold += enemy.type == EntityType::Bat ? 2 : 1; } moveMonsters(); if (state.hp <= 0) { state.gameOver = true; return MoveResult::Dead; } return MoveResult::Attacked; } state.playerPos = target; MoveResult result = MoveResult::Moved; Tile& tile = state.tiles[target.y][target.x]; if (tile == Tile::Treasure) { state.gold += 5; tile = Tile::Floor; result = MoveResult::Treasure; } else if (tile == Tile::Stairs) { state.floor += 1; if (state.floor > bestFloor) bestFloor = state.floor; if (state.gold > bestGold) bestGold = state.gold; generateFloor(); return MoveResult::Stairs; } moveMonsters(); if (state.hp <= 0) { state.gameOver = true; return MoveResult::Dead; } if (state.floor > bestFloor) bestFloor = state.floor; if (state.gold > bestGold) bestGold = state.gold; return result; } void DungeonModel::moveMonsters() { for (uint8_t i = 0; i < state.entityCount; ++i) { Entity& enemy = state.entities[i]; if (!enemy.alive) continue; const int16_t dx = state.playerPos.x - enemy.pos.x; const int16_t dy = state.playerPos.y - enemy.pos.y; if ((dx == 0 && (dy == 1 || dy == -1)) || (dy == 0 && (dx == 1 || dx == -1))) { state.hp -= 1; continue; } GameKit::GridPos target = enemy.pos; if (enemy.type == EntityType::Bat && (nextRandom() % 3 == 0)) { const int dirs[4][2] = {{1,0},{-1,0},{0,1},{0,-1}}; const int* dir = dirs[nextRandom() % 4]; target.x += dir[0]; target.y += dir[1]; } else if (LV_ABS(dx) > LV_ABS(dy)) { target.x += dx > 0 ? 1 : -1; } else if (dy != 0) { target.y += dy > 0 ? 1 : -1; } if (target == state.playerPos) { state.hp -= 1; } else if (isWalkable(target) && findEnemyAt(target) < 0) { enemy.pos = target; } } } } // namespace PocketDungeon