diff --git a/CMakeLists.txt b/CMakeLists.txt index c8cf222..3e453ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,16 +16,8 @@ add_executable(idleGame src/main.cpp src/InputMonitor.cpp include/InputMonitor.h src/Settings.cpp - include/Settings.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) -endif() + include/Settings.h + src/i18n.cpp + include/i18n.h + src/base64.cpp + include/base64.h) diff --git a/include/Game.h b/include/Game.h index f239a1e..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,8 @@ class Game { void displayMenu(); void displayUpgrade(); void displayShop(); + bool updatePathInput(char ch); + std::string getuserName() const; static void displayMainMenu(); void handleKey(int ch); 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/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/include/base64.h b/include/base64.h new file mode 100644 index 0000000..3d28b40 --- /dev/null +++ b/include/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/include/i18n.h b/include/i18n.h new file mode 100644 index 0000000..47e7d90 --- /dev/null +++ b/include/i18n.h @@ -0,0 +1,16 @@ +// +// Created by Calcite on 25/09/20. +// + +#ifndef I18N_H +#define I18N_H + + + +class i18n { + +}; + + + +#endif //I18N_H diff --git a/src/Game.cpp b/src/Game.cpp index 38de1a7..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(); @@ -39,52 +40,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(); @@ -195,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': @@ -313,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; @@ -332,6 +347,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/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 bfc00dc..15deca2 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,123 @@ 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::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(); + + // Use the new readLine function which handles input buffer display and editing + std::string filePath = game->monitor->readLine("Path: ", fullDefault); + + if (filePath.length() < 4 || filePath.substr(filePath.length() - 4) != ".ids") { + filePath += ".ids"; + } + + 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 += "_"; + 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(); + game->statMessage = "save file to " + path.string() + "!"; + } catch (const std::exception& 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::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(); + + std::string filePath = game->monitor->readLine("Path: ", fullDefault); + + if (filePath.empty()) { + filePath = fullDefault; + } + 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() { @@ -62,6 +182,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; @@ -70,4 +191,6 @@ void Settings::setUsername() { } -void Settings::confirm(int idx){} +void Settings::confirm(int idx) { + +} diff --git a/src/base64.cpp b/src/base64.cpp new file mode 100644 index 0000000..af7727b --- /dev/null +++ b/src/base64.cpp @@ -0,0 +1,51 @@ +// +// Created by Calcite on 26-2-15. +// + +#include "../include/base64.h" +#include + +char base64::base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + +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[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 + valb -= 6; + } + } + + 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(128, -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(static_cast((val >> valb) & 0xFF)); + valb -= 8; + } + } + return output; +} \ No newline at end of file diff --git a/src/debug.cpp b/src/debug.cpp index a36589a..1223f80 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -2,16 +2,14 @@ #include "Game.h" #include "Buyables.h" +#include "../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]<