diff --git a/.Jules/palette.md b/.Jules/palette.md index 01736b2..71c4142 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -6,3 +6,7 @@ ## 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. + +## 2026-02-03 - Tactile Feedback in CLI +**Learning:** For interactive CLI tools, immediate visual feedback on keypress is crucial for a "tactile" feel. Decoupling the UI refresh from a fixed tick rate and triggering it on every input event makes the application feel significantly more responsive. +**Action:** Always ensure input handlers call the UI render logic immediately after updating the state, rather than waiting for the next game loop iteration. diff --git a/.github/workflows/rust.yml b/.github/workflows/build.yml similarity index 65% rename from .github/workflows/rust.yml rename to .github/workflows/build.yml index 9fd45e0..41ca831 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Rust +name: C++ Build and Test 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/Makefile b/Makefile index 3bf8240..5da3825 100644 --- a/Makefile +++ b/Makefile @@ -25,5 +25,9 @@ run: $(TARGET) clean: rm -f $(TARGET) +# Run tests +test: $(TARGET) + echo "q" | ./$(TARGET) | grep "Final Score:" + # Phony targets -.PHONY: all run clean +.PHONY: all run clean test diff --git a/src/main.cpp b/src/main.cpp index a70e887..99cb7fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,11 @@ #include #include #include +#include + +void render_ui(int score, bool hardMode) { + std::cout << "\rScore: " << score << (hardMode ? " [HARD MODE] " : " [NORMAL MODE] ") << std::flush; +} int main() { struct termios oldt, newt; @@ -20,20 +25,25 @@ int main() { auto last_tick = std::chrono::steady_clock::now(); while (true) { int timeout = hardMode ? 100 : 1000; - if (poll(fds, 1, 0) > 0) { + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(now - last_tick).count(); + int wait_time = std::max(0, timeout - (int)elapsed); + + if (poll(fds, 1, wait_time) > 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++; + } + render_ui(score, hardMode); } - auto now = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast(now - last_tick).count(); + now = std::chrono::steady_clock::now(); + 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; + render_ui(score, hardMode); } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); } tcsetattr(STDIN_FILENO, TCSANOW, &oldt); std::cout << "\nFinal Score: " << score << "\nThanks for playing!\n";