2 Commits

Author SHA1 Message Date
66f64510f8 displays team scores on LCD 2025-08-09 16:57:56 -04:00
2bc9a28162 Working display on the LCD 2025-08-09 16:50:37 -04:00
2 changed files with 84 additions and 34 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
lcd-env/

View File

@@ -1,22 +1,36 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Catchphrase Catchphrase with LCD Output (Scores & Reset)
Author: Jerick Oates
""" """
import random import random
import time import time
import threading import threading
import sys import sys
from RPLCD.i2c import CharLCD
# ---------- CONFIG ---------- # ---------- CONFIG ----------
WORDS_FILE = "words.txt" # file with one word per line WORDS_FILE = "words.txt" # file with one word per line
TIMER_SECONDS = 30 # how long the timer runs 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))
def lcd_update_scores():
"""Update the bottom LCD line with team scores (T1 left, T2 right)."""
lcd.cursor_pos = (1, 0)
score_str = f"{score_t1}{' ' * 14}{score_t2}"
lcd.write_string(score_str)
# ---------- HELPERS ---------- # ---------- HELPERS ----------
def load_words(path=WORDS_FILE): def load_words(path=WORDS_FILE):
"""Return a list of non-empty stripped lines from the given file."""
try: try:
with open(path, "r", encoding="utf-8") as f: with open(path, "r", encoding="utf-8") as f:
words = [line.strip() for line in f if line.strip()] words = [line.strip() for line in f if line.strip()]
@@ -27,76 +41,93 @@ def load_words(path=WORDS_FILE):
print(f"❌ Error loading words: {e}") print(f"❌ Error loading words: {e}")
sys.exit(1) sys.exit(1)
def pick_random(words): def pick_random(words):
"""Return a random word from the list."""
return random.choice(words) return random.choice(words)
# ---------- TIMER THREAD ---------- # ---------- TIMER THREAD ----------
class CountdownTimer(threading.Thread): class CountdownTimer(threading.Thread):
"""
Background thread that counts down for TIMER_SECONDS.
When it finishes, it calls an optional callback.
"""
def __init__(self, seconds=TIMER_SECONDS, on_finish=None): def __init__(self, seconds=TIMER_SECONDS, on_finish=None):
super().__init__() super().__init__()
self.seconds = seconds self.seconds = seconds
self.on_finish = on_finish # function to call when timer ends self.on_finish = on_finish
self._running = threading.Event() self._running = threading.Event()
self._running.set() self._running.set()
def run(self): def run(self):
for _ in range(self.seconds): for _ in range(self.seconds):
if not self._running.is_set(): if not self._running.is_set():
break # stopped early by user break
time.sleep(1) time.sleep(1)
# Timer finished (or was stopped)
self._running.clear() self._running.clear()
if self.on_finish: if self.on_finish:
self.on_finish() # announce completion self.on_finish()
def stop(self): def stop(self):
"""Stop the timer before it reaches zero."""
self._running.clear() self._running.clear()
@property @property
def is_running(self): def is_running(self):
return self._running.is_set() return self._running.is_set()
# ---------- SCORE VARIABLES ----------
score_t1 = 0
score_t2 = 0
def add_score(team):
global score_t1, score_t2
if team == 1:
score_t1 += 1
if score_t1 >= 7:
print("🏆 Team 1 wins! Scores reset.")
score_t1 = score_t2 = 0
elif team == 2:
score_t2 += 1
if score_t2 >= 7:
print("🏆 Team 2 wins! Scores reset.")
score_t1 = score_t2 = 0
lcd_update_scores()
def reset_scores():
global score_t1, score_t2
score_t1 = score_t2 = 0
lcd_update_scores()
# ---------- MAIN LOOP ---------- # ---------- MAIN LOOP ----------
def main(): def main():
global score_t1, score_t2
words = load_words() words = load_words()
timer_thread = None timer_thread = None
print("=== Catchphrase, but Better ===") print("=== Catchphrase, with LCD & Scores ===")
print("Commands:") print("Commands:")
print(" start - begin the 30-second timer and show a word immediately") print(" start - start 30s timer & show a word")
print(" next - show another random word while the timer is running") print(" next - show another word while timer runs")
print(" stop - cancel the timer early") print(" stop - stop timer early")
print(" t1 - add point to Team 1")
print(" t2 - add point to Team 2")
print(" reset - reset both scores to 0")
print(" exit / quit - leave the program") print(" exit / quit - leave the program")
# Helper that prints when the timer ends lcd_print("Ready", line=0)
lcd_update_scores()
def announce_finish(): def announce_finish():
print("\n⏰ Time is up! Returning to menu.") print("\n⏰ Time is up! Returning to menu.")
print("=== Catchphrase ===") lcd_print("Time is up!", line=0)
print("Commands:") lcd_update_scores()
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: while True:
try: try:
cmd = input("\n> ").strip().lower() cmd = input("\n> ").strip().lower()
except EOFError: except EOFError:
break break
if cmd in ("exit", "quit"): if cmd in ("exit", "quit"):
if timer_thread and timer_thread.is_running: if timer_thread and timer_thread.is_running:
timer_thread.stop() timer_thread.stop()
timer_thread.join() timer_thread.join()
lcd_print("Goodbye!", line=0)
lcd_update_scores()
print("👋 Goodbye!") print("👋 Goodbye!")
break break
@@ -104,29 +135,47 @@ def main():
if timer_thread and timer_thread.is_running: if timer_thread and timer_thread.is_running:
print("[!] Timer is already running.") print("[!] Timer is already running.")
else: else:
# Start a new timer with the finish callback
timer_thread = CountdownTimer(on_finish=announce_finish) timer_thread = CountdownTimer(on_finish=announce_finish)
timer_thread.daemon = True # exit when main thread exits timer_thread.daemon = True
timer_thread.start() timer_thread.start()
# Show the first word right away word = pick_random(words)
print(pick_random(words)) print(word)
lcd_print(word, line=0)
lcd_update_scores()
print(f"[+] 30-second timer started. ({TIMER_SECONDS}s)") print(f"[+] 30-second timer started. ({TIMER_SECONDS}s)")
elif cmd == "next": elif cmd == "next":
if not (timer_thread and timer_thread.is_running): if not (timer_thread and timer_thread.is_running):
print("[!] No active timer - use 'start' first.") print("[!] No active timer - use 'start' first.")
else: else:
print(pick_random(words)) word = pick_random(words)
print(word)
lcd_print(word, line=0)
lcd_update_scores()
elif cmd == "stop": elif cmd == "stop":
if timer_thread and timer_thread.is_running: if timer_thread and timer_thread.is_running:
timer_thread.stop() timer_thread.stop()
timer_thread.join() timer_thread.join()
lcd_print("Stopped early", line=0)
lcd_update_scores()
print("[+] Timer stopped early.") print("[+] Timer stopped early.")
else: else:
print("[!] There is no running timer to stop.") print("[!] There is no running timer to stop.")
elif cmd == "t1":
add_score(1)
print(f"Team 1: {score_t1}, Team 2: {score_t2}")
elif cmd == "t2":
add_score(2)
print(f"Team 1: {score_t1}, Team 2: {score_t2}")
elif cmd == "reset":
reset_scores()
print("Scores reset.")
else: else:
print(f"[!] Unknown command: {cmd}") print(f"[!] Unknown command: {cmd}")