From 016d1cdf036039792f50e1ed0431386c7b9e93d7 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 13 Mar 2022 13:21:17 -0400 Subject: refactored game code into the game class (for titles / multiple games) --- src/game.cpp | 885 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 885 insertions(+) create mode 100644 src/game.cpp (limited to 'src/game.cpp') diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000..8de9b7b --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,885 @@ +#include "game.h" +#include +#include +#include +#include "util.h" +#include "renderer.h" + +Game::Game(std::mt19937& rng, Muxer& muxer) : + rng(rng), + muxer(muxer), + map( + -INIT_ZOOM * ZOOM_X_FACTOR / 2, + -INIT_ZOOM * ZOOM_Y_FACTOR / 2, + INIT_ZOOM * ZOOM_X_FACTOR, + INIT_ZOOM * ZOOM_Y_FACTOR) +{ + losePopLampTimer.accumulate(losePopLampTimer.getDt()); + + for (MapData& md : map.data()) + { + if (std::bernoulli_distribution(0.5)(rng)) + { + md.tile = Tile::Wall; + } + } + + tick(); + tick(); + + 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) +{ + Map temp(map); + + 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); + + if (count >= 5) + { + temp.at(x,y).tile = Tile::Wall; + } else { + temp.at(x,y).tile = Tile::Floor; + } + + if (temp.at(x,y).tile != map.at(x,y).tile) { + temp.at(x,y).dirtyRender = true; + dirtyRender = true; + } + } + } + + map = std::move(temp); +} + +void Game::tick(bool onlyDark) +{ + tick( + map.getLeft(), + map.getTop(), + map.getRight(), + map.getBottom(), + false, + onlyDark); +} + +bool Game::movePlayer(int x, int y) +{ + if (x >= curBoundX && + y >= curBoundY && + x < curBoundX + curZoom * ZOOM_X_FACTOR && + y < curBoundY + curZoom * ZOOM_Y_FACTOR && + 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(66); + dirtyLighting = true; + + 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(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 = 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(this), + static_cast(&map.at(x,y)), + x, + y, + lightRadius); + + map.at(x,y).lit = true; + } + } + } + + 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 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(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 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 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::growMap(size_t zoom) +{ + int ol = map.getLeft(); + int ot = map.getTop(); + int ow = map.getWidth(); + int oh = map.getHeight(); + + map.resize( + -zoom * ZOOM_X_FACTOR / 2, + -zoom * ZOOM_Y_FACTOR / 2, + zoom * ZOOM_X_FACTOR, + zoom * ZOOM_Y_FACTOR); + + maxZoom = zoom; + + for (int y = map.getTop(); y < map.getBottom(); y++) + { + for (int x = map.getLeft(); x < map.getRight(); x++) + { + if (!(x >= ol && x < (ol + ow) && y >= ot && y < (ot + oh))) + { + if (std::bernoulli_distribution(0.5)(rng)) + { + map.at(x,y).tile = Tile::Wall; + } + } + } + } + + for (int i = 0; i < 3; i++) + { + tick(ol, ot, ol + ow, ot + oh, true); + } +} + +void Game::setZoom(size_t zoom) +{ + if (zoom == curZoom) + { + return; + } + + if (zoom > maxZoom) + { + growMap(zoom); + } + + std::tie( + lastZoomLeft, + lastZoomTop, + lastZoomWidth, + lastZoomHeight) = + Renderer::calculateZoomRect(*this); + + zoomProgress = 0; + zoomLength = std::abs(static_cast(zoom - curZoom)) * TILE_WIDTH; + curZoom = zoom; + zooming = true; + + curBoundX = player_x - zoom * ZOOM_X_FACTOR / 2; + if (curBoundX < map.getLeft()) + { + curBoundX = map.getLeft(); + } else if (curBoundX + zoom * ZOOM_X_FACTOR >= map.getRight()) + { + curBoundX = map.getRight() - zoom * ZOOM_X_FACTOR; + } + + curBoundY = player_y - zoom * ZOOM_Y_FACTOR / 2; + if (curBoundY < map.getTop()) + { + curBoundY = map.getTop(); + } else if (curBoundY + zoom * ZOOM_Y_FACTOR >= map.getBottom()) + { + curBoundY = map.getBottom() - zoom * ZOOM_Y_FACTOR; + } + + 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 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 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 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> 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 lampDist(0, lamps.size() - 1); + std::tuple 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; + } + } + } + + tick(true); + tick(true); + tick(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; + } + } + } + + playerAnim.update(frameTime); +} -- cgit 1.4.1