diff --git a/Typing-Test b/Typing-Test new file mode 160000 index 0000000..8463ec6 --- /dev/null +++ b/Typing-Test @@ -0,0 +1 @@ +Subproject commit 8463ec60d837ef175dcdeaa12a7c084c7d776e4a diff --git a/typing_speed_test.py b/typing_speed_test.py index bccbae5..81c95a0 100644 --- a/typing_speed_test.py +++ b/typing_speed_test.py @@ -1,11 +1,16 @@ import customtkinter as ctk import random import time +import winsound +import json +import os +from datetime import datetime, timedelta ctk.set_appearance_mode("light") ctk.set_default_color_theme("blue") TEST_DURATION = 60 +DATA_FILE = "streak_data.json" SENTENCES = [ "Technology is the most effective way to change the world.", @@ -16,13 +21,37 @@ ] +# ====================== +# LOAD / SAVE DATA +# ====================== + +def load_data(): + if os.path.exists(DATA_FILE): + with open(DATA_FILE, "r") as f: + return json.load(f) + + return { + "daily_streak": 0, + "last_practice_date": "", + "improvement_streak": 0, + "personal_best_streak": 0, + "best_wpm": 0, + "last_wpm": 0 + } + + +def save_data(data): + with open(DATA_FILE, "w") as f: + json.dump(data, f) + + class TypingSpeedTest(ctk.CTk): def __init__(self): super().__init__() self.title("Typing Speed Test") - self.geometry("700x520") + self.geometry("720x580") self.current_sentence = "" self.start_time = None @@ -31,13 +60,15 @@ def __init__(self): self.paused = False self.countdown = 3 + self.data = load_data() + # TITLE self.title_label = ctk.CTkLabel( self, text="Typing Speed Test", font=("Helvetica", 30, "bold") ) - self.title_label.pack(pady=20) + self.title_label.pack(pady=15) # TIMER self.timer_label = ctk.CTkLabel( @@ -47,12 +78,11 @@ def __init__(self): ) self.timer_label.pack() - # SENTENCE FRAME (for better visibility) + # SENTENCE FRAME self.sentence_frame = ctk.CTkFrame(self, width=620, height=100) self.sentence_frame.pack(pady=20) self.sentence_frame.pack_propagate(False) - # SENTENCE LABEL self.sentence_label = ctk.CTkLabel( self.sentence_frame, text="Press Start to begin", @@ -72,19 +102,28 @@ def __init__(self): self.input_textbox.pack(pady=10) self.input_textbox.configure(state="disabled") - # RESULT + self.input_textbox.bind("", self.handle_typing) + + # RESULT LABEL self.result_label = ctk.CTkLabel( self, text="", - font=("Helvetica", 20, "bold") + font=("Helvetica", 18, "bold") ) self.result_label.pack(pady=10) + # STREAK LABEL + self.streak_label = ctk.CTkLabel( + self, + text=self.get_streak_text(), + font=("Helvetica", 16) + ) + self.streak_label.pack(pady=5) + # BUTTON FRAME self.button_frame = ctk.CTkFrame(self) self.button_frame.pack(pady=10) - # START BUTTON self.start_button = ctk.CTkButton( self.button_frame, text="Start Test", @@ -92,7 +131,6 @@ def __init__(self): ) self.start_button.grid(row=0, column=0, padx=10) - # PAUSE BUTTON self.pause_button = ctk.CTkButton( self.button_frame, text="Pause", @@ -101,24 +139,27 @@ def __init__(self): ) self.pause_button.grid(row=0, column=1, padx=10) - # RESULT BUTTON - self.result_button = ctk.CTkButton( - self.button_frame, - text="Check Result", - command=self.check_result, - state="disabled" + # ====================== + # STREAK TEXT + # ====================== + + def get_streak_text(self): + return ( + f"🔥 Daily Streak: {self.data['daily_streak']} days\n" + f"📈 Improvement Streak: {self.data['improvement_streak']}\n" + f"🏆 Personal Best Streak: {self.data['personal_best_streak']}\n" + f"⭐ Best WPM: {self.data['best_wpm']}" ) - self.result_button.grid(row=0, column=2, padx=10) # ====================== # START TEST # ====================== + def start_test(self): self.result_label.configure(text="") self.countdown = 3 self.input_textbox.configure(state="disabled") - self.start_button.configure(state="disabled") self.show_countdown() @@ -126,6 +167,7 @@ def start_test(self): # ====================== # COUNTDOWN # ====================== + def show_countdown(self): if self.countdown > 0: @@ -141,12 +183,12 @@ def show_countdown(self): else: self.sentence_label.configure(text="GO!") - self.after(800, self.begin_test) # ====================== # BEGIN TEST # ====================== + def begin_test(self): self.current_sentence = random.choice(SENTENCES) @@ -163,13 +205,13 @@ def begin_test(self): self.paused = False self.pause_button.configure(state="normal") - self.result_button.configure(state="normal") self.update_timer() # ====================== # TIMER # ====================== + def update_timer(self): if not self.timer_running: @@ -184,35 +226,96 @@ def update_timer(self): ) self.time_left -= 1 - self.after(1000, self.update_timer) else: - self.check_result() # ====================== - # PAUSE / RESUME + # PAUSE # ====================== + def toggle_pause(self): if not self.timer_running: return if not self.paused: - self.paused = True self.pause_button.configure(text="Resume") else: - self.paused = False self.pause_button.configure(text="Pause") self.update_timer() + # ====================== + # HANDLE TYPING + SOUND + # ====================== + + def handle_typing(self, event): + + if not self.timer_running: + return + + typed = self.input_textbox.get("1.0", "end-1c") + index = len(typed) + + if event.keysym == "BackSpace": + winsound.Beep(500, 40) + return + + if index <= len(self.current_sentence) and index > 0: + + expected = self.current_sentence[index-1] + + if typed[-1] == expected: + winsound.Beep(800, 30) + else: + winsound.Beep(300, 80) + + # Finish early if sentence completed + if typed.strip() == self.current_sentence.strip(): + self.check_result() + + # ====================== + # UPDATE STREAKS + # ====================== + + def update_streaks(self, wpm): + + today = datetime.now().date() + last_date = self.data["last_practice_date"] + + if last_date: + last_date = datetime.strptime(last_date, "%Y-%m-%d").date() + + if today == last_date + timedelta(days=1): + self.data["daily_streak"] += 1 + elif today != last_date: + self.data["daily_streak"] = 1 + else: + self.data["daily_streak"] = 1 + + self.data["last_practice_date"] = str(today) + + if wpm > self.data["last_wpm"]: + self.data["improvement_streak"] += 1 + else: + self.data["improvement_streak"] = 0 + + self.data["last_wpm"] = wpm + + if wpm > self.data["best_wpm"]: + self.data["best_wpm"] = wpm + self.data["personal_best_streak"] += 1 + + save_data(self.data) + # ====================== # RESULT # ====================== + def check_result(self): if not self.start_time: @@ -220,6 +323,8 @@ def check_result(self): self.timer_running = False + winsound.Beep(1200, 300) + typed_text = self.input_textbox.get("1.0", "end-1c") elapsed_time = time.time() - self.start_time @@ -231,10 +336,16 @@ def check_result(self): else: wpm = (chars / 5) / (elapsed_time / 60) + self.update_streaks(wpm) + self.result_label.configure( text=f"Typing Speed: {wpm:.2f} WPM" ) + self.streak_label.configure( + text=self.get_streak_text() + ) + self.input_textbox.configure(state="disabled") self.pause_button.configure(state="disabled") self.start_button.configure(state="normal")