| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- import platform
- import sys
- import customtkinter as ctk
- from PIL import Image, ImageTk
- import time
- import threading
- import tkinter as tk
- import dobluetooth
- import math
- import pygame
- # Initialize pygame and mixer specifically for MP3
- pygame.init()
- pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=2048)
- try:
- stop_sound = pygame.mixer.Sound("ding.mp3")
- print("Successfully loaded sound file")
- except Exception as e:
- print(f"Warning: Could not load sound file: {str(e)}")
- stop_sound = None
- # If on Windows, allow STA for Bleak
- if platform.system() == "Windows":
- try:
- from bleak.backends.winrt.util import allow_sta
- allow_sta()
- except ImportError:
- pass
- # UI setup
- ctk.set_appearance_mode("System")
- ctk.set_default_color_theme("blue")
- app = ctk.CTk()
- app.geometry("1440x1024")
- canvas = ctk.CTkCanvas(app, width=250, height=250, highlightthickness=0)
- canvas.config(background="white")
- canvas.place(x=0, y=0)
- # Score variables
- currentScore = tk.DoubleVar()
- currentScore.set(0)
- names = []
- scores = []
- nameEntry = None
- enterButton = None
- statusLabel = None
- dinger = None
- bell_image = None # Store the image reference
- highestScore = 0
- highScoreName = ""
- punch_on = False
- current_dinger_y = 900
- def move_dinger_step(start_y, target_y, steps_left, total_steps):
- global current_dinger_y
- if steps_left > 0:
- # Calculate progress (0 to 1)
- progress = (total_steps - steps_left) / total_steps
-
- # Calculate the current position directly between start and target
- new_y = start_y + (target_y - start_y) * progress
-
- # Update position
- current_dinger_y = new_y
- dinger.place_configure(y=int(new_y))
-
- # Schedule next step
- app.after(10, lambda: move_dinger_step(start_y, target_y, steps_left - 1, total_steps))
- else:
- # Ensure we end up exactly at target
- current_dinger_y = target_y
- dinger.place_configure(y=int(target_y))
- def update_dinger_position(final_score=None):
- global current_dinger_y
- start_y = current_dinger_y # Remember where we're starting from
-
- if final_score is None:
- target_y = 900 # Bottom position
- else:
- # Calculate target position based on score
- max_movement = 600
- score_threshold = 100
- # Calculate new position
- movement = (final_score / score_threshold) * max_movement
- movement = min(movement, max_movement)
- target_y = 900 - movement # Calculate from top position
- target_y = max(100, min(target_y, 900)) # Ensure within bounds
-
- # Start the animation
- move_dinger_step(start_y, target_y, 60, 60)
- def startPunch():
- global punch_on
- punch_on = True
- updateStatusLabel()
- update_dinger_position()
- def stopPunch():
- global punch_on
- punch_on = False
- updateStatusLabel()
- update_dinger_position(currentScore.get())
- if stop_sound:
- try:
- stop_sound.play()
- except Exception as e:
- print(f"Error playing sound: {str(e)}")
- def resetPunch():
- global currentScore
- currentScore.set(0)
- update_dinger_position(0)
- def load_scores():
- global names, scores, highestScore, highScoreName
- try:
- with open("all_scores.txt", "r") as f:
- lines = f.readlines()
- for line in lines:
- line = line.strip()
- if line and "," in line: # Check if line is not empty and contains a comma
- try:
- name, score = line.split(",")
- score = float(score)
- names.append(name)
- scores.append(score)
- if score > highestScore:
- highestScore = score
- highScoreName = name
- except (ValueError, IndexError):
- # Skip malformed lines
- continue
- except FileNotFoundError:
- # Create the file if it doesn't exist
- with open("all_scores.txt", "w") as f:
- pass
- def save_new_score(name, score):
- with open("all_scores.txt", "a") as f:
- f.write(f"{name},{score}\n")
- def updateStatusLabel():
- global statusLabel
- if punch_on:
- statusLabel.configure(text="RECORDING", fg_color="green", text_color="white")
- else:
- statusLabel.configure(text="NOT RECORDING", fg_color="gray70", text_color="black")
- def submitScore():
- global highestScore, highScoreName
- # Get the name from the entry field
- entered_name = nameEntry.get().strip()
- if entered_name: # Only proceed if a name was entered
- current_score = currentScore.get()
- names.append(entered_name)
- scores.append(current_score)
-
- # Save the new score to file
- save_new_score(entered_name, current_score)
-
- if current_score > highestScore:
- highestScore = current_score
- highScoreName = entered_name
- # Clear the entry field after recording
- nameEntry.delete(0, 'end')
-
- # Refresh the leaderboard display
- placeLeaderboard()
- # Leaderboard & UI setup
- def placeLeaderboard():
- global nameEntry, enterButton, statusLabel, dinger, bell_image
- canvas.create_rectangle(0, 0, 500, 500, fill="grey20", width=2)
- # Get top 5 scores
- score_pairs = list(zip(names, scores))
- score_pairs.sort(key=lambda x: x[1], reverse=True)
- top_5_pairs = score_pairs[:5]
- intx = 0
- inty = 60
- for name, score in top_5_pairs:
- label1 = ctk.CTkLabel(width=124, height=39, text_color="white", text=name, font=("Arial", 15), master=app)
- label1.place(x=intx, y=inty)
- label2 = ctk.CTkLabel(width=124, height=39, text_color="white", text=f"{score:.2f}", font=("Arial", 15), master=app)
- label2.place(x=intx + 125, y=inty)
- inty += 40
- ctk.CTkLabel(width=249, height=39, text_color="white", text="Leaderboard", font=("Arial", 50), master=app).place(x=0, y=0)
-
- # Create status indicator
- statusLabel = ctk.CTkLabel(
- master=app,
- width=120,
- height=40,
- text="NOT RECORDING",
- fg_color="gray70",
- text_color="black",
- corner_radius=10,
- font=("Arial", 16)
- )
- statusLabel.place(x=50, y=450)
-
- ctk.CTkButton(master=app,
- width=120,
- height=80,
- text="START",
- fg_color="green",
- text_color="gray99",
- corner_radius=25,
- font=("Arial", 30),
- command=startPunch).place(x=50, y=500)
- ctk.CTkButton(master=app,
- width=120,
- height=80,
- text="STOP",
- fg_color="orange2",
- text_color="gray99",
- corner_radius=25,
- font=("Arial", 30),
- command=stopPunch).place(x=50, y=600)
- ctk.CTkButton(master=app,
- width=120,
- height=80,
- text="RESET",
- fg_color="red1",
- text_color="gray99",
- corner_radius=25,
- font=("Arial", 30),
- command=resetPunch).place(x=50, y=700)
- ctk.CTkLabel(width=50, height=20, text_color="white", text="Current Score", font=("Arial", 50), master=app).place(x=350, y=20)
- ctk.CTkLabel(width=50, height=20, text_color="white", textvariable=currentScore, font=("Arial", 50), master=app).place(x=500, y=100)
- nameEntry = ctk.CTkEntry(width=300, height=50, master=app, font=("Arial", 50))
- nameEntry.place(x=370, y=200)
- enterButton = ctk.CTkButton(width=50, height=50, text="ENTER", fg_color="green", font=("Arial", 20),
- text_color="gray99", master=app, command=submitScore)
- enterButton.place(x=670, y=205)
- # Load and display the bell image
- bell_image = Image.open("./BellDrawing.png")
- photo = ImageTk.PhotoImage(bell_image)
- image_label = ctk.CTkLabel(image=photo, text="", width=10, master=app)
- image_label.image = photo # Keep a reference to prevent garbage collection
- image_label.place(x=800, y=0)
- # Create the dinger after the image (so it appears on top)
- dinger = ctk.CTkLabel(width=40, height=30, text="", fg_color="gray77", master=app)
- dinger.place(x=1005, y=900)
-
- # Start updating dinger position
- update_dinger_position()
- # BLE thread setup
- def ble_thread_func():
- import asyncio
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
- loop.run_until_complete(dobluetooth.getData())
- def start_ble_thread():
- thread = threading.Thread(target=ble_thread_func, daemon=True)
- thread.start()
- def data_loop():
- last_data = None
- last_value = 0.00
- while True:
- bledata = dobluetooth.getDataArr()
- if punch_on:
- if last_data == None:
- last_data = bledata
- current_x_accel = bledata[3]
- current_y_accel = bledata[4]
- last_x_accel = last_data[3]
- last_y_accel = last_data[4]
- value = math.sqrt((current_x_accel - last_y_accel)**2 + ((current_y_accel - last_y_accel)**2))/4
- print(value)
- if(value < 0.26):
- pass
- else:
- if(last_value - value > 10.00):
- app.after(0, lambda val=value: currentScore.set(currentScore.get() + int(val)))
- else:
- if(value - last_value > 10.00):
- if(value == last_value):
- app.after(0, lambda val=value: currentScore.set(currentScore.get() + int(val*10)))
- else:
- app.after(0, lambda val=value: currentScore.set(currentScore.get() + int(val)))
- else:
- app.after(0, lambda val=value: currentScore.set(currentScore.get() + int(val*25)))
- app.after(0, update_dinger_position)
- last_data = bledata
- last_value = value
- time.sleep(0.1)
- def start_data_loop_thread():
- thread = threading.Thread(target=data_loop, daemon=True)
- thread.start()
- # Run UI setup
- load_scores() # Load existing scores before setting up UI
- placeLeaderboard()
- # Start threads after UI initializes
- app.after(500, start_ble_thread)
- app.after(1000, start_data_loop_thread)
- app.mainloop()
|