From be9ccb73bc20b03f62c77f5d529602a10ef4eda9 Mon Sep 17 00:00:00 2001
From: Star Rauchenberger <fefferburbia@gmail.com>
Date: Sat, 12 Mar 2022 12:09:58 -0500
Subject: player has a sprite now thanks to world of solaria

---
 CMakeLists.txt      |   1 +
 res/player.png      | Bin 0 -> 6428 bytes
 res/player_anim.txt |  12 +++++++
 src/animation.cpp   |  93 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/animation.h     |  39 ++++++++++++++++++++++
 src/direction.h     |  22 +++++++++++++
 src/game.h          |   2 ++
 src/main.cpp        |  11 +++++++
 src/renderer.cpp    |  22 ++++++++++---
 src/renderer.h      |   1 +
 src/util.h          |  35 ++++++++++++++++++++
 11 files changed, 234 insertions(+), 4 deletions(-)
 create mode 100755 res/player.png
 create mode 100644 res/player_anim.txt
 create mode 100644 src/animation.cpp
 create mode 100644 src/animation.h
 create mode 100644 src/direction.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e499ef2..360d4a2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,6 +32,7 @@ add_executable(Ether
   src/main.cpp
   src/renderer.cpp
   src/muxer.cpp
+  src/animation.cpp
   vendor/fov.c
 )
 
