GUI.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import platform
  2. import sys
  3. import customtkinter as ctk
  4. from PIL import Image, ImageTk
  5. import time
  6. import threading
  7. import tkinter as tk
  8. import dobluetooth
  9. import math
  10. import pygame
  11. # Initialize pygame and mixer specifically for MP3
  12. pygame.init()
  13. pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=2048)
  14. try:
  15. stop_sound = pygame.mixer.Sound("ding.mp3")
  16. print("Successfully loaded sound file")
  17. except Exception as e:
  18. print(f"Warning: Could not load sound file: {str(e)}")
  19. stop_sound = None
  20. # If on Windows, allow STA for Bleak
  21. if platform.system() == "Windows":
  22. try:
  23. from bleak.backends.winrt.util import allow_sta
  24. allow_sta()
  25. except ImportError:
  26. pass
  27. # UI setup
  28. ctk.set_appearance_mode("System")
  29. ctk.set_default_color_theme("blue")
  30. app = ctk.CTk()
  31. app.geometry("1440x1024")
  32. app.title("Punching Game")
  33. canvas = ctk.CTkCanvas(app, width=250, height=250, highlightthickness=0)
  34. canvas.config(background="white")
  35. canvas.place(x=0, y=0)
  36. # Score variables
  37. currentScore = tk.DoubleVar()
  38. currentScore.set(0)
  39. names = []
  40. scores = []
  41. nameEntry = None
  42. enterButton = None
  43. statusLabel = None
  44. dinger = None
  45. bell_image = None # Store the image reference
  46. highestScore = 0
  47. highScoreName = ""
  48. punch_on = False
  49. current_dinger_y = 900
  50. def move_dinger_step(start_y, target_y, steps_left, total_steps):
  51. global current_dinger_y
  52. if steps_left > 0:
  53. # Calculate progress (0 to 1)
  54. progress = (total_steps - steps_left) / total_steps
  55. # Calculate the current position directly between start and target
  56. new_y = start_y + (target_y - start_y) * progress
  57. # Update position
  58. current_dinger_y = new_y
  59. dinger.place_configure(y=int(new_y))
  60. # Schedule next step
  61. app.after(10, lambda: move_dinger_step(start_y, target_y, steps_left - 1, total_steps))
  62. else:
  63. # Ensure we end up exactly at target
  64. current_dinger_y = target_y
  65. dinger.place_configure(y=int(target_y))
  66. def update_dinger_position(final_score=None):
  67. global current_dinger_y
  68. start_y = current_dinger_y # Remember where we're starting from
  69. if final_score is None:
  70. target_y = 900 # Bottom position
  71. else:
  72. # Calculate target position based on score
  73. max_movement = 600
  74. score_threshold = 100
  75. # Calculate new position
  76. movement = (final_score / score_threshold) * max_movement
  77. movement = min(movement, max_movement)
  78. target_y = 900 - movement # Calculate from top position
  79. target_y = max(100, min(target_y, 900)) # Ensure within bounds
  80. # Start the animation
  81. move_dinger_step(start_y, target_y, 60, 60)
  82. def startPunch():
  83. global punch_on
  84. punch_on = True
  85. updateStatusLabel()
  86. update_dinger_position()
  87. print("STARTED PUNCH")
  88. def stopPunch():
  89. global punch_on
  90. punch_on = False
  91. updateStatusLabel()
  92. update_dinger_position(currentScore.get())
  93. if stop_sound:
  94. try:
  95. if 100.0<=currentScore.get():
  96. stop_sound.play()
  97. except Exception as e:
  98. print(f"Error playing sound: {str(e)}")
  99. print("STOPPED PUNCH")
  100. def resetPunch():
  101. global currentScore
  102. currentScore.set(0)
  103. update_dinger_position(0)
  104. def load_scores():
  105. global names, scores, highestScore, highScoreName
  106. try:
  107. with open("all_scores.txt", "r") as f:
  108. lines = f.readlines()
  109. for line in lines:
  110. line = line.strip()
  111. if line and "," in line: # Check if line is not empty and contains a comma
  112. try:
  113. name, score = line.split(",")
  114. score = float(score)
  115. names.append(name)
  116. scores.append(score)
  117. if score > highestScore:
  118. highestScore = score
  119. highScoreName = name
  120. except (ValueError, IndexError):
  121. # Skip malformed lines
  122. continue
  123. except FileNotFoundError:
  124. # Create the file if it doesn't exist
  125. with open("all_scores.txt", "w") as f:
  126. pass
  127. def save_new_score(name, score):
  128. with open("all_scores.txt", "a") as f:
  129. f.write(f"{name},{score}\n")
  130. def updateStatusLabel():
  131. global statusLabel
  132. if punch_on:
  133. statusLabel.configure(text="RECORDING", fg_color="green", text_color="white")
  134. else:
  135. statusLabel.configure(text="NOT RECORDING", fg_color="gray70", text_color="black")
  136. def submitScore():
  137. global highestScore, highScoreName
  138. # Get the name from the entry field
  139. entered_name = nameEntry.get().strip()
  140. if entered_name: # Only proceed if a name was entered
  141. current_score = currentScore.get()
  142. names.append(entered_name)
  143. scores.append(current_score)
  144. # Save the new score to file
  145. save_new_score(entered_name, current_score)
  146. if current_score > highestScore:
  147. highestScore = current_score
  148. highScoreName = entered_name
  149. # Clear the entry field after recording
  150. nameEntry.delete(0, 'end')
  151. # Refresh the leaderboard display
  152. placeLeaderboard()
  153. # Leaderboard & UI setup
  154. def placeLeaderboard():
  155. global nameEntry, enterButton, statusLabel, dinger, bell_image
  156. canvas.create_rectangle(0, 0, 500, 500, fill="grey20", width=2)
  157. # Get top 5 scores
  158. score_pairs = list(zip(names, scores))
  159. score_pairs.sort(key=lambda x: x[1], reverse=True)
  160. top_5_pairs = score_pairs[:5]
  161. intx = 0
  162. inty = 100
  163. for name, score in top_5_pairs:
  164. label1 = ctk.CTkLabel(width=124, height=39, text_color="white", text=name, font=("Arial", 15), master=app)
  165. label1.place(x=intx, y=inty)
  166. label2 = ctk.CTkLabel(width=124, height=39, text_color="white", text=f"{score:.2f}", font=("Arial", 15), master=app)
  167. label2.place(x=intx + 125, y=inty)
  168. inty += 40
  169. ctk.CTkLabel(width=200, height=30, text_color="white", text="Leaderboard", font=("Arial", 50), master=app).place(x=0, y=0)
  170. # Create status indicator
  171. statusLabel = ctk.CTkLabel(
  172. master=app,
  173. width=120,
  174. height=40,
  175. text="NOT RECORDING",
  176. fg_color="gray70",
  177. text_color="black",
  178. corner_radius=10,
  179. font=("Arial", 16)
  180. )
  181. statusLabel.place(x=50, y=450)
  182. ctk.CTkButton(master=app,
  183. width=120,
  184. height=80,
  185. text="START",
  186. fg_color="green",
  187. text_color="gray99",
  188. corner_radius=25,
  189. font=("Arial", 30),
  190. command=startPunch).place(x=50, y=500)
  191. ctk.CTkButton(master=app,
  192. width=120,
  193. height=80,
  194. text="STOP",
  195. fg_color="orange2",
  196. text_color="gray99",
  197. corner_radius=25,
  198. font=("Arial", 30),
  199. command=stopPunch).place(x=50, y=600)
  200. ctk.CTkButton(master=app,
  201. width=120,
  202. height=80,
  203. text="RESET",
  204. fg_color="red1",
  205. text_color="gray99",
  206. corner_radius=25,
  207. font=("Arial", 30),
  208. command=resetPunch).place(x=50, y=700)
  209. ctk.CTkLabel(width=50, height=20, text_color="white", text="Current Score", font=("Arial", 50), master=app).place(x=500, y=0)
  210. ctk.CTkLabel(width=50, height=20, text_color="white", textvariable=currentScore, font=("Arial", 50), master=app).place(x=725, y=100)
  211. nameEntry = ctk.CTkEntry(width=300, height=50, master=app, font=("Arial", 50))
  212. nameEntry.place(x=500, y=200)
  213. enterButton = ctk.CTkButton(width=50, height=75, text="ENTER", fg_color="green", font=("Arial", 20),
  214. text_color="gray99", master=app, command=submitScore)
  215. enterButton.place(x=825, y=205)
  216. # Load and display the bell image
  217. bell_image = Image.open("./BellDrawing.png")
  218. photo = ImageTk.PhotoImage(bell_image)
  219. image_label = ctk.CTkLabel(image=photo, text="", width=10, master=app)
  220. image_label.image = photo # Keep a reference to prevent garbage collection
  221. image_label.place(x=1000, y=0)
  222. # Create the dinger after the image (so it appears on top)
  223. dinger = ctk.CTkLabel(width=40, height=30, text="", fg_color="gray77", master=app)
  224. dinger.place(x=1205, y=900)
  225. # Start updating dinger position
  226. update_dinger_position()
  227. # BLE thread setup
  228. def ble_thread_func():
  229. import asyncio
  230. loop = asyncio.new_event_loop()
  231. asyncio.set_event_loop(loop)
  232. loop.run_until_complete(dobluetooth.getData())
  233. def start_ble_thread():
  234. thread = threading.Thread(target=ble_thread_func, daemon=True)
  235. thread.start()
  236. def data_loop():
  237. last_data = None
  238. last_value = 0.00
  239. while True:
  240. bledata = dobluetooth.getDataArr()
  241. if punch_on:
  242. if last_data == None:
  243. last_data = bledata
  244. current_x_accel = bledata[3]
  245. current_y_accel = bledata[4]
  246. last_x_accel = last_data[3]
  247. last_y_accel = last_data[4]
  248. value = math.sqrt((current_x_accel - last_y_accel)**2 + ((current_y_accel - last_y_accel)**2))/4
  249. print(value)
  250. if(value < 0.4):
  251. pass
  252. else:
  253. if(value * 100 > currentScore.get()):
  254. app.after(0, lambda val=value: currentScore.set(int(value*100)))
  255. """
  256. if(last_value - value > 10.00):
  257. app.after(0, lambda val=value: currentScore.set(currentScore.get() + int(val)))
  258. else:
  259. if(value - last_value > 10.00):
  260. if(value == last_value):
  261. app.after(0, lambda val=value: currentScore.set(currentScore.get() + int(val*10)))
  262. else:
  263. app.after(0, lambda val=value: currentScore.set(currentScore.get() + int(val)))
  264. else:
  265. app.after(0, lambda val=value: currentScore.set(currentScore.get() + int(val*25)))
  266. """
  267. app.after(0, update_dinger_position)
  268. last_data = bledata
  269. last_value = value
  270. time.sleep(0.1)
  271. def start_data_loop_thread():
  272. thread = threading.Thread(target=data_loop, daemon=True)
  273. thread.start()
  274. # Run UI setup
  275. load_scores() # Load existing scores before setting up UI
  276. placeLeaderboard()
  277. # Start threads after UI initializes
  278. app.after(500, start_ble_thread)
  279. app.after(1000, start_data_loop_thread)
  280. app.mainloop()