#!/usr/bin/env python3 """ Catchphrase with LCD Output (Centered & Clean) """ import random import time import threading import sys from RPLCD.i2c import CharLCD # ---------- CONFIG ---------- WORDS_FILE = "words.txt" # file with one word per line TIMER_SECONDS = 30 # how long the timer runs I2C_ADDRESS = 0x27 # change if your LCD address is different I2C_CHIP = 'PCF8574' # common backpack chip # ---------- LCD ---------- lcd = CharLCD(I2C_CHIP, I2C_ADDRESS) def lcd_print(text, line=0): """Print text centered on given LCD line (0 or 1), padded to clear leftovers.""" lcd.cursor_pos = (line, 0) lcd.write_string(text.center(16)) # 16 chars wide # ---------- HELPERS ---------- def load_words(path=WORDS_FILE): """Return a list of non-empty stripped lines from the given file.""" try: with open(path, "r", encoding="utf-8") as f: words = [line.strip() for line in f if line.strip()] if not words: raise ValueError("Word file is empty.") return words except Exception as e: print(f"āŒ Error loading words: {e}") sys.exit(1) def pick_random(words): """Return a random word from the list.""" return random.choice(words) # ---------- TIMER THREAD ---------- class CountdownTimer(threading.Thread): def __init__(self, seconds=TIMER_SECONDS, on_finish=None): super().__init__() self.seconds = seconds self.on_finish = on_finish self._running = threading.Event() self._running.set() def run(self): for _ in range(self.seconds): if not self._running.is_set(): break time.sleep(1) self._running.clear() if self.on_finish: self.on_finish() def stop(self): self._running.clear() @property def is_running(self): return self._running.is_set() # ---------- MAIN LOOP ---------- def main(): words = load_words() timer_thread = None print("=== Catchphrase, but Better (LCD) ===") print("Commands:") print(" start - begin the 30-second timer and show a word immediately") print(" next - show another random word while the timer is running") print(" stop - cancel the timer early") print(" exit / quit - leave the program") def announce_finish(): print("\nā° Time is up! Returning to menu.") lcd_print("Time is up!", line=0) print("=== Catchphrase ===") print("Commands:") print(" start - begin the 30-second timer and show a word immediately") print(" next - show another random word while the timer is running") print(" stop - cancel the timer early") print(" exit / quit - leave the program") while True: try: cmd = input("\n> ").strip().lower() except EOFError: break if cmd in ("exit", "quit"): if timer_thread and timer_thread.is_running: timer_thread.stop() timer_thread.join() lcd_print("Goodbye!", line=0) print("šŸ‘‹ Goodbye!") break elif cmd == "start": if timer_thread and timer_thread.is_running: print("[!] Timer is already running.") else: timer_thread = CountdownTimer(on_finish=announce_finish) timer_thread.daemon = True timer_thread.start() word = pick_random(words) print(word) lcd_print(word, line=0) print(f"[+] 30-second timer started. ({TIMER_SECONDS}s)") elif cmd == "next": if not (timer_thread and timer_thread.is_running): print("[!] No active timer - use 'start' first.") else: word = pick_random(words) print(word) lcd_print(word, line=0) elif cmd == "stop": if timer_thread and timer_thread.is_running: timer_thread.stop() timer_thread.join() lcd_print("Stopped early", line=0) print("[+] Timer stopped early.") else: print("[!] There is no running timer to stop.") else: print(f"[!] Unknown command: {cmd}") if __name__ == "__main__": main()