diff --git a/escape_turings_tarpit.cpp b/escape_turings_tarpit.cpp index f50f4ca..32cf91f 100644 --- a/escape_turings_tarpit.cpp +++ b/escape_turings_tarpit.cpp @@ -1,17 +1,33 @@ -#include -#include -#include -#include -#include -#include #include "dbc.hpp" #include "game_engine.hpp" -#include -#include -#include +#include // for milliseconds #include -#include #include +#include +#include +#include +#include +#include +#include // for Event +#include // for ftxui +#include // for text, separator, Element, operator|, vbox, border +#include +#include +#include // for allocator, shared_ptr +#include +#include +#include // for EXIT_SUCCESS +#include // for operator+, to_string +#include // for sleep_for +#include +#include + +#include "ftxui/component/component.hpp" // for CatchEvent, Renderer, operator|= +#include "ftxui/component/loop.hpp" // for Loop +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive + +using namespace ftxui; +using namespace std; using namespace std; using namespace fmt; @@ -19,8 +35,14 @@ namespace fs = std::filesystem; const auto ERROR = fmt::emphasis::bold | fg(fmt::color::red); +vector lines; + #define BUF_MAX 1024 +void output(const string &msg) { + lines.push_back(msg); +} + class UpdateListener : public efsw::FileWatchListener { public: bool changes = false; @@ -49,11 +71,9 @@ class UpdateListener : public efsw::FileWatchListener { dbc::check(rc == 0, "git ignored failed."); if(!ignored) { - println("\nCHANGE: filename={} .gitignored?={}", full_path.c_str(), ignored); + output(format("\nCHANGE: filename={} .gitignored?={}", full_path.c_str(), ignored)); changes = changes || !ignored; - } else { - print("."); } } @@ -95,13 +115,13 @@ void run_build(GameEngine &game, const char* command) { lnumber, col, type, message); stats_out << result; - print(ERROR, "\nHIT WITH {} @ {}:{}:{} {}", type, file_name, lnumber, col, message); + output(format("\nHIT WITH {} @ {}:{}:{} {}", type, file_name, lnumber, col, message)); game.hit(type); // refactor this if(game.is_dead()) { - print(ERROR, "YOU DIED!\n"); + output(format("YOU DIED!\n")); } } } @@ -110,6 +130,89 @@ void run_build(GameEngine &game, const char* command) { dbc::post("a post test", [&]() { return !stats_out.is_open(); }); } + +ButtonOption Style() { + auto option = ButtonOption::Animated(); + option.transform = [](const EntryState& s) { + auto element = paragraph(s.label); + if (s.focused) { + element |= bold; + } + return element | center | borderEmpty | flex; + }; + return option; +} + +int main_loop(UpdateListener* listener, efsw::FileWatcher* fileWatcher, GameEngine &game, const char *build_cmd) { + auto screen = ScreenInteractive::Fullscreen(); + screen.TrackMouse(true); + + // Create a component counting the number of frames drawn and event handled. + float scroll_x = 0.1; + float scroll_y = 1.0; + + auto status = Renderer([&] { + return vbox({ + paragraph(fmt::format("HP {}", game.hit_points)), + separator(), + hbox({ + text("HP"), + gauge(game.hit_points / 100.0f), + }), + }); + }); + + auto content = Renderer([&] { + vector output; + + for(const auto line : lines) { + output.push_back(text(line)); + } + + return vbox(output); + }); + + auto scrollable_content = Renderer(content, + [&, content] { + return content->Render() + | focusPositionRelative(scroll_x, scroll_y) + | frame | flex; + }); + + auto component = Renderer(scrollable_content, [&] { + return vbox({ + status->Render(), + separator(), + scrollable_content->Render() | vscroll_indicator | yframe | size(HEIGHT, LESS_THAN, 20), + }) | + border; + }); + + component |= CatchEvent([&](Event) -> bool { + return false; + }); + + Loop loop(&screen, component); + + while (!loop.HasQuitted()) { + fileWatcher->watch(); + + if(listener->changes) { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + output(format("CHANGES! Running build {}", build_cmd)); + run_build(game, build_cmd); + listener->reset_state(); + } + + loop.RunOnce(); + screen.Post(Event::Custom); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + return EXIT_SUCCESS; +} + + int main(int argc, char *argv[]) { git_repository* repo = nullptr; @@ -119,7 +222,7 @@ int main(int argc, char *argv[]) const char *git_path = argv[1]; const char *build_cmd = argv[2]; - println("Using build command: {}", build_cmd); + output(format("Using build command: {}", build_cmd)); efsw::FileWatcher* fileWatcher = new efsw::FileWatcher(); dbc::check(fileWatcher != nullptr, "Failed to create filewatcher."); @@ -131,27 +234,17 @@ int main(int argc, char *argv[]) UpdateListener* listener = new UpdateListener(repo); dbc::check(listener != nullptr, "Failed to create listener."); - print("Watching directory {} for changes...\n", git_path); + output(format("Watching directory {} for changes...\n", git_path)); efsw::WatchID wid = fileWatcher->addWatch(git_path, listener, true); GameEngine game{100}; - while(true) { - print("WAITING...\r"); - fileWatcher->watch(); - - if(listener->changes) { - sleep(1); - println("CHANGES! Running build {}", build_cmd); - run_build(game, build_cmd); - listener->reset_state(); - } - sleep(1); - } + int rc = main_loop(listener, fileWatcher, game, build_cmd); + dbc::check(rc == 0, "Invalid return from main_loop."); git_libgit2_shutdown(); } catch(dbc::Error &err) { - print("ERROR: {}\n", err.message); + output(format("ERROR: {}\n", err.message)); if(repo != nullptr) git_repository_free(repo); git_libgit2_shutdown(); return 1; diff --git a/ftxtest.cpp b/ftxtest.cpp index 4a41ed5..ffd4e68 100644 --- a/ftxtest.cpp +++ b/ftxtest.cpp @@ -37,33 +37,13 @@ int main() { vector lines; // Create a component counting the number of frames drawn and event handled. - int custom_loop_count = 0; - int frame_count = 0; - int event_count = 0; float scroll_x = 0.1; float scroll_y = 1.0; int hp = 100; - int row = 0; - auto hit_button = Button("Hit", [&] { - hp -= 1; - lines.push_back(fmt::format("You were hit! HP now {}", hp)); - }, Style()); - auto hard_button = Button("Hard", [&] { - hp -= 10; - lines.push_back(fmt::format("You were hit HARD! HP now {}", hp)); - }, Style()); - auto heal_button = Button("Heal", [&] { - hp += 10; - lines.push_back(fmt::format("You healed! HP now {}", hp)); - }, Style()); - - auto buttons = Container::Horizontal({ - hit_button, hard_button, heal_button}, &row); auto status = Renderer([&] { return vbox({ - paragraph(fmt::format("HP {} frames {} events {} custom {}", - hp, frame_count, event_count, custom_loop_count)), + paragraph(fmt::format("HP {}", hp)), separator(), hbox({ text("HP"), @@ -89,27 +69,22 @@ int main() { | frame | flex; }); - auto component = Renderer(buttons, [&] { - frame_count++; - + auto component = Renderer(scrollable_content, [&] { return vbox({ status->Render(), separator(), - buttons->Render(), scrollable_content->Render() | vscroll_indicator | yframe | size(HEIGHT, LESS_THAN, 20), }) | border; }); component |= CatchEvent([&](Event) -> bool { - event_count++; return false; }); Loop loop(&screen, component); while (!loop.HasQuitted()) { - custom_loop_count++; loop.RunOnce(); screen.Post(Event::Custom); std::this_thread::sleep_for(std::chrono::milliseconds(10)); diff --git a/game_engine.hpp b/game_engine.hpp index 7224199..abbf3d4 100644 --- a/game_engine.hpp +++ b/game_engine.hpp @@ -26,7 +26,6 @@ class Brainfucker { }; class GameEngine { - int hit_points = 0; map damage_types{ {"error", 4}, {"warning", 1}, @@ -34,6 +33,8 @@ class GameEngine { }; public: + int hit_points = 0; + GameEngine(int hp); int determine_damage(string &type);