]> git.localhorst.tv Git - ffmpeg-test.git/commitdiff
add simple drawing game
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Sun, 13 Oct 2024 19:37:12 +0000 (21:37 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Sun, 13 Oct 2024 19:37:12 +0000 (21:37 +0200)
src/app/Application.h
src/app/DrawingGame.h [new file with mode: 0644]
src/app/Game.h [new file with mode: 0644]
src/app/Mixer.h
src/app/Renderer.h
src/app/State.h
src/ws/Connection.cpp

index 84014cd2eaddf1405ec1a7cf8352d5212d42aca7..aa2d06c70c236d28c64f25cef3f3c1dee6316e7a 100644 (file)
@@ -1,9 +1,11 @@
 #ifndef TEST_APP_APPLICATION_H_
 #define TEST_APP_APPLICATION_H_
 
+#include <cstdlib>
 #include <thread>
 #include <json/value.h>
 
+#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 (file)
index 0000000..6f9b629
--- /dev/null
@@ -0,0 +1,194 @@
+#ifndef TEST_APP_DRAWINGGAME_H_
+#define TEST_APP_DRAWINGGAME_H_
+
+#include <sstream>
+#include <vector>
+
+#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<gfx::ColorRGB> 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 (file)
index 0000000..eabc3ce
--- /dev/null
@@ -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
index f7616680a573767ec680e69e9afdcec9d44c91f2..f1ca45fd2fdf9f899fb2c93e115f5a02a176d1b9 100644 (file)
@@ -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:
index 923c6a734346b10cf46701d2b9f6e577aa5070d5..76a658ef19062f10b05dd0d8af98574a44e4e568 100644 (file)
@@ -44,6 +44,10 @@ public:
                        msg.Render(ctx);
                }
 
+               if (state.HasGame()) {
+                       state.GetGame().Render(ctx);
+               }
+
                surface.Flush();
        }
 
index d0d4eb115f80776ee1971bf167f72cdb3e5308c5..fcf6828a361fbbfde5c3563a057d1a0cb38b26c2 100644 (file)
@@ -5,6 +5,7 @@
 #include <ostream>
 
 #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<Media> &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> media;
        std::list<Message> msgs;
 
index 854245d0797bfd6cacde6a33256404386054c492..2109aff7d1b3e77d89365b9ab40ad1ea2a83df19 100644 (file)
@@ -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)) {