Files
tactility_apps/Apps/PocketDungeon/main/Source/DungeonModel.cpp
T
aeroreyna d976595879
Main / Build (BookPlayer) (push) Has been cancelled
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
feat: add Pocket Dungeon game prototype
2026-07-16 22:31:55 -04:00

132 lines
4.5 KiB
C++

#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<uint8_t>(2 + (nextRandom() % 5));
auto y = static_cast<uint8_t>(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<int8_t>(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<int16_t>(state.playerPos.x + dx), static_cast<int16_t>(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