#!/usr/bin/env python3 import RPi.GPIO as GPIO import time import random import threading from RPLCD.i2c import CharLCD # --- CONFIG --- WORDS_FILE = "words.txt" TIMER_SECONDS = 30 I2C_ADDRESS = 0x27 I2C_CHIP = 'PCF8574' # --- GPIO SETUP --- GPIO.setmode(GPIO.BCM) # Buttons mapped to pins BUTTON_START_STOP = 23 # Green button BUTTON_NEXT = 27 BUTTON_TEAM1 = 22 BUTTON_TEAM2 = 17 for btn_pin in (BUTTON_START_STOP, BUTTON_NEXT, BUTTON_TEAM1, BUTTON_TEAM2): GPIO.setup(btn_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # --- LCD SETUP --- lcd = CharLCD(I2C_CHIP, I2C_ADDRESS) lcd.clear() # --- GLOBALS --- words = [] timer_thread = None timer_running = False time_left = TIMER_SECONDS score_t1 = 0 score_t2 = 0 current_word = "" lock = threading.Lock() # --- LOAD WORDS --- def load_words(path=WORDS_FILE): try: with open(path, "r", encoding="utf-8") as f: lines = [line.strip() for line in f if line.strip()] if not lines: raise ValueError("Word file is empty.") return lines except Exception as e: print(f"❌ Error loading words: {e}") exit(1) # --- LCD HELPERS --- def lcd_print_lines(line1="", line2=""): lcd.clear() lcd.cursor_pos = (0, 0) lcd.write_string(line1.ljust(16)[:16]) lcd.cursor_pos = (1, 0) lcd.write_string(line2.ljust(16)[:16]) print(line1) print(line2) def lcd_show_word_and_timer(word, seconds): lcd.clear() lcd.cursor_pos = (0, 0) lcd.write_string(f"Time:{seconds:02d} ") lcd.cursor_pos = (1, 0) lcd.write_string(word.center(16)) print(f"Time:{seconds:02d}") print(word) def lcd_show_scores(): msg = f"T1:{score_t1} T2:{score_t2}".center(16) lcd.clear() lcd.write_string(msg) print(msg) def lcd_show_winner(winner_team): msg = f"🏆 Team {winner_team} Wins!".center(16) lcd.clear() lcd.write_string(msg) print(msg) # --- TIMER THREAD --- class CountdownTimer(threading.Thread): def __init__(self, seconds): super().__init__() self.seconds = seconds self._running = threading.Event() self._running.set() def run(self): global time_left, timer_running time_left = self.seconds while time_left > 0 and self._running.is_set(): with lock: lcd_show_word_and_timer(current_word, time_left) time.sleep(1) time_left -= 1 with lock: timer_running = False lcd_show_scores() def stop(self): self._running.clear() # --- GAME LOGIC --- def pick_random_word(): global current_word current_word = random.choice(words) def start_stop_button_callback(channel): global timer_running, timer_thread with lock: if timer_running: # Stop timer if timer_thread: timer_thread.stop() timer_thread.join() timer_running = False lcd_show_scores() else: # Start timer & show first word pick_random_word() timer_thread = CountdownTimer(TIMER_SECONDS) timer_thread.daemon = True timer_running = True timer_thread.start() def next_button_callback(channel): global current_word with lock: if timer_running: # Only works while timer runs pick_random_word() def team1_button_callback(channel): global score_t1 with lock: if not timer_running: # Only works when timer stopped score_t1 += 1 if score_t1 >= 7: lcd_show_winner(1) reset_game_after_delay() else: lcd_show_scores() def team2_button_callback(channel): global score_t2 with lock: if not timer_running: # Only works when timer stopped score_t2 += 1 if score_t2 >= 7: lcd_show_winner(2) reset_game_after_delay() else: lcd_show_scores() def reset_game_after_delay(delay=5): def reset(): global score_t1, score_t2, current_word time.sleep(delay) with lock: score_t1 = 0 score_t2 = 0 current_word = "" lcd_print_lines("Catchphrase started!", "Press Green to Start") threading.Thread(target=reset, daemon=True).start() # --- Setup GPIO event detection --- GPIO.add_event_detect(BUTTON_START_STOP, GPIO.FALLING, callback=start_stop_button_callback, bouncetime=300) GPIO.add_event_detect(BUTTON_NEXT, GPIO.FALLING, callback=next_button_callback, bouncetime=300) GPIO.add_event_detect(BUTTON_TEAM1, GPIO.FALLING, callback=team1_button_callback, bouncetime=300) GPIO.add_event_detect(BUTTON_TEAM2, GPIO.FALLING, callback=team2_button_callback, bouncetime=300) # --- Main program --- def main(): global words words = load_words() lcd_print_lines("Catchphrase started!", "Press Green to Start") try: while True: time.sleep(0.1) except KeyboardInterrupt: pass finally: GPIO.cleanup() lcd.clear() if __name__ == "__main__": main()