Compare commits
6 Commits
main
...
buzzer-tes
| Author | SHA1 | Date | |
|---|---|---|---|
| 8afb65612f | |||
| ad989978e3 | |||
| c0014aa7d9 | |||
| 79c92c23aa | |||
| 66f64510f8 | |||
| 2bc9a28162 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
lcd-env/
|
||||
241
random_timer.py
241
random_timer.py
@@ -1,134 +1,191 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Catchphrase
|
||||
Author: Jerick Oates
|
||||
"""
|
||||
|
||||
import random
|
||||
import RPi.GPIO as GPIO
|
||||
import time
|
||||
import random
|
||||
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
|
||||
# --- CONFIG ---
|
||||
WORDS_FILE = "words.txt"
|
||||
TIMER_SECONDS = 30
|
||||
I2C_ADDRESS = 0x27
|
||||
I2C_CHIP = 'PCF8574'
|
||||
|
||||
# --- GPIO SETUP ---
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
|
||||
# ---------- HELPERS ----------
|
||||
# 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):
|
||||
"""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:
|
||||
lines = [line.strip() for line in f if line.strip()]
|
||||
if not lines:
|
||||
raise ValueError("Word file is empty.")
|
||||
return words
|
||||
return lines
|
||||
except Exception as e:
|
||||
print(f"❌ Error loading words: {e}")
|
||||
sys.exit(1)
|
||||
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 pick_random(words):
|
||||
"""Return a random word from the list."""
|
||||
return random.choice(words)
|
||||
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)
|
||||
|
||||
# ---------- TIMER THREAD ----------
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
super().__init__()
|
||||
self.seconds = seconds
|
||||
self.on_finish = on_finish # function to call when timer ends
|
||||
self._running = threading.Event()
|
||||
self._running.set()
|
||||
|
||||
def run(self):
|
||||
for _ in range(self.seconds):
|
||||
if not self._running.is_set():
|
||||
break # stopped early by user
|
||||
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)
|
||||
# Timer finished (or was stopped)
|
||||
self._running.clear()
|
||||
if self.on_finish:
|
||||
self.on_finish() # announce completion
|
||||
time_left -= 1
|
||||
with lock:
|
||||
timer_running = False
|
||||
lcd_show_scores()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the timer before it reaches zero."""
|
||||
self._running.clear()
|
||||
|
||||
@property
|
||||
def is_running(self):
|
||||
return self._running.is_set()
|
||||
# --- GAME LOGIC ---
|
||||
def pick_random_word():
|
||||
global current_word
|
||||
current_word = random.choice(words)
|
||||
|
||||
|
||||
# ---------- MAIN LOOP ----------
|
||||
def main():
|
||||
words = load_words()
|
||||
timer_thread = None
|
||||
|
||||
print("=== Catchphrase, but Better ===")
|
||||
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")
|
||||
|
||||
# Helper that prints when the timer ends
|
||||
def announce_finish():
|
||||
print("\n⏰ Time is up! Returning to menu.")
|
||||
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:
|
||||
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()
|
||||
print("👋 Goodbye!")
|
||||
break
|
||||
|
||||
elif cmd == "start":
|
||||
if timer_thread and timer_thread.is_running:
|
||||
print("[!] Timer is already running.")
|
||||
timer_running = False
|
||||
lcd_show_scores()
|
||||
else:
|
||||
# Start a new timer with the finish callback
|
||||
timer_thread = CountdownTimer(on_finish=announce_finish)
|
||||
timer_thread.daemon = True # exit when main thread exits
|
||||
# Start timer & show first word
|
||||
pick_random_word()
|
||||
timer_thread = CountdownTimer(TIMER_SECONDS)
|
||||
timer_thread.daemon = True
|
||||
timer_running = True
|
||||
timer_thread.start()
|
||||
|
||||
# Show the first word right away
|
||||
print(pick_random(words))
|
||||
print(f"[+] 30-second timer started. ({TIMER_SECONDS}s)")
|
||||
def next_button_callback(channel):
|
||||
global current_word
|
||||
with lock:
|
||||
if timer_running: # Only works while timer runs
|
||||
pick_random_word()
|
||||
|
||||
elif cmd == "next":
|
||||
if not (timer_thread and timer_thread.is_running):
|
||||
print("[!] No active timer - use 'start' first.")
|
||||
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:
|
||||
print(pick_random(words))
|
||||
lcd_show_scores()
|
||||
|
||||
elif cmd == "stop":
|
||||
if timer_thread and timer_thread.is_running:
|
||||
timer_thread.stop()
|
||||
timer_thread.join()
|
||||
print("[+] Timer stopped early.")
|
||||
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:
|
||||
print("[!] There is no running timer to stop.")
|
||||
lcd_show_scores()
|
||||
|
||||
else:
|
||||
print(f"[!] Unknown command: {cmd}")
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user