#include <vector>
#include <fov.h>
#include <iostream>
#include "util.h"
#include "game.h"
#include "renderer.h"
inline bool isTileSetOrNotLit(const Map<MapData>& map, int x, int y)
{
return (map.inBounds(x, y) && (map.at(x,y).tile == Tile::Wall || !map.at(x,y).lit));
}
inline bool isTileSet(const Map<MapData>& map, int x, int y, Tile val = Tile::Wall)
{
return (map.inBounds(x, y) && map.at(x,y).tile == val);
}
inline void incrementIfSet(const Game& game, int& count, int x, int y, Tile val = Tile::Wall)
{
if (isTileSet(game.map, x, y, val))
{
count++;
}
}
inline int getZoomLevel(const Game& game) {
return (game.curZoom - INIT_ZOOM) / 2;
}
void tick(
Game& game,
int x1,
int y1,
int x2,
int y2,
bool invert = false,
bool onlyDark = false)
{
Map<MapData> temp(game.map);
for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
{
for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
{
if (invert == (x >= x1 && x < x2 && y >= y1 && y < y2))
{
continue;
}
if (onlyDark && game.map.at(x,y).lit)
{
continue;
}
if (game.map.at(x,y).tile == Tile::Lamp)
{
continue;
}
int count = 0;
incrementIfSet(game, count, x-1, y-1);
incrementIfSet(game, count, x-1, y );
incrementIfSet(game, count, x-1, y+1);
incrementIfSet(game, count, x , y-1);
incrementIfSet(game, count, x , y );
incrementIfSet(game, count, x , y+1);
incrementIfSet(game, count, x+1, y-1);
incrementIfSet(game, count, x+1, y );
incrementIfSet(game, count, x+1, y+1);
if (count >= 5)
{
temp.at(x,y).tile = Tile::Wall;
} else {
temp.at(x,y).tile = Tile::Floor;
}
if (temp.at(x,y).tile != game.map.at(x,y).tile) {
temp.at(x,y).dirtyRender = true;
game.dirtyRender = true;
}
}
}
game.map = std::move(temp);
}
void tick(Game& game, bool onlyDark = false)
{
tick(
game,
game.map.getLeft(),
game.map.getTop(),
game.map.getRight(),
game.map.getBottom(),
false,
onlyDark);
}
bool movePlayer(Game& game, int x, int y)
{
if (x >= game.curBoundX &&
y >= game.curBoundY &&
x < game.curBoundX + game.curZoom * ZOOM_X_FACTOR &&
y < game.curBoundY + game.curZoom * ZOOM_Y_FACTOR &&
game.map.at(x,y).tile == Tile::Floor)
{
if (game.map.at(game.player_x, game.player_y).tile == Tile::Floor)
{
game.map.at(game.player_x, game.player_y).tile = Tile::Dust;
game.map.at(game.player_x, game.player_y).dustLife = 1;
game.numDust++;
}
game.player_oldx = game.player_x;
game.player_oldy = game.player_y;
game.player_x = x;
game.player_y = y;
game.muxer.setPlayerLoc(x, y);
game.moving = true;
game.moveProgress.start(66);
game.dirtyLighting = true;
return true;
} else {
if (!game.alreadyBumped) {
game.muxer.playSoundAtPosition("bump", x, y);
game.alreadyBumped = true;
game.bumpCooldown.reset();
}
return false;
}
}
void recalculateLighting(Game& game)
{
game.litSpots = 0;
game.dirtyRender = true;
for (MapData& md : game.map.data())
{
md.wasLit = md.lit;
md.lit = false;
md.litTiles.clear();
if (md.tile == Tile::Wall) {
md.dirtyRender = true;
}
}
fov_settings_type fov;
fov_settings_init(&fov);
fov_settings_set_opacity_test_function(
&fov,
[] (void* data, int x, int y) {
Game& game = *static_cast<Game*>(data);
return game.map.inBounds(x,y) && game.map.at(x,y).tile == Tile::Wall;
});
fov_settings_set_apply_lighting_function(
&fov,
[] (void* data, int x, int y, int dx, int dy, void* source) {
Game& game = *static_cast<Game*>(data);
if (game.map.inBounds(x, y))
{
MapData& sourceData = *static_cast<MapData*>(source);
double lightRadius = static_cast<double>(sourceData.lightRadius);
if (!game.map.at(x,y).lit)
{
game.litSpots++;
}
game.map.at(x,y).lit = true;
sourceData.litTiles.emplace(x,y);
}
});
for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
{
for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
{
Source ls = Source::None;
int lightRadius;
if (game.renderPlayer && game.player_x == x && game.player_y == y)
{
ls = Source::Player;
lightRadius = RADIUS;
} else if (game.map.at(x,y).tile == Tile::Dust)
{
ls = Source::Dust;
lightRadius = 2;
} else if (game.map.at(x,y).tile == Tile::Lamp)
{
ls = Source::Lamp;
lightRadius = RADIUS;
}
game.map.at(x,y).lightType = ls;
if (ls != Source::None)
{
game.map.at(x,y).lightRadius = lightRadius;
game.map.at(x,y).litTiles.emplace(x,y);
fov_circle(
&fov,
static_cast<void*>(&game),
static_cast<void*>(&game.map.at(x,y)),
x,
y,
lightRadius);
game.map.at(x,y).lit = true;
}
}
}
game.dirtyLighting = false;
}
void recalculateRender(Game& game) {
for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
{
for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
{
if (game.map.at(x,y).dirtyRender) {
game.map.at(x,y).dirtyRender = false;
if (game.map.at(x,y).tile == Tile::Floor) {
if (std::bernoulli_distribution(0.05)(game.rng)) {
static const std::vector<int> furnishings {
TilesetIndex(20, 16),
TilesetIndex(21, 2),
TilesetIndex(22, 2),
TilesetIndex(21, 3),
TilesetIndex(22, 3)};
game.map.at(x,y).renderId = furnishings.at(std::uniform_int_distribution<int>(0, furnishings.size()-1)(game.rng));
} else {
game.map.at(x,y).renderId = -1;
}
} else if (game.map.at(x,y).tile == Tile::Wall) {
static bool initWalls = false;
static std::vector<int> wallRenders(256, TilesetIndex(0, 0));
if (!initWalls) {
initWalls = true;
// Start in top left and go clockwise.
wallRenders[0b00011100] = TilesetIndex(16, 14);
wallRenders[0b00111100] = TilesetIndex(16, 14);
wallRenders[0b00011110] = TilesetIndex(16, 14);
wallRenders[0b00111110] = TilesetIndex(16, 14);
wallRenders[0b00011111] = TilesetIndex(17, 14);
wallRenders[0b10011111] = TilesetIndex(17, 14);
wallRenders[0b00111111] = TilesetIndex(17, 14);
wallRenders[0b10111111] = TilesetIndex(17, 14);
wallRenders[0b00000111] = TilesetIndex(18, 14);
wallRenders[0b00001111] = TilesetIndex(18, 14);
wallRenders[0b10000111] = TilesetIndex(18, 14);
wallRenders[0b10001111] = TilesetIndex(18, 14);
wallRenders[0b01111100] = TilesetIndex(16, 15);
wallRenders[0b11111100] = TilesetIndex(16, 15);
wallRenders[0b01111110] = TilesetIndex(16, 15);
wallRenders[0b11111110] = TilesetIndex(16, 15);
wallRenders[0b11000111] = TilesetIndex(18, 15);
wallRenders[0b11001111] = TilesetIndex(18, 15);
wallRenders[0b11100111] = TilesetIndex(18, 15);
wallRenders[0b11101111] = TilesetIndex(18, 15);
wallRenders[0b01110000] = TilesetIndex(16, 16);
wallRenders[0b01111000] = TilesetIndex(16, 16);
wallRenders[0b11110000] = TilesetIndex(16, 16);
wallRenders[0b11111000] = TilesetIndex(16, 16);
wallRenders[0b11110001] = TilesetIndex(17, 16);
wallRenders[0b11110011] = TilesetIndex(17, 16);
wallRenders[0b11111001] = TilesetIndex(17, 16);
wallRenders[0b11111011] = TilesetIndex(17, 16);
wallRenders[0b11000001] = TilesetIndex(18, 16);
wallRenders[0b11000011] = TilesetIndex(18, 16);
wallRenders[0b11100001] = TilesetIndex(18, 16);
wallRenders[0b11100011] = TilesetIndex(18, 16);
wallRenders[0b11110111] = TilesetIndex(21, 14);
wallRenders[0b11111101] = TilesetIndex(22, 14);
wallRenders[0b11011111] = TilesetIndex(21, 15);
wallRenders[0b01111111] = TilesetIndex(22, 15);
}
int renderDesc = 0;
if (isTileSetOrNotLit(game.map, x-1, y-1)) renderDesc |= (1 << 7);
if (isTileSetOrNotLit(game.map, x , y-1)) renderDesc |= (1 << 6);
if (isTileSetOrNotLit(game.map, x+1, y-1)) renderDesc |= (1 << 5);
if (isTileSetOrNotLit(game.map, x+1, y )) renderDesc |= (1 << 4);
if (isTileSetOrNotLit(game.map, x+1, y+1)) renderDesc |= (1 << 3);
if (isTileSetOrNotLit(game.map, x , y+1)) renderDesc |= (1 << 2);
if (isTileSetOrNotLit(game.map, x-1, y+1)) renderDesc |= (1 << 1);
if (isTileSetOrNotLit(game.map, x-1, y )) renderDesc |= (1 << 0);
game.map.at(x,y).renderId = wallRenders.at(renderDesc);
if (wallRenders.at(renderDesc) == 0 && renderDesc != 255) {
std::cout << renderDesc << std::endl;
}
}
}
}
}
game.dirtyRender = false;
}
bool processKeys(Game& game, const Input& keystate)
{
int px = game.player_x;
int py = game.player_y;
Direction dir = Direction::up;
if (keystate.up)
{
py--;
}
if (keystate.down)
{
py++;
dir = Direction::down;
}
if (keystate.left)
{
px--;
dir = Direction::left;
}
if (keystate.right)
{
px++;
dir = Direction::right;
}
if (!(game.player_x == px && game.player_y == py))
{
game.playerAnim.setAnimation("walk");
game.playerAnim.setDirection(dir);
return movePlayer(game, px, py);
} else {
return false;
}
}
void kickUpDust(Game& game, int x, int y, size_t chain)
{
Kickup dk;
dk.x = x;
dk.y = y;
dk.chain = chain;
dk.cur = 0;
dk.radius = RADIUS + (chain + 1) * (chain + 1);
dk.front.emplace(x, y);
dk.done.emplace(x, y);
game.kickups.push_back(dk);
}
void popLamp(Game& game, int x, int y, size_t chain)
{
game.muxer.playSoundAtPosition("pop", x, y);
if (game.map.at(x,y).tile == Tile::Lamp)
{
game.numLamps--;
}
game.map.at(x,y).tile = Tile::Dust;
game.map.at(x,y).dustLife = 2;
game.numDust++;
game.dirtyLighting = true;
kickUpDust(game, x, y, chain);
}
void processKickup(Game& game)
{
for (Kickup& kickup : game.kickups)
{
kickup.cur++;
std::set<coord> newFront;
for (const coord& xy : kickup.front)
{
auto processDir = [&] (int x, int y) {
coord c {x,y};
if (game.map.inBounds(x,y) &&
(game.map.at(x,y).tile == Tile::Floor ||
game.map.at(x,y).tile == Tile::Lamp) &&
!kickup.done.count(c))
{
newFront.insert(c);
kickup.done.insert(c);
if (game.map.at(x,y).tile == Tile::Floor)
{
game.map.at(x,y).tile = Tile::Dust;
game.map.at(x,y).dustLife = 2;
game.numDust++;
game.dirtyLighting = true;
} else if (game.map.at(x,y).tile == Tile::Lamp)
{
popLamp(game, x, y, kickup.chain + 1);
}
}
};
processDir(std::get<0>(xy) - 1, std::get<1>(xy) );
processDir(std::get<0>(xy) + 1, std::get<1>(xy) );
processDir(std::get<0>(xy) , std::get<1>(xy) - 1);
processDir(std::get<0>(xy) , std::get<1>(xy) + 1);
if (std::bernoulli_distribution(0.5)(game.rng))
{
processDir(std::get<0>(xy) - 1, std::get<1>(xy) - 1);
}
if (std::bernoulli_distribution(0.5)(game.rng))
{
processDir(std::get<0>(xy) - 1, std::get<1>(xy) + 1);
}
if (std::bernoulli_distribution(0.5)(game.rng))
{
processDir(std::get<0>(xy) + 1, std::get<1>(xy) - 1);
}
if (std::bernoulli_distribution(0.5)(game.rng))
{
processDir(std::get<0>(xy) + 1, std::get<1>(xy) + 1);
}
}
kickup.front.swap(newFront);
}
erase_if(
game.kickups,
[] (const Kickup& kickup) {
return kickup.cur == kickup.radius;
});
}
void growMap(Game& game, size_t zoom)
{
int ol = game.map.getLeft();
int ot = game.map.getTop();
int ow = game.map.getWidth();
int oh = game.map.getHeight();
game.map.resize(
-zoom * ZOOM_X_FACTOR / 2,
-zoom * ZOOM_Y_FACTOR / 2,
zoom * ZOOM_X_FACTOR,
zoom * ZOOM_Y_FACTOR);
game.maxZoom = zoom;
for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
{
for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
{
if (!(x >= ol && x < (ol + ow) && y >= ot && y < (ot + oh)))
{
if (std::bernoulli_distribution(0.5)(game.rng))
{
game.map.at(x,y).tile = Tile::Wall;
}
}
}
}
for (int i = 0; i < 3; i++)
{
tick(game, ol, ot, ol + ow, ot + oh, true);
}
}
void setZoom(Game& game, size_t zoom)
{
if (zoom == game.curZoom)
{
return;
}
if (zoom > game.maxZoom)
{
growMap(game, zoom);
}
std::tie(
game.lastZoomLeft,
game.lastZoomTop,
game.lastZoomWidth,
game.lastZoomHeight) =
Renderer::calculateZoomRect(game);
game.zoomProgress = 0;
game.zoomLength =
std::abs(static_cast<long>(zoom - game.curZoom)) * TILE_WIDTH;
game.curZoom = zoom;
game.zooming = true;
game.curBoundX = game.player_x - zoom * ZOOM_X_FACTOR / 2;
if (game.curBoundX < game.map.getLeft())
{
game.curBoundX = game.map.getLeft();
} else if (game.curBoundX + zoom * ZOOM_X_FACTOR >= game.map.getRight())
{
game.curBoundX = game.map.getRight() - zoom * ZOOM_X_FACTOR;
}
game.curBoundY = game.player_y - zoom * ZOOM_Y_FACTOR / 2;
if (game.curBoundY < game.map.getTop())
{
game.curBoundY = game.map.getTop();
} else if (game.curBoundY + zoom * ZOOM_Y_FACTOR >= game.map.getBottom())
{
game.curBoundY = game.map.getBottom() - zoom * ZOOM_Y_FACTOR;
}
int zoomLevel = getZoomLevel(game);
if (zoomLevel == 0) {
game.muxer.setMusicLevel(0);
} else if (zoomLevel < 3) {
game.muxer.setMusicLevel(1);
} else if (zoomLevel < 5) {
game.muxer.setMusicLevel(2);
} else if (zoomLevel < 7) {
game.muxer.setMusicLevel(3);
} else {
game.muxer.setMusicLevel(4);
}
}
void performDash(Game& game, std::mt19937& rng) {
if (game.map.at(game.player_x, game.player_y).tile ==
Tile::Floor)
{
std::vector<coord> freeSpaces;
auto addIfFree = [&] (int x, int y) {
if (game.map.inBounds(x,y) &&
game.map.at(x,y).tile == Tile::Floor)
{
freeSpaces.emplace_back(x, y);
}
};
addIfFree(game.player_x - 1, game.player_y - 1);
addIfFree(game.player_x , game.player_y - 1);
addIfFree(game.player_x + 1, game.player_y - 1);
addIfFree(game.player_x - 1, game.player_y );
addIfFree(game.player_x + 1, game.player_y );
addIfFree(game.player_x - 1, game.player_y + 1);
addIfFree(game.player_x , game.player_y + 1);
addIfFree(game.player_x + 1, game.player_y + 1);
if (!freeSpaces.empty())
{
game.map.at(game.player_x, game.player_y).tile = Tile::Lamp;
game.numLamps++;
game.dirtyLighting = true;
kickUpDust(game, game.player_x, game.player_y, 0);
game.muxer.playSoundAtPosition("drop", game.player_x, game.player_y);
if (game.firstInput)
{
for (int i = 0; i < 5; i++)
{
if (!processKeys(game, game.lastInput))
{
std::uniform_int_distribution<int> freeDist(
0, freeSpaces.size() - 1);
int freeIndex = freeDist(rng);
coord& moveTo = freeSpaces[freeIndex];
movePlayer(
game,
std::get<0>(moveTo),
std::get<1>(moveTo));
}
tick(
game,
game.player_x - (RADIUS - 1),
game.player_y - (RADIUS - 1),
game.player_x + RADIUS,
game.player_y + RADIUS);
}
} else {
std::uniform_int_distribution<int> freeDist(
0, freeSpaces.size() - 1);
int freeIndex = freeDist(rng);
coord& moveTo = freeSpaces[freeIndex];
movePlayer(
game,
std::get<0>(moveTo),
std::get<1>(moveTo));
}
//game.muxer.playSoundAtPosition("dash", game.player_x, game.player_y);
}
}
}
int main(int, char**)
{
std::random_device randomEngine;
std::mt19937 rng(randomEngine());
try
{
Renderer renderer;
Game game(rng);
for (MapData& md : game.map.data())
{
if (std::bernoulli_distribution(0.5)(rng))
{
md.tile = Tile::Wall;
}
}
tick(game);
tick(game);
for (int y = -1; y <= 1; y++)
{
for (int x = -1; x <= 1; x++)
{
game.map.at(x,y).tile = Tile::Floor;
}
}
tick(game);
bool quit = false;
LoseState losing = LoseState::None;
Input keystate;
SDL_Event e;
size_t dustDt = 40;
size_t dustAcc = 0;
size_t inputDt = 50;
size_t inputAcc = 0;
size_t losePopLampDt = 800;
size_t losePopLampAcc = losePopLampDt;
size_t losePopPlayerDt = 3000;
size_t losePopPlayerAcc = 0;
size_t zoomDt = 62;
size_t zoomAcc = 0;
size_t lastTime = SDL_GetTicks();
while (!quit)
{
size_t currentTime = SDL_GetTicks();
size_t frameTime = currentTime - lastTime;
lastTime = currentTime;
while (SDL_PollEvent(&e))
{
if (e.type == SDL_QUIT)
{
if (losing != LoseState::None)
{
quit = true;
} else {
losing = LoseState::PoppingLamps;
game.muxer.stopMusic();
}
} else if (e.type == SDL_KEYDOWN)
{
switch (e.key.keysym.sym)
{
case SDLK_ESCAPE:
{
if (losing != LoseState::None)
{
quit = true;
} else {
losing = LoseState::PoppingLamps;
game.muxer.stopMusic();
}
break;
}
case SDLK_SPACE:
{
if (losing == LoseState::None)
{
if (game.moving) {
game.queueDash = true;
} else {
performDash(game, rng);
}
}
break;
}
}
}
}
const Uint8* state = SDL_GetKeyboardState(NULL);
keystate.left = state[SDL_SCANCODE_LEFT];
keystate.right = state[SDL_SCANCODE_RIGHT];
keystate.up = state[SDL_SCANCODE_UP];
keystate.down = state[SDL_SCANCODE_DOWN];
game.bumpCooldown.accumulate(frameTime);
if (game.alreadyBumped && keystate != game.lastInput && game.bumpCooldown.step()) {
game.alreadyBumped = false;
}
if (game.queueDash && !game.moving) {
game.queueDash = false;
if (state[SDL_SCANCODE_SPACE]) {
performDash(game, rng);
}
}
if (keystate.left || keystate.right || keystate.up || keystate.down)
{
game.firstInput = true;
game.lastInput = keystate;
} else if (losing == LoseState::None) {
game.playerAnim.setAnimation("still");
}
dustAcc += frameTime;
inputAcc += frameTime;
while (dustAcc >= dustDt)
{
game.numDust = 0;
for (MapData& md : game.map.data())
{
if (md.tile == Tile::Dust)
{
md.dustLife--;
if (md.dustLife <= 0)
{
md.tile = Tile::Floor;
game.dirtyLighting = true;
} else {
game.numDust++;
}
}
}
processKickup(game);
dustAcc -= dustDt;
}
switch (losing)
{
case LoseState::None:
{
if (game.moving) {
game.moveProgress.tick(frameTime);
if (game.moveProgress.isComplete()) {
game.moving = false;
}
}
while (inputAcc >= inputDt)
{
if (!game.moving) {
processKeys(game, keystate);
}
inputAcc -= inputDt;
}
break;
}
case LoseState::PoppingLamps:
{
if (game.numLamps == 0)
{
if (game.numDust == 0)
{
losing = LoseState::PoppingPlayer;
}
} else {
losePopLampAcc += frameTime;
while (losePopLampAcc >= losePopLampDt)
{
std::vector<std::tuple<int, int>> lamps;
for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
{
for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
{
if (game.map.at(x,y).tile == Tile::Lamp)
{
lamps.emplace_back(x, y);
}
}
}
std::uniform_int_distribution<int> lampDist(0, lamps.size() - 1);
std::tuple<int, int> popPos = lamps[lampDist(rng)];
popLamp(game, std::get<0>(popPos), std::get<1>(popPos), 1);
losePopLampAcc -= losePopLampDt;
}
}
break;
}
case LoseState::PoppingPlayer:
{
losePopPlayerAcc += frameTime;
if (losePopPlayerAcc >= losePopPlayerDt)
{
popLamp(game, game.player_x, game.player_y, 10);
game.renderPlayer = false;
losing = LoseState::Outro;
}
break;
}
case LoseState::Outro:
{
if (game.numDust == 0)
{
quit = true;
}
break;
}
}
if (game.dirtyLighting)
{
recalculateLighting(game);
for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
{
for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
{
if (!game.map.at(x,y).lit && game.map.at(x,y).wasLit)
{
if (std::bernoulli_distribution(0.5)(rng))
{
game.map.at(x,y).tile = Tile::Wall;
} else {
game.map.at(x,y).tile = Tile::Floor;
}
game.map.at(x,y).dirtyRender = true;
}
}
}
tick(game, true);
tick(game, true);
tick(game, true);
// TODO: better zoom algorithm
setZoom(game, game.litSpots / 1500 * 2 + INIT_ZOOM);
}
if (game.dirtyRender) {
recalculateRender(game);
}
zoomAcc += frameTime;
while (zoomAcc >= zoomDt)
{
if (game.zooming)
{
game.zoomProgress++;
if (game.zoomProgress == game.zoomLength)
{
game.zooming = false;
}
}
zoomAcc -= zoomDt;
}
game.playerAnim.update(frameTime);
game.muxer.update();
renderer.render(game, true);
}
} catch (const sdl_error& ex)
{
std::cout << "SDL error (" << ex.what() << ")" << std::endl;
} catch (const img_error& ex)
{
std::cout << "SDL_IMG error (" << ex.what() << ")" << std::endl;
}
return 0;
}