#include "game.h"
#include <vector>
#include <fov.h>
#include <iostream>
#include "util.h"
#include "renderer.h"
#include "consts.h"
Game::Game(std::mt19937& rng, Muxer& muxer) :
rng(rng),
muxer(muxer)
{
losePopLampTimer.accumulate(losePopLampTimer.getDt());
loadMap();
for (int y = -1; y <= 1; y++)
{
for (int x = -1; x <= 1; x++)
{
map.at(x,y).tile = Tile::Floor;
}
}
tick();
}
inline bool isTileSetOrNotLit(const Map& 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& 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;
}
void Game::tick(
int x1,
int y1,
int x2,
int y2,
bool invert,
bool onlyDark)
{
mapDoubleBuffer = map.data();
for (int y = map.getTop(); y < map.getBottom(); y++)
{
for (int x = map.getLeft(); x < map.getRight(); x++)
{
if (invert == (x >= x1 && x < x2 && y >= y1 && y < y2))
{
continue;
}
if (onlyDark && map.at(x,y).lit)
{
continue;
}
if (map.at(x,y).tile == Tile::Lamp)
{
continue;
}
int count = 0;
incrementIfSet(*this, count, x-1, y-1);
incrementIfSet(*this, count, x-1, y );
incrementIfSet(*this, count, x-1, y+1);
incrementIfSet(*this, count, x , y-1);
incrementIfSet(*this, count, x , y );
incrementIfSet(*this, count, x , y+1);
incrementIfSet(*this, count, x+1, y-1);
incrementIfSet(*this, count, x+1, y );
incrementIfSet(*this, count, x+1, y+1);
int tempIndex = map.getTrueX(x) + map.getTrueY(y) * map.getWidth();
if (count >= 5)
{
mapDoubleBuffer[tempIndex].tile = Tile::Wall;
} else {
mapDoubleBuffer[tempIndex].tile = Tile::Floor;
}
if (mapDoubleBuffer[tempIndex].tile != map.at(x,y).tile) {
mapDoubleBuffer[tempIndex].dirtyRender = true;
dirtyRender = true;
}
}
}
std::swap(map.data(), mapDoubleBuffer);
}
void Game::tick(bool onlyDark)
{
tick(
map.getLeft(),
map.getTop(),
map.getRight(),
map.getBottom(),
false,
onlyDark);
}
bool Game::movePlayer(int x, int y)
{
if (map.at(x,y).tile == Tile::Floor)
{
if (map.at(player_x, player_y).tile == Tile::Floor)
{
map.at(player_x, player_y).tile = Tile::Dust;
map.at(player_x, player_y).dustLife = 1;
numDust++;
}
player_oldx = player_x;
player_oldy = player_y;
player_x = x;
player_y = y;
muxer.setPlayerLoc(x, y);
moving = true;
moveProgress.start(1000/6);
dirtyLighting = true;
int chunkX, chunkY, old_chunkX, old_chunkY;
toChunkPos(player_x, player_y, chunkX, chunkY);
toChunkPos(player_oldx, player_oldy, old_chunkX, old_chunkY);
if ((chunkX != old_chunkX) || (chunkY != old_chunkY)) {
loadMap();
}
return true;
} else {
if (!alreadyBumped) {
muxer.playSoundAtPosition("bump", x, y);
alreadyBumped = true;
bumpCooldown.reset();
}
return false;
}
}
void Game::recalculateLighting()
{
litSpots = 0;
dirtyRender = true;
for (MapData& md : 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 = map.getTop(); y < map.getBottom(); y++)
{
for (int x = map.getLeft(); x < map.getRight(); x++)
{
Source ls = Source::None;
int lightRadius;
if (renderPlayer && player_x == x && player_y == y)
{
ls = Source::Player;
lightRadius = RADIUS;
} else if (map.at(x,y).tile == Tile::Dust)
{
ls = Source::Dust;
lightRadius = 2;
} else if (map.at(x,y).tile == Tile::Lamp)
{
ls = Source::Lamp;
lightRadius = RADIUS;
}
map.at(x,y).lightType = ls;
if (ls != Source::None)
{
map.at(x,y).lightRadius = lightRadius;
map.at(x,y).litTiles.emplace(x,y);
fov_circle(
&fov,
static_cast<void*>(this),
static_cast<void*>(&map.at(x,y)),
x,
y,
lightRadius);
map.at(x,y).lit = true;
}
}
}
litSpots += map.getUnloadedLitTiles();
dirtyLighting = false;
}
void Game::recalculateRender() {
for (int y = map.getTop(); y < map.getBottom(); y++)
{
for (int x = map.getLeft(); x < map.getRight(); x++)
{
if (map.at(x,y).dirtyRender) {
map.at(x,y).dirtyRender = false;
if (map.at(x,y).tile == Tile::Floor) {
if (std::bernoulli_distribution(0.05)(rng)) {
static const std::vector<int> furnishings {
TilesetIndex(20, 16),
TilesetIndex(21, 2),
TilesetIndex(22, 2),
TilesetIndex(21, 3),
TilesetIndex(22, 3)};
map.at(x,y).renderId = furnishings.at(std::uniform_int_distribution<int>(0, furnishings.size()-1)(rng));
} else {
map.at(x,y).renderId = -1;
}
} else if (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(map, x-1, y-1)) renderDesc |= (1 << 7);
if (isTileSetOrNotLit(map, x , y-1)) renderDesc |= (1 << 6);
if (isTileSetOrNotLit(map, x+1, y-1)) renderDesc |= (1 << 5);
if (isTileSetOrNotLit(map, x+1, y )) renderDesc |= (1 << 4);
if (isTileSetOrNotLit(map, x+1, y+1)) renderDesc |= (1 << 3);
if (isTileSetOrNotLit(map, x , y+1)) renderDesc |= (1 << 2);
if (isTileSetOrNotLit(map, x-1, y+1)) renderDesc |= (1 << 1);
if (isTileSetOrNotLit(map, x-1, y )) renderDesc |= (1 << 0);
map.at(x,y).renderId = wallRenders.at(renderDesc);
if (wallRenders.at(renderDesc) == 0 && renderDesc != 255) {
std::cout << renderDesc << std::endl;
}
}
}
}
}
dirtyRender = false;
}
bool Game::processKeys(const Input& keystate)
{
int px = player_x;
int py = 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 (!(player_x == px && player_y == py))
{
playerAnim.setAnimation("walk");
playerAnim.setDirection(dir);
bool succeeds = movePlayer(px, py);
if (!succeeds && px != player_x) {
succeeds = movePlayer(px, player_y);
}
if (!succeeds && py != player_y) {
succeeds = movePlayer(player_x, py);
}
return succeeds;
} else {
return false;
}
}
void Game::kickUpDust(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);
kickups.push_back(dk);
}
void Game::popLamp(int x, int y, size_t chain)
{
muxer.playSoundAtPosition("pop", x, y);
if (map.at(x,y).tile == Tile::Lamp)
{
numLamps--;
}
map.at(x,y).tile = Tile::Dust;
map.at(x,y).dustLife = 2;
numDust++;
dirtyLighting = true;
kickUpDust(x, y, chain);
}
void Game::processKickup()
{
for (Kickup& kickup : kickups)
{
kickup.cur++;
std::set<coord> newFront;
for (const coord& xy : kickup.front)
{
auto processDir = [&] (int x, int y) {
coord c {x,y};
if (map.inBounds(x,y) &&
(map.at(x,y).tile == Tile::Floor || map.at(x,y).tile == Tile::Lamp) &&
!kickup.done.count(c))
{
newFront.insert(c);
kickup.done.insert(c);
if (map.at(x,y).tile == Tile::Floor)
{
map.at(x,y).tile = Tile::Dust;
map.at(x,y).dustLife = 2;
numDust++;
dirtyLighting = true;
} else if (map.at(x,y).tile == Tile::Lamp)
{
popLamp(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)(rng))
{
processDir(std::get<0>(xy) - 1, std::get<1>(xy) - 1);
}
if (std::bernoulli_distribution(0.5)(rng))
{
processDir(std::get<0>(xy) - 1, std::get<1>(xy) + 1);
}
if (std::bernoulli_distribution(0.5)(rng))
{
processDir(std::get<0>(xy) + 1, std::get<1>(xy) - 1);
}
if (std::bernoulli_distribution(0.5)(rng))
{
processDir(std::get<0>(xy) + 1, std::get<1>(xy) + 1);
}
}
kickup.front.swap(newFront);
}
erase_if(
kickups,
[] (const Kickup& kickup) {
return kickup.cur == kickup.radius;
});
}
void Game::loadMap() {
int newChunksHoriz = std::ceil(static_cast<double>(curZoom) * ZOOM_X_FACTOR / CHUNK_WIDTH) + 4;
int newChunksVert = std::ceil(static_cast<double>(curZoom) * ZOOM_Y_FACTOR / CHUNK_HEIGHT) + 4;
int curPlayerChunkX, curPlayerChunkY;
toChunkPos(player_x, player_y, curPlayerChunkX, curPlayerChunkY);
map.load(
curPlayerChunkX - newChunksHoriz / 2,
curPlayerChunkY - newChunksVert / 2,
newChunksHoriz,
newChunksVert,
rng);
tick();
tick();
tick();
dirtyLighting = true;
dirtyRender = true;
}
void Game::setZoom(size_t zoom)
{
if (zoom == curZoom)
{
return;
}
/*zoomProgress = 0;
zoomLength = std::abs(static_cast<long>(zoom - curZoom)) * TILE_WIDTH;*/
zoomProgress.start(62 * std::abs(static_cast<long>(zoom - curZoom)) * TILE_WIDTH);
oldZoom = curZoom;
curZoom = zoom;
zooming = true;
loadMap();
int zoomLevel = getZoomLevel(*this);
if (zoomLevel == 0) {
muxer.setMusicLevel(0);
} else if (zoomLevel < 3) {
muxer.setMusicLevel(1);
} else if (zoomLevel < 5) {
muxer.setMusicLevel(2);
} else if (zoomLevel < 7) {
muxer.setMusicLevel(3);
} else {
muxer.setMusicLevel(4);
}
}
void Game::performDash() {
if (map.at(player_x, player_y).tile == Tile::Floor) {
std::vector<coord> freeSpaces;
auto addIfFree = [&] (int x, int y) {
if (map.inBounds(x,y) && map.at(x,y).tile == Tile::Floor)
{
freeSpaces.emplace_back(x, y);
}
};
addIfFree(player_x - 1, player_y - 1);
addIfFree(player_x , player_y - 1);
addIfFree(player_x + 1, player_y - 1);
addIfFree(player_x - 1, player_y );
addIfFree(player_x + 1, player_y );
addIfFree(player_x - 1, player_y + 1);
addIfFree(player_x , player_y + 1);
addIfFree(player_x + 1, player_y + 1);
if (!freeSpaces.empty())
{
map.at(player_x, player_y).tile = Tile::Lamp;
numLamps++;
dirtyLighting = true;
kickUpDust(player_x, player_y, 0);
muxer.playSoundAtPosition("drop", player_x, player_y);
if (firstInput)
{
for (int i = 0; i < 5; i++)
{
if (!processKeys(lastInput))
{
std::uniform_int_distribution<int> freeDist(0, freeSpaces.size() - 1);
int freeIndex = freeDist(rng);
coord& moveTo = freeSpaces[freeIndex];
movePlayer(std::get<0>(moveTo), std::get<1>(moveTo));
}
tick(
player_x - (RADIUS - 1),
player_y - (RADIUS - 1),
player_x + RADIUS,
player_y + RADIUS);
}
} else {
std::uniform_int_distribution<int> freeDist(0, freeSpaces.size() - 1);
int freeIndex = freeDist(rng);
coord& moveTo = freeSpaces[freeIndex];
movePlayer(std::get<0>(moveTo), std::get<1>(moveTo));
}
//muxer.playSoundAtPosition("dash", player_x, player_y);
}
}
}
void Game::update(size_t frameTime) {
SDL_Event e;
while (SDL_PollEvent(&e))
{
if (e.type == SDL_QUIT)
{
if (losing != LoseState::None)
{
quit = true;
} else {
losing = LoseState::PoppingLamps;
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;
muxer.stopMusic();
}
break;
}
case SDLK_SPACE:
{
if (losing == LoseState::None)
{
if (moving) {
queueDash = true;
} else {
performDash();
}
}
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];
bumpCooldown.accumulate(frameTime);
if (alreadyBumped && keystate != lastInput && bumpCooldown.step()) {
alreadyBumped = false;
}
if (queueDash && !moving) {
queueDash = false;
performDash();
}
if (keystate.left || keystate.right || keystate.up || keystate.down)
{
firstInput = true;
lastInput = keystate;
} else if (losing == LoseState::None) {
playerAnim.setAnimation("still");
}
dustTimer.accumulate(frameTime);
inputTimer.accumulate(frameTime);
while (dustTimer.step())
{
numDust = 0;
for (MapData& md : map.data())
{
if (md.tile == Tile::Dust)
{
md.dustLife--;
if (md.dustLife <= 0)
{
md.tile = Tile::Floor;
dirtyLighting = true;
} else {
numDust++;
}
}
}
processKickup();
}
switch (losing)
{
case LoseState::None:
{
if (moving) {
moveProgress.tick(frameTime);
if (moveProgress.isComplete()) {
moving = false;
}
}
while (inputTimer.step())
{
if (!moving) {
processKeys(keystate);
}
}
break;
}
case LoseState::PoppingLamps:
{
if (numLamps == 0)
{
if (numDust == 0)
{
losing = LoseState::PoppingPlayer;
}
} else {
losePopLampTimer.accumulate(frameTime);
while (losePopLampTimer.step())
{
std::vector<std::tuple<int, int>> lamps;
for (int y = map.getTop(); y < map.getBottom(); y++)
{
for (int x = map.getLeft(); x < map.getRight(); x++)
{
if (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(std::get<0>(popPos), std::get<1>(popPos), 1);
}
}
break;
}
case LoseState::PoppingPlayer:
{
losePopPlayerTimer.accumulate(frameTime);
if (losePopPlayerTimer.step())
{
popLamp(player_x, player_y, 10);
renderPlayer = false;
losing = LoseState::Outro;
}
break;
}
case LoseState::Outro:
{
if (numDust == 0)
{
quit = true;
}
break;
}
}
if (dirtyLighting)
{
recalculateLighting();
for (int y = map.getTop(); y < map.getBottom(); y++)
{
for (int x = map.getLeft(); x < map.getRight(); x++)
{
if (!map.at(x,y).lit && map.at(x,y).wasLit)
{
if (std::bernoulli_distribution(0.5)(rng))
{
map.at(x,y).tile = Tile::Wall;
} else {
map.at(x,y).tile = Tile::Floor;
}
map.at(x,y).dirtyRender = true;
}
}
}
/*int x1 = player_x - curZoom * ZOOM_X_FACTOR / 2 - 1;
int y1 = player_y - curZoom * ZOOM_Y_FACTOR / 2 - 1;
int x2 = player_x + curZoom * ZOOM_X_FACTOR / 2 + 2;
int y2 = player_y + curZoom * ZOOM_Y_FACTOR / 2 + 2;*/
int x1 = player_x - CHUNK_WIDTH / 2 - 1;
int y1 = player_y - CHUNK_HEIGHT / 2 - 1;
int x2 = player_x + CHUNK_WIDTH / 2 + 2;
int y2 = player_y + CHUNK_HEIGHT / 2 + 2;
tick(x1, y1, x2, y2, false, true);
tick(x1, y1, x2, y2, false, true);
tick(x1, y1, x2, y2, false, true);
// TODO: better zoom algorithm
setZoom(litSpots / 1500 + INIT_ZOOM);
}
if (dirtyRender) {
recalculateRender();
}
/*zoomTimer.accumulate(frameTime);
while (zoomTimer.step())
{
if (zooming)
{
zoomProgress++;
if (zoomProgress == zoomLength)
{
zooming = false;
}
}
}*/
if (zooming) {
zoomProgress.tick(frameTime);
if (zoomProgress.isComplete()) {
zooming = false;
}
}
playerAnim.update(frameTime);
}