From: Daniel Karbach Date: Sun, 13 Oct 2024 19:37:12 +0000 (+0200) Subject: add simple drawing game X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=424d1ad45245da2891f6b0a3068de973ed359722;p=ffmpeg-test.git add simple drawing game --- diff --git a/src/app/Application.h b/src/app/Application.h index 84014cd..aa2d06c 100644 --- a/src/app/Application.h +++ b/src/app/Application.h @@ -1,9 +1,11 @@ #ifndef TEST_APP_APPLICATION_H_ #define TEST_APP_APPLICATION_H_ +#include #include #include +#include "DrawingGame.h" #include "Mixer.h" #include "Renderer.h" #include "State.h" @@ -28,7 +30,9 @@ public: , stream(url, width, height, fps) , mixer(stream.GetAudioPlane(), stream.GetAudioChannels(), stream.GetAudioFrameSize()) , renderer(stream.GetVideoPlane(), stream.GetVideoLineSize(), width, height) - , state(width, height) { + , state(width, height) + , drawing_game(renderer.GetContext(), 45, 50, { 1, 1, 1 }) { + state.SetGame(&drawing_game); } ~Application() { } @@ -68,7 +72,7 @@ public: if (target > 0 && difference < 0) { std::cout << (difference / -1000.0) << "s behind schedule, dropping frame" << std::endl; } else { - state.Update(stream.GetVideoClock()); + state.Update(renderer.GetContext(), stream.GetVideoClock()); renderer.RenderVideoFrame(state); } @@ -126,7 +130,89 @@ private: } void HandleTwitch(const twitch::IRCMessage &msg) { - std::cout << "got message: " << msg.GetText() << std::endl; + if (msg.nick == "horstiebot") return; + std::string text = msg.GetText(); + if (text.empty()) return; + for (auto i = text.begin(); i != text.end(); ++i) { + switch (*i) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + drawing_game.SetModifier(*i - '0'); + break; + case 'u': case 'U': + case 'n': case 'N': + drawing_game.MoveUp(); + break; + case 'd': case 'D': + case 's': case 'S': + drawing_game.MoveDown(); + break; + case 'l': case 'L': + case 'w': case 'W': + drawing_game.MoveLeft(); + break; + case 'r': case 'R': + case 'e': case 'E': + drawing_game.MoveRight(); + break; + case 'a': case 'A': + drawing_game.SetRed(); + break; + case 'b': case 'B': + drawing_game.SetGreen(); + break; + case 'c': case 'C': + drawing_game.SetBlue(); + break; + case 'f': case 'F': + drawing_game.ClearRed(); + break; + case 'g': case 'G': + drawing_game.ClearGreen(); + break; + case 'h': case 'H': + drawing_game.ClearBlue(); + break; + case 'i': case 'I': + drawing_game.SetYellow(); + break; + case 'j': case 'J': + drawing_game.SetCyan(); + break; + case 'k': case 'K': + drawing_game.SetMagenta(); + break; + case 'm': case 'M': + drawing_game.ClearYellow(); + break; + case 'o': case 'O': + drawing_game.ClearCyan(); + break; + case 'p': case 'P': + drawing_game.ClearMagenta(); + break; + case 'q': case 'Q': + drawing_game.InvertRed(); + break; + case 't': case 'T': + drawing_game.InvertGreen(); + break; + case 'v': case 'V': + drawing_game.InvertBlue(); + break; + case 'x': case 'X': + drawing_game.InvertColor(); + break; + case 'y': case 'Y': + drawing_game.SetColor({ 1, 1, 1 }); + break; + case 'z': case 'Z': + drawing_game.SetColor({ 0, 0, 0 }); + break; + default: + break; + } + } } private: @@ -140,6 +226,8 @@ private: Renderer renderer; State state; + DrawingGame drawing_game; + }; } diff --git a/src/app/DrawingGame.h b/src/app/DrawingGame.h new file mode 100644 index 0000000..6f9b629 --- /dev/null +++ b/src/app/DrawingGame.h @@ -0,0 +1,194 @@ +#ifndef TEST_APP_DRAWINGGAME_H_ +#define TEST_APP_DRAWINGGAME_H_ + +#include +#include + +#include "Game.h" +#include "../gfx/ColorRGB.h" +#include "../pango/Layout.h" + +namespace app { + +class DrawingGame: public Game { + +public: + DrawingGame(cairo::Context &ctx, int w, int h, const gfx::ColorRGB &base_color) + : Game() + , w(w), h(h), x(0), y(0) + , modifier(1) + , pos{ 760, 100 } + , font_color{ 1, 1, 1 } + , cursor_font("DejaVu Sans 16px") + , cursor_layout(ctx.CreateLayout()) + , text_dirty(true) { + cells.resize(w * h, base_color); + } + +public: + virtual void Update(cairo::Context &ctx, const Clock &now) { + if (text_dirty) { + UpdateText(ctx); + } + } + + virtual void Render(cairo::Context &ctx) const { + ctx.MoveTo(pos); + ctx.SetSourceColor(font_color); + ctx.DrawLayout(cursor_layout); + + gfx::Rectangle r{ 0, 0, 10, 10 }; + for (int cy = 0; cy < h; ++cy) { + for (int cx = 0; cx < w; ++cx) { + r.x = pos.x + cx * r.w; + r.y = pos.y + cy * r.h + 30; + ctx.SetSourceColor(cells[cy * w + cx]); + ctx.Rectangle(r); + ctx.Fill(); + } + } + r.x = pos.x + x * r.w; + r.y = pos.y + y * r.h + 30; + ctx.SetSourceColor({ 0.5, 0.5, 0.5 }); + ctx.SetLineWidth(1); + ctx.Rectangle(r); + ctx.Stroke(); + } + + virtual void Mix(const Clock &, float *plane, int channels, int frame_size) const { + } + +public: + void MoveUp() { + y = (y - modifier) % h; + while (y < 0) y += h; + text_dirty = true; + } + + void MoveDown() { + y = (y + modifier) % h; + while (y < 0) y += h; + text_dirty = true; + } + + void MoveLeft() { + x = (x - modifier) % w; + while (x < 0) x += w; + text_dirty = true; + } + + void MoveRight() { + x = (x + modifier) % w; + while (x < 0) x += w; + text_dirty = true; + } + + void SetColor(const gfx::ColorRGB &c) { + cells[y * w + x] = c; + } + + void SetRed() { + cells[y * w + x].r = double(modifier) / 9.0; + } + + void SetGreen() { + cells[y * w + x].g = double(modifier) / 9.0; + } + + void SetBlue() { + cells[y * w + x].b = double(modifier) / 9.0; + } + + void ClearRed() { + cells[y * w + x].r = 0; + } + + void ClearGreen() { + cells[y * w + x].g = 0; + } + + void ClearBlue() { + cells[y * w + x].b = 0; + } + + void SetYellow() { + cells[y * w + x].r = double(modifier) / 9.0; + cells[y * w + x].g = double(modifier) / 9.0; + } + + void SetCyan() { + cells[y * w + x].g = double(modifier) / 9.0; + cells[y * w + x].b = double(modifier) / 9.0; + } + + void SetMagenta() { + cells[y * w + x].r = double(modifier) / 9.0; + cells[y * w + x].b = double(modifier) / 9.0; + } + + void ClearYellow() { + cells[y * w + x].r = 0; + cells[y * w + x].g = 0; + } + + void ClearCyan() { + cells[y * w + x].g = 0; + cells[y * w + x].b = 0; + } + + void ClearMagenta() { + cells[y * w + x].r = 0; + cells[y * w + x].b = 0; + } + + void InvertRed() { + cells[y * w + x].r = 1.0 - cells[y * w + x].r; + } + + void InvertGreen() { + cells[y * w + x].g = 1.0 - cells[y * w + x].g; + } + + void InvertBlue() { + cells[y * w + x].b = 1.0 - cells[y * w + x].b; + } + + void InvertColor() { + cells[y * w + x].r = 1.0 - cells[y * w + x].r; + cells[y * w + x].g = 1.0 - cells[y * w + x].g; + cells[y * w + x].b = 1.0 - cells[y * w + x].b; + } + + void SetModifier(int m) { + modifier = std::max(0, std::min(9, m)); + text_dirty = true; + } + +private: + void UpdateText(cairo::Context &ctx) { + if (!text_dirty) return; + std::stringstream out; + out << "Cursor: " << x << ", " << y; + out << " Modifier: " << modifier; + cursor_layout.SetText(out.str()); + ctx.UpdateLayout(cursor_layout); + text_dirty = false; + } + +private: + std::vector cells; + int w, h, x, y; + int modifier; + + gfx::Position pos; + + gfx::ColorRGB font_color; + pango::Font cursor_font; + pango::Layout cursor_layout; + bool text_dirty; + +}; + +} + +#endif diff --git a/src/app/Game.h b/src/app/Game.h new file mode 100644 index 0000000..eabc3ce --- /dev/null +++ b/src/app/Game.h @@ -0,0 +1,26 @@ +#ifndef TEST_APP_GAME_H_ +#define TEST_APP_GAME_H_ + +#include "Clock.h" +#include "../cairo/Context.h" + +namespace app { + +class Game { + +public: + virtual ~Game() { + } + +public: + virtual void Update(cairo::Context &, const Clock &) = 0; + + virtual void Render(cairo::Context &) const = 0; + + virtual void Mix(const Clock &, float *plane, int channels, int frame_size) const = 0; + +}; + +} + +#endif diff --git a/src/app/Mixer.h b/src/app/Mixer.h index f761668..f1ca45f 100644 --- a/src/app/Mixer.h +++ b/src/app/Mixer.h @@ -28,6 +28,9 @@ public: for (const Media &media : state.GetMedia()) { media.Mix(clock, plane, channels, frame_size); } + if (state.HasGame()) { + state.GetGame().Mix(clock, plane, channels, frame_size); + } } private: diff --git a/src/app/Renderer.h b/src/app/Renderer.h index 923c6a7..76a658e 100644 --- a/src/app/Renderer.h +++ b/src/app/Renderer.h @@ -44,6 +44,10 @@ public: msg.Render(ctx); } + if (state.HasGame()) { + state.GetGame().Render(ctx); + } + surface.Flush(); } diff --git a/src/app/State.h b/src/app/State.h index d0d4eb1..fcf6828 100644 --- a/src/app/State.h +++ b/src/app/State.h @@ -5,6 +5,7 @@ #include #include "Clock.h" +#include "Game.h" #include "Media.h" #include "Message.h" #include "../cairo/Context.h" @@ -16,10 +17,30 @@ class State { public: State(int width, int height) - : width(width), height(height) { + : width(width), height(height), game(nullptr) { } public: + bool HasGame() const { + return game; + } + + Game &GetGame() { + return *game; + } + + const Game &GetGame() const { + return *game; + } + + void SetGame(Game *g) { + game = g; + } + + void ClearGame() { + game = nullptr; + } + const std::list &GetMedia() const { return media; } @@ -59,7 +80,7 @@ public: } } - void Update(const Clock &clock) { + void Update(cairo::Context &ctx, const Clock &clock) { gfx::Position pos({ 50, 50 }); for (Message &msg : msgs) { double distance = msg.GetHeight() + 10.0; @@ -68,6 +89,9 @@ public: msg.SetPosition(pos); pos.y = std::max(pos.y + distance, 50.0); } + if (HasGame()) { + GetGame().Update(ctx, clock); + } } void Clean() { @@ -88,6 +112,8 @@ private: int width; int height; + Game *game; + std::list media; std::list msgs; diff --git a/src/ws/Connection.cpp b/src/ws/Connection.cpp index 854245d..2109aff 100644 --- a/src/ws/Connection.cpp +++ b/src/ws/Connection.cpp @@ -217,6 +217,7 @@ int TwitchConnection::ProtoCallback(lws_callback_reasons reason, void *in, size_ break; case LWS_CALLBACK_CLIENT_CLOSED: connected = false; + std::cout << "twitch connection closed :/" << std::endl; break; case LWS_CALLBACK_CLIENT_RECEIVE: if (lws_is_first_fragment(wsi)) {