diff --git a/.Jules/palette.md b/.Jules/palette.md index 01736b2..d6b3a40 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -6,3 +6,11 @@ ## 2026-01-09 - Terminal I/O and Blocking **Learning:** Standard terminal I/O is line-buffered by default. For real-time games, it's essential to use non-canonical mode (raw mode) to capture keypresses immediately. Also, internal journals should be kept clean if they are to be included in the repo. + +## 2025-05-14 - Tactile Feedback in CLI +**Learning:** In terminal-based interactive applications, waiting for the next "tick" of a game loop to update the UI creates a "laggy" feel. Users expect immediate visual confirmation of their actions. Consolidating the rendering logic and triggering it immediately upon both input events and timer ticks significantly improves the perceived responsiveness (tactile feedback). +**Action:** Always trigger a UI refresh immediately after processing user input, even if a periodic timer update is already scheduled. + +## 2025-05-14 - CI Integrity as Developer UX +**Learning:** Broken CI workflows or redundant configuration files (like Rust files in a C++ project) create a confusing and frustrating experience for developers. Maintaining a clean and relevant project structure is essential for a good "first contact" UX. +**Action:** Always verify that CI workflows match the project's actual language and build system, and remove any leftover boilerplate from other languages. diff --git a/.github/workflows/rust.yml b/.github/workflows/ci.yml similarity index 65% rename from .github/workflows/rust.yml rename to .github/workflows/ci.yml index 9fd45e0..75aa8b9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Rust +name: C++ CI on: push: @@ -6,17 +6,13 @@ on: pull_request: branches: [ "main" ] -env: - CARGO_TERM_COLOR: always - jobs: build: - runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build - run: cargo build --verbose + run: make - name: Run tests - run: cargo test --verbose + run: make test diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index c9a3abc..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "code" -version = "0.1.0" -edition = "2021" - -[dependencies] -# Add your dependencies here - -[dev-dependencies] -# Add your dev dependencies here diff --git a/Makefile b/Makefile index 3bf8240..447e444 100644 --- a/Makefile +++ b/Makefile @@ -25,5 +25,11 @@ run: $(TARGET) clean: rm -f $(TARGET) +# Run automated tests +test: $(TARGET) + @echo "Running tests..." + @echo "q" | ./$(TARGET) | grep -q "Thanks for playing!" + @echo "Tests passed!" + # Phony targets -.PHONY: all run clean +.PHONY: all run clean test diff --git a/src/main.cpp b/src/main.cpp index a70e887..52a0fe4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,15 @@ #include #include #include +#include + +// ANSI colors +#define RESET "\033[0m" +#define BOLD "\033[1m" +#define BOLD_GREEN "\033[1;32m" +#define BOLD_RED "\033[1;31m" +#define BOLD_BLUE "\033[1;34m" +#define BOLD_YELLOW "\033[1;33m" int main() { struct termios oldt, newt; @@ -13,29 +22,51 @@ int main() { tcsetattr(STDIN_FILENO, TCSANOW, &newt); int score = 0; bool hardMode = false; char input; - std::cout << "==========================\n SPEED CLICKER\n==========================\n" - << "Controls:\n [h] Toggle Hard Mode (10x Speed!)\n [q] Quit Game\n [Any key] Click!\n\n"; + std::cout << BOLD << "==========================\n" + << BOLD_GREEN << " SPEED CLICKER\n" + << BOLD << "==========================\n" << RESET + << BOLD_YELLOW << "Controls:\n" << RESET + << " [" << BOLD_YELLOW << "h" << RESET << "] Toggle Hard Mode (10x Speed!)\n" + << " [" << BOLD_YELLOW << "q" << RESET << "] Quit Game\n" + << " [" << BOLD_YELLOW << "Any key" << RESET << "] Click!\n\n"; struct pollfd fds[1] = {{STDIN_FILENO, POLLIN, 0}}; auto last_tick = std::chrono::steady_clock::now(); + while (true) { - int timeout = hardMode ? 100 : 1000; - if (poll(fds, 1, 0) > 0) { + int base_timeout = hardMode ? 100 : 1000; + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(now - last_tick).count(); + int remaining = std::max(0, (int)(base_timeout - (long long)elapsed)); + + bool shouldUpdateUI = false; + if (poll(fds, 1, remaining) > 0) { if (read(STDIN_FILENO, &input, 1) <= 0 || input == 'q') break; if (input == 'h') { hardMode = !hardMode; - std::cout << (hardMode ? "\n[HARD MODE] Speed x10!\n" : "\n[NORMAL MODE]\n"); - } else score++; + } else { + score++; + } + shouldUpdateUI = true; } - auto now = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast(now - last_tick).count(); - if (elapsed >= timeout) { - score++; last_tick = now; - std::cout << "Score: " << score << (hardMode ? " [FAST] " : " [NORMAL] ") << "\r" << std::flush; + + now = std::chrono::steady_clock::now(); + elapsed = std::chrono::duration_cast(now - last_tick).count(); + if (elapsed >= base_timeout) { + score++; + last_tick = now; + shouldUpdateUI = true; + } + + if (shouldUpdateUI) { + std::cout << "\r" << BOLD << "Score: " << BOLD_GREEN << score << RESET + << (hardMode ? BOLD_RED " [FAST!] " : BOLD_BLUE " [NORMAL] ") + << std::flush; } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); } + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); - std::cout << "\nFinal Score: " << score << "\nThanks for playing!\n"; + std::cout << "\n\n" << BOLD << "Final Score: " << BOLD_GREEN << score << RESET << "\n" + << BOLD << "Thanks for playing!\n" << RESET; return 0; }