#include #include #include #include "util.h" #include "game.h" #include "renderer.h" 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) / 2; } void tick( Game& game, int x1, int y1, int x2, int y2, bool invert = false, bool onlyDark = false) { Map 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(33); 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(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(data); if (game.map.inBounds(x, y)) { MapData& sourceData = *static_cast(source); double lightRadius = static_cast(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(&game), static_cast(&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 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(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 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 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(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 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 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 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> 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 lampDist(0, lamps.size() - 1); std::tuple 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; }