From da4b82a7b7022d780a31e2157717e374d2878db2 Mon Sep 17 00:00:00 2001 From: Calc1te Date: Sun, 15 Feb 2026 22:34:47 +0800 Subject: [PATCH 1/3] Feat: a handmade base64 parser --- CMakeLists.txt | 10 +++++++-- src/Game.cpp | 58 +++++++++++++++++++++++------------------------- src/Settings.cpp | 1 + src/base64.cpp | 50 +++++++++++++++++++++++++++++++++++++++++ src/base64.h | 22 ++++++++++++++++++ src/debug.cpp | 16 ++++++------- src/i18n.cpp | 5 +++++ src/i18n.h | 16 +++++++++++++ 8 files changed, 137 insertions(+), 41 deletions(-) create mode 100644 src/base64.cpp create mode 100644 src/base64.h create mode 100644 src/i18n.cpp create mode 100644 src/i18n.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c8cf222..15e0e85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,9 @@ add_executable(idleGame src/main.cpp src/InputMonitor.cpp include/InputMonitor.h src/Settings.cpp - include/Settings.h) + include/Settings.h + src/i18n.cpp + src/i18n.h) if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_executable(debug src/debug.cpp src/Game.cpp @@ -27,5 +29,9 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") include/InputMonitor.h src/Settings.cpp include/Settings.h - include/displayUtils.h) + include/displayUtils.h + src/i18n.cpp + src/i18n.h + src/base64.cpp + src/base64.h) endif() diff --git a/src/Game.cpp b/src/Game.cpp index 38de1a7..7b0a48a 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -39,52 +39,50 @@ Game::~Game() { bIsRunning.store(false); } void Game::initWindow() { - int LINE_WIDTH = 0; + constexpr int DEFAULT_HEIGHT = 25; + constexpr int DEFAULT_WIDTH = 80; + + int lineWidth = 0; #ifdef _WIN64 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csbi; - if (GetConsoleScreenBufferInfo(hConsole, &csbi)) { - LINE_HEIGHT = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; - LINE_WIDTH = csbi.srWindow.Right - csbi.srWindow.Left + 1; - } else { - LINE_HEIGHT = 25; - LINE_WIDTH = 80; - } -#else - struct winsize win; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0) { - LINE_HEIGHT = win.ws_row; - LINE_WIDTH = win.ws_col; - } else { - LINE_HEIGHT = 25; - LINE_WIDTH = 80; - } -#endif - - while (LINE_HEIGHT < 18 || LINE_WIDTH < 40) { - std::cout << MAGENTA << "this terminal window seems too small" << nl; - std::cout << MAGENTA << "current window size is " - << LINE_WIDTH << "*" << LINE_HEIGHT << nl; - std::cout << MAGENTA << "try resize this window to continue?" << nl << nl; - - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - -#ifdef _WIN64 + auto getSize = [&]() { if (GetConsoleScreenBufferInfo(hConsole, &csbi)) { LINE_HEIGHT = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; - LINE_WIDTH = csbi.srWindow.Right - csbi.srWindow.Left + 1; + lineWidth = csbi.srWindow.Right - csbi.srWindow.Left + 1; + } else { + LINE_HEIGHT = DEFAULT_HEIGHT; + lineWidth = DEFAULT_WIDTH; } + }; #else + struct winsize win; + auto getSize = [&]() { if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0) { LINE_HEIGHT = win.ws_row; - LINE_WIDTH = win.ws_col; + lineWidth = win.ws_col; + } else { + LINE_HEIGHT = DEFAULT_HEIGHT; + lineWidth = DEFAULT_WIDTH; } + }; #endif + + getSize(); + + while (LINE_HEIGHT < 18 || lineWidth < 40) { + std::cout << MAGENTA << "This terminal window seems too small\n" + << "Current size: " << lineWidth << "x" << LINE_HEIGHT << "\n" + << "Try resize this window to continue.\n\n"; + + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + getSize(); clear_screen(); } } + void Game::gameRun() { if (!bIsDisplayOnHalt.load()) { clear_screen(); diff --git a/src/Settings.cpp b/src/Settings.cpp index bfc00dc..b435367 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -62,6 +62,7 @@ void Settings::setUsername() { std::string userName; std::cout<<"what's your name?"<< nl; std::getline(std::cin, userName); + // remember to reactivate input monitor game->monitor->resume(); game->setHalt(false); thisSettingPage = menu; diff --git a/src/base64.cpp b/src/base64.cpp new file mode 100644 index 0000000..059be7d --- /dev/null +++ b/src/base64.cpp @@ -0,0 +1,50 @@ +// +// Created by Calcite on 26-2-15. +// + +#include "base64.h" +#include + +char base64::base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + +std::string base64::encode(const std::string& input) { + int ch_ptr = 0; + std::string output; + while (ch_ptr < input.size()) { + const char c1 = input[ch_ptr]; + const char c2 = (ch_ptr + 1 < input.size()) ? input[ch_ptr + 1] : 0; + const char c3 = (ch_ptr + 2 < input.size()) ? input[ch_ptr + 2] : 0; + int val = (c1 << 16) + (c2 << 8) + c3; + // int is 32 bit + + const char o1 = base64_chars[(val & 0xFC0000) >> 18]; + const char o2 = base64_chars[(val & 0x3F000) >> 12]; + const char o3 = base64_chars[(val & 0xFC0) >> 6]; + const char o4 = base64_chars[val & 0x3F]; + output += o1; + output += o2; + output += (ch_ptr + 1 < input.size()) ? o3 : '='; + output += (ch_ptr + 2 < input.size()) ? o4 : '='; + ch_ptr += 3; + } + return output; +} + +std::string base64::decode(const std::string& input) { + std::string output; + std::vector T(256, -1); + for (int i = 0; i < 64; i++) T[base64_chars[i]] = i; + + int val = 0, valb = -8; + for (unsigned char c : input) { + if (T[c] == -1) break; + val = (val << 6) + T[c]; + valb += 6; + if (valb >= 0) { + output.push_back(char((val >> valb) & 0xFF)); + valb -= 8; + } + } + return output; +} \ No newline at end of file diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..3d28b40 --- /dev/null +++ b/src/base64.h @@ -0,0 +1,22 @@ +// +// Created by Calcite on 26-2-15. +// + +#ifndef BASE64_H +#define BASE64_H +#include + + +class base64 { + public: + + static char base64_chars[]; + + static std::string encode(const std::string& input); + + static std::string decode(const std::string& input); +}; + + + +#endif //BASE64_H diff --git a/src/debug.cpp b/src/debug.cpp index a36589a..bc154e7 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -2,16 +2,14 @@ #include "Game.h" #include "Buyables.h" +#include "base64.h" int main() { - int level = 0; - building *build = new building(std::vector{10}, std::vector{1}); - while (level<20) { - *build = building::next_buyable(*build); - std::cout << build->vBoost[0]<vPrice[0]< Date: Sun, 22 Feb 2026 13:40:20 +0800 Subject: [PATCH 2/3] Feat: save/load --- include/Game.h | 1 + include/Settings.h | 8 ++ {src => include}/base64.h | 0 {src => include}/i18n.h | 0 src/Game.cpp | 5 +- src/Settings.cpp | 168 +++++++++++++++++++++++++++++++++++++- src/base64.cpp | 37 +++++---- src/debug.cpp | 2 +- src/i18n.cpp | 2 +- 9 files changed, 199 insertions(+), 24 deletions(-) rename {src => include}/base64.h (100%) rename {src => include}/i18n.h (100%) diff --git a/include/Game.h b/include/Game.h index f239a1e..c7265d3 100644 --- a/include/Game.h +++ b/include/Game.h @@ -58,6 +58,7 @@ class Game { void displayMenu(); void displayUpgrade(); void displayShop(); + std::string getuserName() const; static void displayMainMenu(); void handleKey(int ch); diff --git a/include/Settings.h b/include/Settings.h index 087fd4b..9d08774 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -6,6 +6,9 @@ #define SETTINGS_H #include "InputMonitor.h" #include +#include <__filesystem/filesystem_error.h> + +#include "Game.h" class Game; class Settings { @@ -24,6 +27,10 @@ class Settings { void displaySettingsExportSave(); // $username.ids + void exportSaveFile(std::filesystem::path path); + + void importSaveFile(std::filesystem::path path); + void displaySettingsLoadSave(); void setUsername(); @@ -34,6 +41,7 @@ class Settings { std::vector theFunnyNumber; int uLevel; int bLevel; + std::string userName; }; }; diff --git a/src/base64.h b/include/base64.h similarity index 100% rename from src/base64.h rename to include/base64.h diff --git a/src/i18n.h b/include/i18n.h similarity index 100% rename from src/i18n.h rename to include/i18n.h diff --git a/src/Game.cpp b/src/Game.cpp index 7b0a48a..e26af3f 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -58,7 +58,7 @@ void Game::initWindow() { }; #else struct winsize win; - auto getSize = [&]() { + auto getSize = [&] { if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0) { LINE_HEIGHT = win.ws_row; lineWidth = win.ws_col; @@ -330,6 +330,9 @@ void Game::buyUpgrade(int idx) { statMessage = "success!"; } +std::string Game::getuserName() const{ + return username; +} void Game::buyBuilding(int idx) { if (idx < 0 || idx >= buildings.size()) return; diff --git a/src/Settings.cpp b/src/Settings.cpp index b435367..30d0193 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -2,6 +2,19 @@ #include "Game.h" #include "DisplayUtils.h" #include +#include +#include +#include + +#include "base64.h" + +#ifdef _WIN64 +#include +#else +#include +#include +#include +#endif Settings::Settings(Game* g) { thisSettingPage = menu; @@ -41,16 +54,163 @@ void Settings::displaySettingsMainMenu() { std::cout << YELLOW<< "[u] set user name" << RESET_COLOR << nl; std::cout << YELLOW<< "[e] export save" << RESET_COLOR << nl; std::cout << YELLOW<< "[l] load save" << RESET_COLOR << nl; - - std::cout << nl; } void Settings::displaySettingsExportSave() { + game->setHalt(true); + game->monitor->pause(); + std::cout << "\033[H\033[J"; + std::cout << title << nl; + std::cout << YELLOW << "Where do you want to save your file?" << RESET_COLOR << nl; + std::cout << YELLOW << "directory + name" << RESET_COLOR << nl; + std::string filePath; + std::filesystem::path cwd = std::filesystem::current_path(); + std::string defaultPath = cwd.string(); + if (!defaultPath.empty() && defaultPath.back() != std::filesystem::path::preferred_separator) { + defaultPath += std::filesystem::path::preferred_separator; + } + +#ifdef _WIN64 + HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); + for (char c : defaultPath) { + INPUT inputs[2] = {}; + + inputs[0].EventType = KEY_EVENT; + inputs[0].Event.KeyEvent.bKeyDown = TRUE; + inputs[0].Event.KeyEvent.uChar.AsciiChar = c; + inputs[0].Event.KeyEvent.wRepeatCount = 1; + + inputs[1].EventType = KEY_EVENT; + inputs[1].Event.KeyEvent.bKeyDown = FALSE; + inputs[1].Event.KeyEvent.uChar.AsciiChar = c; + inputs[1].Event.KeyEvent.wRepeatCount = 1; + + DWORD written; + WriteConsoleInputA(hInput, inputs, 2, &written); + } +#else + for (char c : defaultPath) { + ioctl(STDIN_FILENO, TIOCSTI, &c); + } +#endif + std::getline(std::cin, filePath); + + if (filePath.empty()) { + filePath = defaultPath + game->getuserName(); // Fallback + } + + exportSaveFile(filePath); + + game->monitor->resume(); + game->setHalt(false); + thisSettingPage = menu; } + +void Settings::exportSaveFile(std::filesystem::path path) { + saveData data = { + .theFunnyNumber = game->theFunnyNumber, + .uLevel = upgrade::level, + .bLevel = building::level, + .userName = game->getuserName() + }; + std::string o; + for (int num : data.theFunnyNumber) { + o += std::to_string(num) + "-"; + } + o += std::to_string('_'); + o += std::to_string(data.uLevel) + "_"; + o += std::to_string(data.bLevel) + "_"; + o += data.userName; + std::string bo = base64::encode(o); + try { + std::ofstream outFile(path); + outFile << bo; + outFile.close(); + } catch (const std::filesystem::filesystem_error& e) { + game->statMessage = "save failed! " + std::string(e.what()); + } + +} + void Settings::displaySettingsLoadSave() { + game->setHalt(true); + game->monitor->pause(); + std::cout << "\033[H\033[J"; + std::cout << title << nl; + std::cout << YELLOW << "Where is the file?" << RESET_COLOR << nl; + std::string filePath; + std::filesystem::path cwd = std::filesystem::current_path(); + std::string defaultPath = cwd.string(); + if (!defaultPath.empty() && defaultPath.back() != std::filesystem::path::preferred_separator) { + defaultPath += std::filesystem::path::preferred_separator; + } + +#ifdef _WIN64 + HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); + for (char c : defaultPath) { + INPUT inputs[2] = {}; + + inputs[0].EventType = KEY_EVENT; + inputs[0].Event.KeyEvent.bKeyDown = TRUE; + inputs[0].Event.KeyEvent.uChar.AsciiChar = c; + inputs[0].Event.KeyEvent.wRepeatCount = 1; + + inputs[1].EventType = KEY_EVENT; + inputs[1].Event.KeyEvent.bKeyDown = FALSE; + inputs[1].Event.KeyEvent.uChar.AsciiChar = c; + inputs[1].Event.KeyEvent.wRepeatCount = 1; + + DWORD written; + WriteConsoleInputA(hInput, inputs, 2, &written); + } +#else + for (char c : defaultPath) { + ioctl(STDIN_FILENO, TIOCSTI, &c); + } +#endif + std::getline(std::cin, filePath); + if (filePath.empty()) { + filePath = defaultPath + game->getuserName(); // Fallback + } + + importSaveFile(filePath); + game->monitor->resume(); + game->setHalt(false); + thisSettingPage = menu; +} + +void Settings::importSaveFile(std::filesystem::path path) { + try { + std::ifstream inFile(path); + std::string encodedData((std::istreambuf_iterator(inFile)), std::istreambuf_iterator()); + inFile.close(); + + std::string decodedData = base64::decode(encodedData); + size_t pos = 0; + std::vector theFunnyNumber; + while ((pos = decodedData.find('-')) != std::string::npos) { + theFunnyNumber.push_back(std::stoi(decodedData.substr(0, pos))); + decodedData.erase(0, pos + 1); + } + pos = decodedData.find('_'); + int uLevel = std::stoi(decodedData.substr(0, pos)); + decodedData.erase(0, pos + 1); + pos = decodedData.find('_'); + int bLevel = std::stoi(decodedData.substr(0, pos)); + decodedData.erase(0, pos + 1); + std::string userName = decodedData; + + game->theFunnyNumber = theFunnyNumber; + upgrade::level = uLevel; + building::level = bLevel; + game->username = userName; + game->statMessage = "load successful!"; + } catch (const std::filesystem::filesystem_error& e) { + game->statMessage = "load failed! " + std::string(e.what()); + } } void Settings::setUsername() { @@ -71,4 +231,6 @@ void Settings::setUsername() { } -void Settings::confirm(int idx){} +void Settings::confirm(int idx) { + +} diff --git a/src/base64.cpp b/src/base64.cpp index 059be7d..84f1a84 100644 --- a/src/base64.cpp +++ b/src/base64.cpp @@ -2,7 +2,7 @@ // Created by Calcite on 26-2-15. // -#include "base64.h" +#include "../include/base64.h" #include char base64::base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -11,29 +11,30 @@ char base64::base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw std::string base64::encode(const std::string& input) { int ch_ptr = 0; std::string output; - while (ch_ptr < input.size()) { - const char c1 = input[ch_ptr]; - const char c2 = (ch_ptr + 1 < input.size()) ? input[ch_ptr + 1] : 0; - const char c3 = (ch_ptr + 2 < input.size()) ? input[ch_ptr + 2] : 0; - int val = (c1 << 16) + (c2 << 8) + c3; - // int is 32 bit + std::vector T(128, -1); + for (int i = 0; i < 64; i++) T[base64_chars[i]] = i; + int val = 0, valb = -6; + for (unsigned char c : input) { + val = (val << 8) + c; + valb += 8; + while (valb >= 0) { + output.push_back(base64_chars[val >> valb] & 0x3F); // Rshift 2 + valb -= 6; + } + } - const char o1 = base64_chars[(val & 0xFC0000) >> 18]; - const char o2 = base64_chars[(val & 0x3F000) >> 12]; - const char o3 = base64_chars[(val & 0xFC0) >> 6]; - const char o4 = base64_chars[val & 0x3F]; - output += o1; - output += o2; - output += (ch_ptr + 1 < input.size()) ? o3 : '='; - output += (ch_ptr + 2 < input.size()) ? o4 : '='; - ch_ptr += 3; + if (valb > -6) { + output.push_back(base64_chars[val >> valb] & 0x3F); + } + while (output.size() % 4) { + output.push_back('='); } return output; } std::string base64::decode(const std::string& input) { std::string output; - std::vector T(256, -1); + std::vector T(128, -1); for (int i = 0; i < 64; i++) T[base64_chars[i]] = i; int val = 0, valb = -8; @@ -42,7 +43,7 @@ std::string base64::decode(const std::string& input) { val = (val << 6) + T[c]; valb += 6; if (valb >= 0) { - output.push_back(char((val >> valb) & 0xFF)); + output.push_back(static_cast((val >> valb) & 0xFF)); valb -= 8; } } diff --git a/src/debug.cpp b/src/debug.cpp index bc154e7..1223f80 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -2,7 +2,7 @@ #include "Game.h" #include "Buyables.h" -#include "base64.h" +#include "../include/base64.h" int main() { std::string input = "test"; diff --git a/src/i18n.cpp b/src/i18n.cpp index e666f65..16db83d 100644 --- a/src/i18n.cpp +++ b/src/i18n.cpp @@ -2,4 +2,4 @@ // Created by Calcite on 25/09/20. // -#include "i18n.h" +#include "../include/i18n.h" From 198b85f0ccc4ea74887228294e8ed6e8314b7673 Mon Sep 17 00:00:00 2001 From: Calc1te Date: Mon, 23 Feb 2026 01:57:23 +0800 Subject: [PATCH 3/3] Feat: fix save/load --- CMakeLists.txt | 20 ++---------- include/Game.h | 7 ++-- include/InputMonitor.h | 8 ++++- src/Game.cpp | 21 ++++++++++-- src/InputMonitor.cpp | 61 ++++++++++++++++++++++++++++++++-- src/Settings.cpp | 74 ++++++++++-------------------------------- src/base64.cpp | 4 +-- 7 files changed, 112 insertions(+), 83 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 15e0e85..3e453ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,20 +18,6 @@ add_executable(idleGame src/main.cpp src/Settings.cpp include/Settings.h src/i18n.cpp - src/i18n.h) -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_executable(debug src/debug.cpp - src/Game.cpp - include/Game.h - src/Buyables.cpp - include/Buyables.h - src/InputMonitor.cpp - include/InputMonitor.h - src/Settings.cpp - include/Settings.h - include/displayUtils.h - src/i18n.cpp - src/i18n.h - src/base64.cpp - src/base64.h) -endif() + include/i18n.h + src/base64.cpp + include/base64.h) diff --git a/include/Game.h b/include/Game.h index c7265d3..5458f72 100644 --- a/include/Game.h +++ b/include/Game.h @@ -24,14 +24,16 @@ class Game { public: std::string username; std::string statMessage; + std::string fakeInputBuffer; std::atomic bIsRunning; std::atomic bInputMode; std::atomic bIsDisplayOnHalt; - const int FRAMERATE = 40; - const int AUTO_INCREMENT_RATE = 40; + const int FRAMERATE = 60; + const int AUTO_INCREMENT_RATE = 60; const int CLICK_COOLDOWN = 10; int iTimeCounter = 0; bool bSpammingFlag = false; + bool bPathInputFlag = false; int iClickIncrement; int iAutoIncrement; int iOptionIdx; @@ -58,6 +60,7 @@ class Game { void displayMenu(); void displayUpgrade(); void displayShop(); + bool updatePathInput(char ch); std::string getuserName() const; static void displayMainMenu(); diff --git a/include/InputMonitor.h b/include/InputMonitor.h index 3a6433b..e940f77 100644 --- a/include/InputMonitor.h +++ b/include/InputMonitor.h @@ -3,6 +3,7 @@ #include #include #include +#include #ifdef __linux__ #include #include @@ -33,6 +34,9 @@ class InputMonitor { void stop(); void pause(); void resume(); + void prePathInput(); + void postPathInput(); + std::string readLine(const std::string& prompt, const std::string& defaultInput = ""); private: std::atomic bRunning; @@ -40,7 +44,9 @@ class InputMonitor { void initInput(); void resetInput(); int pollInput(); -#ifndef _WIN32 + char pollPath(); +#ifdef _WIN32 +#else struct termios oldt; int oldf; #endif diff --git a/src/Game.cpp b/src/Game.cpp index e26af3f..b133815 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -26,6 +26,7 @@ Game::Game() { buyConfirm = nullptr; settingConfirm = nullptr; monitor->start([this](int ch){this->handleKey(ch);}); + username = ""; } Game::~Game() { theFunnyNumber.clear(); @@ -193,10 +194,12 @@ void Game::displayShop(){ std::cout << "Press 'y' to buy, 'm' to return to main menu" << nl; } - - void Game::handleKey(int ch) { if (bInputMode.load()){return;} + if (bPathInputFlag) { + updatePathInput(ch); + return; + } std::lock_guard lk(stateMutex); switch (ch) { case 'a': @@ -311,6 +314,20 @@ void Game::click() { } } +bool Game::updatePathInput(char ch) { + if (ch == 13) { + bPathInputFlag = false; + return true; + } + if (ch == 8) { + ch = '/b'; + if (!fakeInputBuffer.empty()) { + fakeInputBuffer.pop_back(); + } + } + fakeInputBuffer.push_back(ch); + return false; +} void Game::buyUpgrade(int idx) { if (idx < 0 || idx >= upgrades.size()) return; diff --git a/src/InputMonitor.cpp b/src/InputMonitor.cpp index c7dee58..fc2388a 100644 --- a/src/InputMonitor.cpp +++ b/src/InputMonitor.cpp @@ -1,5 +1,6 @@ #include "InputMonitor.h" +#include #include #ifdef __linux__ #include @@ -34,7 +35,7 @@ void InputMonitor::start(const Callback& callback) if (ch!=-1) { callback(ch); } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } resetInput(); }); @@ -60,14 +61,38 @@ int InputMonitor::pollInput() { } void InputMonitor::pause() { paused.store(true); resetInput(); } void InputMonitor::resume() { initInput(); paused.store(false); } +std::string InputMonitor::readLine(const std::string& prompt, const std::string& defaultInput) { + std::string buffer = defaultInput; + std::cout << prompt << buffer << std::flush; + + while (true) { + int ch = _getch(); + + if (ch == '\r') { // Enter + std::cout << std::endl; + break; + } else if (ch == 8) { // Backspace (ASCII 8) + if (!buffer.empty()) { + buffer.pop_back(); + std::cout << "\b \b" << std::flush; + } + } else if (ch >= 32 && ch < 127) { // Printable characters + buffer += static_cast(ch); + std::cout << static_cast(ch) << std::flush; + } + } + std::cout << std::endl; // Newline on exit + return buffer; +} #else void InputMonitor::initInput() { - struct termios newt; + struct termios newt = {}; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); + // set non-blocking oldf = fcntl(STDIN_FILENO, F_GETFL, 0); fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); } @@ -82,7 +107,39 @@ int InputMonitor::pollInput() { if (ch != EOF) return ch; return -1; } + void InputMonitor::pause() { paused.store(true); resetInput(); } void InputMonitor::resume() { initInput(); paused.store(false); } +std::string InputMonitor::readLine(const std::string& prompt, const std::string& defaultInput) { + struct termios oldt = {}, newt = {}; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + + std::string buffer = defaultInput; + std::cout << prompt << buffer << std::flush; + while (true) { + char ch; + if (read(STDIN_FILENO, &ch, 1) > 0) { + if (ch == '\n') { // Enter + std::cout << std::endl; + break; + } else if (ch == 127 || ch == '\b') { // Backspace + if (!buffer.empty()) { + buffer.pop_back(); + // Basic ANSI code: Move cursor back, overwrite with space, move back again + std::cout << "\b \b" << std::flush; + } + } else if (static_cast(ch) >= 32 && ch != 127) { + buffer += ch; + std::cout << ch << std::flush; + } + } + } + + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + return buffer; +} #endif \ No newline at end of file diff --git a/src/Settings.cpp b/src/Settings.cpp index 30d0193..15deca2 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -63,42 +63,19 @@ void Settings::displaySettingsExportSave() { std::cout << "\033[H\033[J"; std::cout << title << nl; std::cout << YELLOW << "Where do you want to save your file?" << RESET_COLOR << nl; - std::cout << YELLOW << "directory + name" << RESET_COLOR << nl; - std::string filePath; + std::filesystem::path cwd = std::filesystem::current_path(); std::string defaultPath = cwd.string(); if (!defaultPath.empty() && defaultPath.back() != std::filesystem::path::preferred_separator) { defaultPath += std::filesystem::path::preferred_separator; } + std::string fullDefault = defaultPath + game->getuserName(); -#ifdef _WIN64 - HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); - for (char c : defaultPath) { - INPUT inputs[2] = {}; - - inputs[0].EventType = KEY_EVENT; - inputs[0].Event.KeyEvent.bKeyDown = TRUE; - inputs[0].Event.KeyEvent.uChar.AsciiChar = c; - inputs[0].Event.KeyEvent.wRepeatCount = 1; - - inputs[1].EventType = KEY_EVENT; - inputs[1].Event.KeyEvent.bKeyDown = FALSE; - inputs[1].Event.KeyEvent.uChar.AsciiChar = c; - inputs[1].Event.KeyEvent.wRepeatCount = 1; - - DWORD written; - WriteConsoleInputA(hInput, inputs, 2, &written); - } -#else - for (char c : defaultPath) { - ioctl(STDIN_FILENO, TIOCSTI, &c); - } -#endif - - std::getline(std::cin, filePath); + // Use the new readLine function which handles input buffer display and editing + std::string filePath = game->monitor->readLine("Path: ", fullDefault); - if (filePath.empty()) { - filePath = defaultPath + game->getuserName(); // Fallback + if (filePath.length() < 4 || filePath.substr(filePath.length() - 4) != ".ids") { + filePath += ".ids"; } exportSaveFile(filePath); @@ -119,16 +96,21 @@ void Settings::exportSaveFile(std::filesystem::path path) { for (int num : data.theFunnyNumber) { o += std::to_string(num) + "-"; } - o += std::to_string('_'); + o += "_"; o += std::to_string(data.uLevel) + "_"; o += std::to_string(data.bLevel) + "_"; o += data.userName; std::string bo = base64::encode(o); try { std::ofstream outFile(path); + if (!outFile.is_open()) { + game->statMessage = "save failed! Could not open file: " + path.string(); + return; + } outFile << bo; outFile.close(); - } catch (const std::filesystem::filesystem_error& e) { + game->statMessage = "save file to " + path.string() + "!"; + } catch (const std::exception& e) { game->statMessage = "save failed! " + std::string(e.what()); } @@ -140,40 +122,18 @@ void Settings::displaySettingsLoadSave() { std::cout << "\033[H\033[J"; std::cout << title << nl; std::cout << YELLOW << "Where is the file?" << RESET_COLOR << nl; - std::string filePath; + std::filesystem::path cwd = std::filesystem::current_path(); std::string defaultPath = cwd.string(); if (!defaultPath.empty() && defaultPath.back() != std::filesystem::path::preferred_separator) { defaultPath += std::filesystem::path::preferred_separator; } + std::string fullDefault = defaultPath + game->getuserName(); -#ifdef _WIN64 - HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); - for (char c : defaultPath) { - INPUT inputs[2] = {}; - - inputs[0].EventType = KEY_EVENT; - inputs[0].Event.KeyEvent.bKeyDown = TRUE; - inputs[0].Event.KeyEvent.uChar.AsciiChar = c; - inputs[0].Event.KeyEvent.wRepeatCount = 1; - - inputs[1].EventType = KEY_EVENT; - inputs[1].Event.KeyEvent.bKeyDown = FALSE; - inputs[1].Event.KeyEvent.uChar.AsciiChar = c; - inputs[1].Event.KeyEvent.wRepeatCount = 1; - - DWORD written; - WriteConsoleInputA(hInput, inputs, 2, &written); - } -#else - for (char c : defaultPath) { - ioctl(STDIN_FILENO, TIOCSTI, &c); - } -#endif + std::string filePath = game->monitor->readLine("Path: ", fullDefault); - std::getline(std::cin, filePath); if (filePath.empty()) { - filePath = defaultPath + game->getuserName(); // Fallback + filePath = fullDefault; } importSaveFile(filePath); diff --git a/src/base64.cpp b/src/base64.cpp index 84f1a84..af7727b 100644 --- a/src/base64.cpp +++ b/src/base64.cpp @@ -12,13 +12,13 @@ std::string base64::encode(const std::string& input) { int ch_ptr = 0; std::string output; std::vector T(128, -1); - for (int i = 0; i < 64; i++) T[base64_chars[i]] = i; + for (int i = 0; i < 64; i++) T[i] = base64_chars[i]; int val = 0, valb = -6; for (unsigned char c : input) { val = (val << 8) + c; valb += 8; while (valb >= 0) { - output.push_back(base64_chars[val >> valb] & 0x3F); // Rshift 2 + output.push_back(base64_chars[val >> valb & 0x3F]); // Rshift 2 valb -= 6; } }