summary refs log blame commit diff stats
path: root/src/main.cpp
blob: 2f18995b6cdfede514a99efbb49425a7fd140f48 (plain) (tree)
1
2
3
4
5
6
7
8
9
                 
                
                   
                 
                     
 
                                                                    
 









                                                                                             



            
                                           

                                        






                        
 
                              
 
                                                                
   
                                                                  
     




                                                             


                 
                                              




                    







                                            

                     
                                       
              
                                        
       



                                                       

     
                             
 
                                            
 






                         
 
                                         
 



                                                          
   
                                                                      
     
                                                                  
                                                             
                     
     
                                     
                      
                                  
                       
                                
 
                              

                



                                                   
 
                 

   
                                    
 
                    
                          
                                     
   
                       
                        


                                
   
 

                          
                                         
         


                                                                           

                                           
         
                                                                 

                                             
       
                                                                         
 
                                  
         
                          
         
 
                                    
 
                                         

       
                                                                
   
                                                                  
     
                               
                      
 
                                                                        
                            
                                                     
       
                          
                                                     
                          
                             
       
                                      
 
                             
       
                                                   
                   
               
                                    
                                                

                       
 
                                    

       
 
                             
 































































































                                                                                                                              
                                                   
 
                         
                                



                  
 
                    
   
         
                          
   
                    
   
         
                          
   
                     
   
         
                           
   
                                                    
   

                                         

                                    

   
                                                       
 




                                                 
                         
 
                             
 
                                                    
 
                                              
                                          
   
                    
   
                                     
                                
                            
 
                                
 
                              
 
                                     

                 
                                        
     
                                            
 


                                                     
         

                                
                                                   
           
                                               
                                          

                                                         
           
                                                  
           



























                                                             

           
                 



                                         













                                     














                                                                  
                                               













                                     








                                                                  
                      
















                                                                           
 



                                     
                             
                                


                                
   
 








































































                                                                             



                                   
     
                      
 
                   
 
                                       
     
                                                
       
                             

       
               




                                            

       
               
                      
                                       
                   
                
 
                       



                        
                               
                                          
                                  
                                

                       
                                     
                 


                                                


                               



                                             
                                   
           
                                         
                                   
                             



                                                 
                                       
               

                    
                            
                                            
               


                                         
                 
               
                    



             




                                                      



                                                                                         





                                           


                                                                          
                                              
       
                            
 
                               
       
                         
                                           
         
                                    
           

                                 
             


                                        

             
 
                            
                          
       
                     
       
                             





                                                 
                                     

                                          

                                
 



                                     
                                 
           
                                  








                                                      
                                                                            
               
                                                                              
                 
                                                          







                                                                               
                                                                         





                                              
 




                                                  
                                                            







                                      
                                




                        
       
                             
       
                                  
 
                                                                      
         
                                                                        
           
                                                                 

                                                        
                                                   
                      
                                                    
               
                                                  


             

                         

                                                            
       


                                




                               
                              
 
                                                   




                                 
       
                                        
                          
                                  
     
                               
   
                                                                

                                                                    
   
           
 
#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;
}