From 73a3312b4a1d8262bfdaad1076a7256f1800e853 Mon Sep 17 00:00:00 2001 From: Calc1te Date: Wed, 17 Sep 2025 22:13:23 +0800 Subject: [PATCH 1/3] add some infrastructure --- CMakeLists.txt | 3 +- README.md | 2 +- README_EN.md | 55 ++++++++++++++++++++++ include/DisplayUtils.h | 26 +++++++++++ include/Game.h | 38 ++++++++------- include/InputMonitor.h | 5 +- include/Settings.h | 33 +++++++++++-- src/Game.cpp | 103 ++++++++++++++++++++++++----------------- src/InputMonitor.cpp | 3 ++ src/Settings.cpp | 62 +++++++++++++++++++++++-- 10 files changed, 256 insertions(+), 74 deletions(-) create mode 100644 README_EN.md create mode 100644 include/DisplayUtils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fc20d9..c8cf222 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,5 +26,6 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") src/InputMonitor.cpp include/InputMonitor.h src/Settings.cpp - include/Settings.h) + include/Settings.h + include/displayUtils.h) endif() diff --git a/README.md b/README.md index 1a70c4f..bb84d89 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ 在游戏运行时,你可以输入以下按键: | 按键 | 功能 | -| --- | -------------- | +| --- |----------------| | `a` | 点击,增加点数 | | `u` | 打开升级菜单 | | `b` | 打开建筑商店 | diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..c47b340 --- /dev/null +++ b/README_EN.md @@ -0,0 +1,55 @@ +# FunnyNumber Idle Game + +A small idle game you can run in the background while coding or working over SSH, giving you a little sense of achievement right from the command line. + +--- + +## ✨ Features + +* **Idle gameplay**: Points increase automatically over time +* **Active boost**: Press `a` to manually gain points +* **Upgrades & Buildings**: + + * **Upgrades** – make your clicks stronger + * **Buildings** – increase automatic growth rate +* **Funny big number system**: Uses an *“INT\_MAX carry-over”* mechanic — because it’s funnier this way + +--- + +## 🕹️ Controls + +While the game is running, you can press the following keys: + +| Key | Action | +| --- | ----------------------- | +| `a` | Click to gain points | +| `u` | Open upgrade menu | +| `b` | Open building shop | +| `s` | Open settings menu | +| `y` | Confirm action in menus | +| `m` | Return to main menu | + +--- + +## 📖 Game Mechanics + +* Initial click value: **1 point per click** +* Initial auto-growth: **1 point per second** +* Auto-growth increases based on the number of buildings you own +* Buying upgrades and buildings consumes points +* Each purchase generates a new, more expensive option + +--- + +## ⚙️ Settings (WIP) + +* Work in progress + +--- + +## 🚀 Possible Future Improvements + +* Save/load system +* More diverse upgrade/building trees +* Leaderboards +* Optional ASCII art UI diff --git a/include/DisplayUtils.h b/include/DisplayUtils.h new file mode 100644 index 0000000..9240e97 --- /dev/null +++ b/include/DisplayUtils.h @@ -0,0 +1,26 @@ +// +// Created by Calcite on 2025/9/15. +// +#ifndef DISPLAYUTILS_H +#define DISPLAYUTILS_H + +#pragma once +#include + +#define nl std::endl +#define RESET_COLOR "\033[0m" +#define RED "\033[31m" +#define GREEN "\033[32m" +#define YELLOW "\033[33m" +#define BLUE "\033[34m" +#define MAGENTA "\033[35m" +#define CYAN "\033[36m" +#define WHITE "\033[37m" + +const std::string title = +"\033[31m __ __ _ ____ ____ ___ ____ ____\n" +"\033[33m ( )( ( \\(_ _)( __)/ __)( __)( _ \\\n" +"\033[32m )( / / )( ) _)( (_ \\ ) _) ) /\n" +"\033[34m (__)\\_)__) (__) (____)\\___/(____)(__\\_)\n"; + +#endif //DISPLAYUTILS_H diff --git a/include/Game.h b/include/Game.h index a0254d9..95993ea 100644 --- a/include/Game.h +++ b/include/Game.h @@ -1,33 +1,34 @@ #ifndef GAME_H #define GAME_H -#define nl std::endl -#define RESET_COLOR "\033[0m" -#define RED "\033[31m" -#define GREEN "\033[32m" -#define YELLOW "\033[33m" -#define BLUE "\033[34m" -#define MAGENTA "\033[35m" -#define CYAN "\033[36m" -#define WHITE "\033[37m" + #include #include #include + +#ifdef _WIN64 +#include +#else +#include +#include +#endif + +#include "DisplayUtils.h" #include "Buyables.h" #include "InputMonitor.h" #include "Settings.h" + class Game { public: - const std::string title = - "\033[31m __ __ _ ____ ____ ___ ____ ____\n" -"\033[33m ( )( ( \\(_ _)( __)/ __)( __)( _ \\\n" -"\033[32m )( / / )( ) _)( (_ \\ ) _) ) /\n" -"\033[34m (__)\\_)__) (__) (____)\\___/(____)(__\\_)\n"; + struct winsize win; + const std::string username; std::string statMessage; std::atomic bIsRunning; + std::atomic bInputMode; + std::atomic bIsDisplayOnHalt; const int FRAMERATE = 40; const int AUTO_INCREMENT_RATE = 40; const int CLICK_COOLDOWN = 10; @@ -36,6 +37,7 @@ class Game { int iClickIncrement; int iAutoIncrement; int iOptionIdx; + int LINE_HEIGHT; bool bIsInputThreadRunning; void (Game::*buyConfirm)(int idx); void (Settings::*settingConfirm)(int idx); @@ -51,14 +53,14 @@ class Game { Game(); ~Game(); void gameRun(); - + void initWindow(); + void setHalt(bool active); void display(); void displayNumber(); void displayMenu(); - static void displaySettings(); - static void displayMainMenu(); void displayUpgrade(); void displayShop(); + static void displayMainMenu(); void handleKey(int ch); static void clear_screen(); @@ -66,7 +68,7 @@ class Game { std::vector increment(int carry); std::vector increment(const std::vector& carry); std::vector decrement(const std::vector& cost); - bool isSufficient(const std::vector& cost); + bool isSufficient(const std::vector& cost) const; void click(); void buyUpgrade(int idx); diff --git a/include/InputMonitor.h b/include/InputMonitor.h index d3fed5d..3a6433b 100644 --- a/include/InputMonitor.h +++ b/include/InputMonitor.h @@ -26,10 +26,13 @@ class InputMonitor { public: using Callback = std::function; - InputMonitor(): bRunning(false){} + InputMonitor(): paused(false), bRunning(false){} ~InputMonitor(); + std::atomic paused; void start(const Callback& callback); void stop(); + void pause(); + void resume(); private: std::atomic bRunning; diff --git a/include/Settings.h b/include/Settings.h index 2b79b32..befadb6 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -4,16 +4,39 @@ #ifndef SETTINGS_H #define SETTINGS_H - +#include "Game.h" +#include "InputMonitor.h" +#include class Settings { public: - Settings(); + int thisSettingPage; + Game* game = nullptr; + enum settingState{menu, username, exportSave, loadSave}; + Settings(Game* g); ~Settings(); - void load_settings(); - void save_settings(); - void confirm(int idx); + void loadSettings(); + void saveSettings(); + void displaySettings(); + + void displaySettingsMainMenu(); + + + void displaySettingsExportSave(); // $username.ids + + void displaySettingsLoadSave(); + + std::string setUsername(); + + void confirm(int option); + + struct saveData { + std::vector theFunnyNumber; + int uLevel; + int bLevel; + }; + }; diff --git a/src/Game.cpp b/src/Game.cpp index 4613a07..3db0edb 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -5,29 +5,15 @@ #include "Game.h" #include "Settings.h" -#ifdef __linux__ -#include -#include -#include -#endif - -#ifdef __APPLE__ -#include -#include -#include -#endif - -#ifdef _WIN64 -#include -#include -#endif - Game::Game() { + initWindow(); currentState = MAIN; monitor = new InputMonitor; - settings = new Settings; + settings = new Settings(this); statMessage = ""; + bInputMode.store(false); + bIsDisplayOnHalt.store(false); clear_screen(); upgrades.emplace_back(upgrade::initPrice, upgrade::initBoost); buildings.emplace_back(building::initPrice, building::initBoost); @@ -52,12 +38,28 @@ Game::~Game() { theFunnyNumber.clear(); bIsRunning.store(false); } +void Game::initWindow() { +#ifdef _WIN64 + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo(hConsole, &csbi); + LINE_HEIGHT = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; +#else + ioctl(STDOUT_FILENO, TIOCGWINSZ, &win); + LINE_HEIGHT = win.ws_col; +#endif + +} void Game::gameRun() { - clear_screen(); - { - std::lock_guard lk(stateMutex); - display(); + if (!bIsDisplayOnHalt.load()) { + clear_screen(); + { + std::lock_guard lk(stateMutex); + display(); + } + }else { + clear_screen(); } iTimeCounter += 1; if (iTimeCounter == AUTO_INCREMENT_RATE) { @@ -70,8 +72,10 @@ void Game::gameRun() { std::this_thread::sleep_for(std::chrono::milliseconds(1000 / FRAMERATE)); } void Game::display(){ - std::cout<displaySettings(); break; default: displayMainMenu(); @@ -126,10 +130,13 @@ void Game::displayMainMenu() { std::cout<<"[e] Export save"< 6)std::cout << GREEN <<"..."< LINE_HEIGHT && upgrades.size() - i > LINE_HEIGHT - 13) { + continue; + } std::cout << GREEN << "Upgrade " << i + 1 << ": " << upgrades[i].vBoost[0] << " boost " << " - Cost: " << upgrades[i].vPrice[0] << nl; @@ -138,10 +145,13 @@ void Game::displayUpgrade() { std::cout << "Press 'y' to buy, 'm' to return to main menu" << nl; } -void Game::displayShop() { +void Game::displayShop(){ std::cout << YELLOW << "Shop Menu" << RESET_COLOR << nl << nl; - + if (buildings.size() > 8)std::cout << GREEN <<"..."; for (int i = 0; i < buildings.size(); ++i) { + if (upgrades.size() > LINE_HEIGHT - 13 && upgrades.size() - i > LINE_HEIGHT - 13) { + continue; + } std::cout << GREEN << "Building " << i + 1 << ": " << buildings[i].vBoost[0] << " boost " << " - Cost: " << buildings[i].vPrice[0] << nl; @@ -151,24 +161,20 @@ void Game::displayShop() { std::cout << "Press 'y' to buy, 'm' to return to main menu" << nl; } -void Game::displaySettings() { - std::cout << YELLOW << "Settings Menu" << RESET_COLOR << nl << nl; - std::cout << GREEN << "1. Toggle Sound" << RESET_COLOR << nl; - std::cout << GREEN << "2. Toggle Fullscreen" << RESET_COLOR << nl; - std::cout << GREEN << "3. Change Language" << RESET_COLOR << nl; - std::cout << GREEN << "4. Back to Main Menu" << RESET_COLOR << nl; - - std::cout << nl; -} void Game::handleKey(int ch) { + if (bInputMode.load()){return;} std::lock_guard lk(stateMutex); switch (ch) { case 'a': increment(iClickIncrement); break; case 'u': + if (currentState==SETTINGS) { + settings->thisSettingPage = Settings::username; + break; + } currentState = UPGRADE; buyConfirm = &Game::buyUpgrade; break; @@ -185,6 +191,12 @@ void Game::handleKey(int ch) { break; case 'm': backToMain(); + break; + case 'l': + if (currentState == SETTINGS)settings->thisSettingPage = Settings::loadSave; + break; + case 'e': + if (currentState == SETTINGS)settings->thisSettingPage = Settings::exportSave; default: break; } } @@ -251,9 +263,9 @@ std::vector Game::decrement(const std::vector& cost) { return theFunnyNumber; } -bool Game::isSufficient(const std::vector& cost) { +bool Game::isSufficient(const std::vector& cost) const { if (theFunnyNumber.size() < cost.size()) return false; - for (int i = cost.size() - 1; i >= 0; --i) { + for (int i = static_cast(cost.size()) - 1; i >= 0; --i) { if (theFunnyNumber[i] < cost[i]) return false; if (theFunnyNumber[i] > cost[i]) return true; } @@ -271,7 +283,7 @@ void Game::click() { void Game::buyUpgrade(int idx) { if (idx < 0 || idx >= upgrades.size()) return; - upgrade& u = upgrades[idx]; + upgrade& u = upgrades[upgrades.size() - 1]; if (!isSufficient(u.vPrice)) { statMessage = "too poor"; return; @@ -290,7 +302,7 @@ void Game::buyUpgrade(int idx) { void Game::buyBuilding(int idx) { if (idx < 0 || idx >= buildings.size()) return; - building& b = buildings[idx]; + building& b = buildings[buildings.size() - 1]; if (!isSufficient(b.vPrice)) { statMessage = "too poor"; return; @@ -303,4 +315,9 @@ void Game::buyBuilding(int idx) { buildings.push_back(building::next_buyable(b)); statMessage = "success!"; +} + +void Game::setHalt(bool active) { + bInputMode.store(active); + bIsDisplayOnHalt.store(active); } \ No newline at end of file diff --git a/src/InputMonitor.cpp b/src/InputMonitor.cpp index 55e6a7c..1a31d8b 100644 --- a/src/InputMonitor.cpp +++ b/src/InputMonitor.cpp @@ -76,4 +76,7 @@ int InputMonitor::pollInput() { if (ch != EOF) return ch; return -1; } +void InputMonitor::pause() { paused.store(true); resetInput(); } +void InputMonitor::resume() { initInput(); paused.store(false); } + #endif \ No newline at end of file diff --git a/src/Settings.cpp b/src/Settings.cpp index 0958922..1b64887 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -1,16 +1,68 @@ #include "Settings.h" -Settings::Settings() { +#include + +Settings::Settings(Game* g) { + thisSettingPage = menu; + game = g; } -Settings::~Settings() { +Settings::~Settings() = default; + +void Settings::loadSettings() { } -void Settings::load_settings() { +void Settings::saveSettings() { } -void Settings::save_settings() { +void Settings::displaySettings() { + switch (thisSettingPage) { + case menu: + displaySettingsMainMenu(); + break; + case username: + setUsername(); + break; + case exportSave: + displaySettingsExportSave(); + break; + case loadSave: + displaySettingsLoadSave(); + break; + default: + thisSettingPage = menu; + displaySettingsMainMenu(); + break; + } +} +void Settings::displaySettingsMainMenu() { + std::cout << YELLOW << "Settings Menu" << RESET_COLOR << nl << nl; + 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::confirm(int idx) { + +void Settings::displaySettingsExportSave() { } +void Settings::displaySettingsLoadSave() { + +} + +std::string Settings::setUsername() { + // halt display and input monitor to let user type something + game->setHalt(true); + game->monitor->pause(); + std::cout << "\033[H\033[J"; + std::cout << title << nl; + std::string userName; + std::cout<<"what's your name?"<< nl; + std::getline(std::cin, userName); + game->monitor->resume(); + return userName; +} + +void Settings::confirm(int idx){} From 9eaddbea7c1dd14f65f79b36b2eb291aecf885c2 Mon Sep 17 00:00:00 2001 From: Calc1te Date: Thu, 18 Sep 2025 11:45:32 +0800 Subject: [PATCH 2/3] minor refactor --- include/Game.h | 5 +++-- include/Settings.h | 3 +-- src/InputMonitor.cpp | 2 ++ src/Settings.cpp | 5 ++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/include/Game.h b/include/Game.h index 95993ea..cd576b4 100644 --- a/include/Game.h +++ b/include/Game.h @@ -18,12 +18,13 @@ #include "DisplayUtils.h" #include "Buyables.h" #include "InputMonitor.h" -#include "Settings.h" - +class Settings; class Game { public: +#ifndef _WIN64 struct winsize win; +#endif const std::string username; std::string statMessage; std::atomic bIsRunning; diff --git a/include/Settings.h b/include/Settings.h index befadb6..bd9b55b 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -4,11 +4,10 @@ #ifndef SETTINGS_H #define SETTINGS_H -#include "Game.h" #include "InputMonitor.h" #include - +class Game; class Settings { public: int thisSettingPage; diff --git a/src/InputMonitor.cpp b/src/InputMonitor.cpp index 1a31d8b..af3d27b 100644 --- a/src/InputMonitor.cpp +++ b/src/InputMonitor.cpp @@ -54,6 +54,8 @@ int InputMonitor::pollInput() { } return -1; } +void InputMonitor::pause() { paused.store(true); resetInput(); } +void InputMonitor::resume() { initInput(); paused.store(false); } #else void InputMonitor::initInput() { struct termios newt; diff --git a/src/Settings.cpp b/src/Settings.cpp index 1b64887..b625830 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -1,5 +1,6 @@ #include "Settings.h" - +#include "Game.h" +#include "DisplayUtils.h" #include Settings::Settings(Game* g) { @@ -62,6 +63,8 @@ std::string Settings::setUsername() { std::cout<<"what's your name?"<< nl; std::getline(std::cin, userName); game->monitor->resume(); + game->setHalt(false); + thisSettingPage = menu; return userName; } From 4d560dfec8c98f7b8f7891cb782182d89d1099f8 Mon Sep 17 00:00:00 2001 From: Calc1te Date: Thu, 18 Sep 2025 14:57:12 +0800 Subject: [PATCH 3/3] add a window size check --- include/Game.h | 7 ++----- include/Settings.h | 2 +- src/Game.cpp | 17 ++++++++++++++--- src/Settings.cpp | 6 ++++-- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/include/Game.h b/include/Game.h index cd576b4..f239a1e 100644 --- a/include/Game.h +++ b/include/Game.h @@ -22,10 +22,7 @@ class Settings; class Game { public: -#ifndef _WIN64 - struct winsize win; -#endif - const std::string username; + std::string username; std::string statMessage; std::atomic bIsRunning; std::atomic bInputMode; @@ -38,7 +35,7 @@ class Game { int iClickIncrement; int iAutoIncrement; int iOptionIdx; - int LINE_HEIGHT; + int LINE_HEIGHT = 0; bool bIsInputThreadRunning; void (Game::*buyConfirm)(int idx); void (Settings::*settingConfirm)(int idx); diff --git a/include/Settings.h b/include/Settings.h index bd9b55b..087fd4b 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -26,7 +26,7 @@ class Settings { void displaySettingsLoadSave(); - std::string setUsername(); + void setUsername(); void confirm(int option); diff --git a/src/Game.cpp b/src/Game.cpp index 3db0edb..6295b2a 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -45,10 +45,21 @@ void Game::initWindow() { GetConsoleScreenBufferInfo(hConsole, &csbi); LINE_HEIGHT = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; #else + struct winsize win; ioctl(STDOUT_FILENO, TIOCGWINSZ, &win); - LINE_HEIGHT = win.ws_col; + LINE_HEIGHT = win.ws_row; + int LINE_WIDTH = win.ws_col; #endif - + while (LINE_HEIGHT < 18 || LINE_WIDTH < 40) { + std::cout< 6)std::cout << GREEN <<"..."< LINE_HEIGHT && upgrades.size() - i > LINE_HEIGHT - 13) { + if (upgrades.size() > LINE_HEIGHT - 13 && upgrades.size() - i > LINE_HEIGHT - 13) { continue; } std::cout << GREEN << "Upgrade " << i + 1 << ": " diff --git a/src/Settings.cpp b/src/Settings.cpp index b625830..bfc00dc 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -53,7 +53,7 @@ void Settings::displaySettingsLoadSave() { } -std::string Settings::setUsername() { +void Settings::setUsername() { // halt display and input monitor to let user type something game->setHalt(true); game->monitor->pause(); @@ -65,7 +65,9 @@ std::string Settings::setUsername() { game->monitor->resume(); game->setHalt(false); thisSettingPage = menu; - return userName; + game->username = userName; + + } void Settings::confirm(int idx){}