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
132 lines
4.5 KiB
C++
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
|