diff --git a/res/player.png b/res/player.png
new file mode 100755
index 0000000..9f6dbdf
Binary files /dev/null and b/res/player.png differ
diff --git a/res/player_anim.txt b/res/player_anim.txt
new file mode 100644
index 0000000..e1037a0
--- /dev/null
+++ b/res/player_anim.txt
@@ -0,0 +1,12 @@
+16,16 cell size
+8 frames per row
+128 frames
+
+still[down]: 0,1,2,3
+still[left]: 8,9,10,11
+still[right]: 16,17,18,19
+still[up]: 24,25,26,27
+walk[down]: 32,33,34,35
+walk[left]: 40,41,42,43
+walk[right]: 48,49,50,51
+walk[up]: 56,57,58,59
\ No newline at end of file
diff --git a/src/animation.cpp b/src/animation.cpp
new file mode 100644
index 0000000..fbf7ccf
--- /dev/null
+++ b/src/animation.cpp
@@ -0,0 +1,93 @@
+#include "animation.h"
+#include <string_view>
+#include <fstream>
+#include <stdexcept>
+#include <string>
+#include <regex>
+#include <list>
+#include "direction.h"
+#include "util.h"
+
+Animation::Animation(std::string_view path) {
+  std::ifstream datafile(path.data());
+  if (!datafile.is_open()) {
+    throw std::invalid_argument(std::string("Could not find sprite datafile: ") + path.data());
+  }
+
+  std::string animLine;
+  char ch;
+  int cellWidth;
+  int cellHeight;
+  datafile >> cellWidth;
+  datafile >> ch; //,
+  datafile >> cellHeight;
+  std::getline(datafile, animLine); // cell size
+
+  int framesPerRow;
+  datafile >> framesPerRow;
+  std::getline(datafile, animLine); // frames per row
+
+  int numFrames;
+  datafile >> numFrames;
+  std::getline(datafile, animLine); // frames
+  std::getline(datafile, animLine); // blank
+
+  for (int i=0; i<numFrames; i++) {
+    SDL_Rect srcRect;
+    srcRect.x = (i % framesPerRow) * cellWidth;
+    srcRect.y = (i / framesPerRow) * cellHeight;
+    srcRect.w = cellWidth;
+    srcRect.h = cellHeight;
+    frames_.push_back(srcRect);
+  }
+
+  while (std::getline(datafile, animLine)) {
+    std::regex re(R"(([a-z!._]+)\[([a-z_]+)\]: ([0-9,]+))");
+    std::smatch m;
+    std::regex_match(animLine, m, re);
+
+    std::string animName = m[1];
+    std::vector<int> anim;
+    auto framestrs = splitStr<std::list<std::string>>(m[3], ",");
+    for (const std::string& f : framestrs) {
+      anim.push_back(std::stoi(f));
+    }
+
+    int animId = animations_.size();
+    animations_.push_back(std::move(anim));
+
+    Direction dir = directionFromString(std::string(m[2]));
+    nameDirToAnim_[animName][dir] = animId;
+  }
+
+  updateAnim();
+}
+
+void Animation::setDirection(Direction dir) {
+  dir_ = dir;
+  updateAnim();
+}
+
+void Animation::setAnimation(std::string_view anim) {
+  animationName_ = anim;
+  updateAnim();
+}
+
+void Animation::update(int dt) {
+  animTimer_.accumulate(dt);
+
+  while (animTimer_.step()) {
+    animationFrame_++;
+
+    if (animationFrame_ >= animations_.at(animationId_).size()) {
+      animationFrame_ = 0;
+    }
+  }
+}
+
+void Animation::updateAnim() {
+  if (nameDirToAnim_.at(animationName_).at(dir_) != animationId_) {
+    animationId_ = nameDirToAnim_.at(animationName_).at(dir_);
+    animationFrame_ = 0;
+  }
+}
diff --git a/src/animation.h b/src/animation.h
new file mode 100644
index 0000000..0108c12
--- /dev/null
+++ b/src/animation.h
@@ -0,0 +1,39 @@
+#ifndef ANIMATION_H_332518EB
+#define ANIMATION_H_332518EB
+
+#include <string>
+#include <map>
+#include <vector>
+#include <string_view>
+#include <SDL.h>
+#include "direction.h"
+#include "timer.h"
+
+class Animation {
+public:
+  explicit Animation(std::string_view path);
+
+  void setDirection(Direction dir);
+  void setAnimation(std::string_view anim);
+
+  const SDL_Rect& getRenderRect() const {
+    return frames_.at(animations_.at(animationId_).at(animationFrame_));
+  }
+
+  void update(int dt);
+
+private:
+
+  void updateAnim();
+
+  std::vector<SDL_Rect> frames_;
+  std::vector<std::vector<int>> animations_;
+  std::map<std::string, std::map<Direction, int>> nameDirToAnim_;
+  Direction dir_ = Direction::down;
+  std::string animationName_ = "still";
+  int animationId_ = 0;
+  int animationFrame_ = 0;
+  Timer animTimer_ = {1000/5};
+};
+
+#endif /* end of include guard: ANIMATION_H_332518EB */
diff --git a/src/direction.h b/src/direction.h
new file mode 100644
index 0000000..0fe3e5a
--- /dev/null
+++ b/src/direction.h
@@ -0,0 +1,22 @@
+#ifndef DIRECTION_H_42BDAFB9
+#define DIRECTION_H_42BDAFB9
+
+#include <string_view>
+#include <stdexcept>
+
+enum class Direction {
+  up,
+  down,
+  left,
+  right
+};
+
+inline Direction directionFromString(std::string_view str) {
+  if (str == "up") return Direction::up;
+  if (str == "right") return Direction::right;
+  if (str == "down") return Direction::down;
+  if (str == "left") return Direction::left;
+  throw std::invalid_argument("Invalid direction: " + std::string(str));
+}
+
+#endif /* end of include guard: DIRECTION_H_42BDAFB9 */
diff --git a/src/game.h b/src/game.h
index c489afc..2fd0f05 100644
--- a/src/game.h
+++ b/src/game.h
@@ -8,6 +8,7 @@
 #include "map.h"
 #include "muxer.h"
 #include "timer.h"
+#include "animation.h"
 
 const int GAME_WIDTH = 640*2;
 const int GAME_HEIGHT = 480*2;
@@ -102,6 +103,7 @@ public:
   int player_x = 0;
   int player_y = 0;
   bool renderPlayer = true;
+  Animation playerAnim {"../res/player_anim.txt"};
 
   int maxZoom = INIT_ZOOM;
 
