Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@
## 2026-02-13 - Tactile Feedback in CLI
**Learning:** In terminal-based games, users expect immediate visual feedback for their actions. Relying on a periodic "tick" to update the UI creates a laggy feel. Using `poll()` with a dynamic timeout allows the application to remain idle yet wake up instantly to process and render user input.
**Action:** Always trigger a UI refresh immediately after processing user input in CLI applications, and use efficient waiting mechanisms (like `poll`) that can be interrupted by input.

## 2024-10-24 - Cursor Visibility and Readiness in CLI Games
**Learning:** A visible blinking cursor in a fast-paced CLI game is visually distracting. Additionally, throwing users directly into a timed game loop without a readiness prompt leads to immediate initial failure and a poor UX.
**Action:** Always hide the cursor (`\033[?25l`) during gameplay (restoring it gracefully on normal or interrupted exits) and implement a "Press any key to start..." prompt before entering active timed loops to ensure the user is prepared.
10 changes: 8 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ struct termios oldt;
void restore_terminal(int signum) {
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
// Use write() and _exit() because they are async-signal-safe
const char* msg = "\033[0m\n\nGame interrupted. Terminal settings restored.\n";
write(STDOUT_FILENO, msg, 52);
const char msg[] = "\033[?25h\033[0m\n\nGame interrupted. Terminal settings restored.\n";
write(STDOUT_FILENO, msg, sizeof(msg) - 1);
_exit(signum);
}

Expand All @@ -47,10 +47,15 @@ int main() {
}

long long score = 0; bool hardMode = false; char input;
std::cout << "\033[?25l"; // Hide cursor
std::cout << CLR_CTRL << "==========================\n SPEED CLICKER\n==========================\n" << CLR_RESET
<< "Controls:\n " << CLR_CTRL << "[h]" << CLR_RESET << " Toggle Hard Mode (10x Speed!)\n "
<< CLR_CTRL << "[q]" << CLR_RESET << " Quit Game\n " << CLR_CTRL << "[Any key]" << CLR_RESET << " Click!\n\n";

std::cout << "Press any key to start..." << std::flush;
read(STDIN_FILENO, &input, 1);
std::cout << "\r \r" << std::flush; // Clear the start prompt

struct pollfd fds[1] = {{STDIN_FILENO, POLLIN, 0}};
auto last_tick = std::chrono::steady_clock::now();
bool updateUI = true;
Expand Down Expand Up @@ -86,6 +91,7 @@ int main() {
}
}
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
std::cout << "\033[?25h"; // Restore cursor
std::cout << "\n\n" << CLR_SCORE << "Final Score: " << score << CLR_RESET << "\nThanks for playing!\n";
return 0;
}
82 changes: 82 additions & 0 deletions verify_ux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import pty
import os
import select
import time

def verify_ux():
master, slave = pty.openpty()
pid = os.fork()

if pid == 0:
# Child process: run the game
os.setsid()
os.dup2(slave, 0)
os.dup2(slave, 1)
os.dup2(slave, 2)
os.close(slave)
os.close(master)
os.execl('./game', './game')
else:
# Parent process
os.close(slave)
output = b''

# Read the initial prompt and wait for it
timeout = 2.0
start_time = time.time()
while time.time() - start_time < timeout:
r, _, _ = select.select([master], [], [], 0.1)
if r:
data = os.read(master, 1024)
if not data:
break
output += data
if b'Press any key to start...' in output:
break

# We need to send a key to start the game
os.write(master, b' ')

# Read until we see the first score or wait a little
start_time = time.time()
while time.time() - start_time < timeout:
r, _, _ = select.select([master], [], [], 0.1)
if r:
data = os.read(master, 1024)
if not data:
break
output += data
if b'Score: 0' in output:
break

# Send quit command
os.write(master, b'q')

# Read the rest of the output
while True:
r, _, _ = select.select([master], [], [], 0.5)
if r:
try:
data = os.read(master, 1024)
if not data:
break
output += data
except OSError:
break
else:
break

os.close(master)
os.waitpid(pid, 0)

output_str = output.decode('utf-8', errors='ignore')
print("Captured output:")
print(repr(output_str))

assert '\033[?25l' in output_str, "Cursor hide sequence missing!"
assert '\033[?25h' in output_str, "Cursor restore sequence missing!"
assert 'Press any key to start...' in output_str, "Start prompt missing!"
print("All UX checks passed!")

if __name__ == '__main__':
verify_ux()