diff --git a/src/main.cpp b/src/main.cpp
index 1805357..5178110 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -207,6 +207,7 @@ bool processKeys(Game& game, const Input& keystate)
 {
   int px = game.player_x;
   int py = game.player_y;
+  Direction dir = Direction::up;
 
   if (keystate.up)
   {
@@ -216,20 +217,26 @@ bool processKeys(Game& game, const Input& keystate)
   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;
@@ -614,6 +621,8 @@ int main(int, char**)
       {
         game.firstInput = true;
         game.lastInput = keystate;
+      } else if (losing == LoseState::None) {
+        game.playerAnim.setAnimation("still");
       }
 
       dustAcc += frameTime;
@@ -767,6 +776,8 @@ int main(int, char**)
         zoomAcc -= zoomDt;
       }
 
+      game.playerAnim.update(frameTime);
+
       game.muxer.update();
       renderer.render(game, true);
     }
diff --git a/src/renderer.cpp b/src/renderer.cpp
index 2dac07e..0aaa14a 100644
--- a/src/renderer.cpp
+++ b/src/renderer.cpp
@@ -110,6 +110,18 @@ Renderer::Renderer()
 
   SDL_SetRenderDrawColor(ren_.get(), 100, 100, 100, 255);
   SDL_RenderFillRect(ren_.get(), nullptr);
+
+  {
+    surface_ptr pfs(IMG_Load("../res/player.png"));
+    if (!pfs)
+    {
+      throw img_error();
+    }
+
+    playerSheet_ = texture_ptr(SDL_CreateTextureFromSurface(ren_.get(), pfs.get()));
+  }
+
+  SDL_SetTextureBlendMode(playerSheet_.get(), SDL_BLENDMODE_BLEND);
 }
 
 void Renderer::render(
@@ -140,10 +152,7 @@ void Renderer::render(
     {
       bool draw = true;
 
-      if ((game.player_x == x && game.player_y == y) && game.renderPlayer)
-      {
-        SDL_SetRenderDrawColor(ren_.get(), 255, 255, 0, 255);
-      } else if (!game.map.at(x,y).lit)
+      if (!game.map.at(x,y).lit)
       {
         if (drawDark)
         {
@@ -191,6 +200,11 @@ void Renderer::render(
           TILE_HEIGHT};
 
         SDL_RenderFillRect(ren_.get(), &rect);
+
+        if ((game.player_x == x && game.player_y == y) && game.renderPlayer)
+        {
+          SDL_RenderCopy(ren_.get(), playerSheet_.get(), &game.playerAnim.getRenderRect(), &rect);
+        }
       }
     }
   }
diff --git a/src/renderer.h b/src/renderer.h
index a34b231..0416c9e 100644
--- a/src/renderer.h
+++ b/src/renderer.h
@@ -129,6 +129,7 @@ private:
   texture_ptr playerFade_;
   texture_ptr lampFade_;
   texture_ptr dustFade_;
+  texture_ptr playerSheet_;
 };
 
 #endif /* end of include guard: RENDERER_H_6A58EC30 */
diff --git a/src/util.h b/src/util.h
index 150a6a5..1eb2303 100644
--- a/src/util.h
+++ b/src/util.h
@@ -1,6 +1,9 @@
 #ifndef UTIL_H_E9110D4C
 #define UTIL_H_E9110D4C
 
+#include <string>
+#include <iterator>
+
 template <typename Container, typename Predicate>
 void erase_if(Container& items, const Predicate& predicate)
 {
@@ -17,4 +20,36 @@ void erase_if(Container& items, const Predicate& predicate)
   }
 };
 
+template <class OutputIterator>
+void splitStr(
+  std::string input,
+  std::string delimiter,
+  OutputIterator out) {
+  while (!input.empty()) {
+    int divider = input.find(delimiter);
+    if (divider == std::string::npos) {
+      *out = input;
+      out++;
+
+      input = "";
+    } else {
+      *out = input.substr(0, divider);
+      out++;
+
+      input = input.substr(divider+delimiter.length());
+    }
+  }
+}
+
+template <class Container>
+Container splitStr(
+  std::string input,
+  std::string delimiter) {
+  Container result;
+
+  splitStr(input, delimiter, std::back_inserter(result));
+
+  return result;
+}
+
 #endif /* end of include guard: UTIL_H_E9110D4C */
-- 
cgit 1.4.1