forked from quentinyong/python-experiments
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtyping-tutor-qwerty.py
More file actions
233 lines (195 loc) · 8.86 KB
/
Copy pathtyping-tutor-qwerty.py
File metadata and controls
233 lines (195 loc) · 8.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import tkinter as tk
import random
# QWERTY Row and Hand Groupings
qwerty_rows = {
"home_left": "asdfg", # Include G as shared key
"home_right": "hjkl", # Include H as shared key
"top_left": "qwert",
"top_right": "yuiop",
"bottom_left": "zxcv",
"bottom_right": "bnm",
}
# Function to generate random letters from a specific group
def generate_random_letters(group, length=5):
return "".join(random.choices(qwerty_rows[group], k=length)) # Generate a random sequence of letters
class TypingTutorGame:
def __init__(self, root):
self.root = root
self.root.title("Typing Tutor Game (QWERTY Rows and Hands)")
# Game variables
self.current_level = 1
self.score = 0
self.lives = 5
self.phrase_speed = 3000 # Initial time interval for phrase moving (ms)
self.speed_increase_threshold = 5 # Threshold for increasing speed
self.speed_increment = 200 # Decrease in phrase_speed (ms) for each threshold reached
self.minimum_speed = 1000 # Minimum allowed speed (ms)
self.game_running = True
self.phrase_y = 50
# Level mapping to QWERTY groups
self.level_to_group = {
1: "home_left",
2: "home_right",
3: ["home_left", "home_right"], # Intermixed Home Row
4: "top_left",
5: "top_right",
6: ["top_left", "top_right"], # Intermixed Top Row
7: "bottom_left",
8: "bottom_right",
9: ["bottom_left", "bottom_right"], # Intermixed Bottom Row
10: ["home_left", "home_right", "top_left", "top_right", "bottom_left", "bottom_right"], # Mixed Rows
}
# Generate the first phrase/letter/word based on level
self.phrase = self.generate_phrase()
self.current_index = 0 # To track the user's progress within the phrase
# GUI setup
self.canvas = tk.Canvas(root, width=800, height=400, bg="white")
self.canvas.pack()
self.score_label = tk.Label(root, text=f"Score: {self.score}", font=("Helvetica", 16))
self.score_label.pack(side=tk.LEFT, padx=10)
self.lives_label = tk.Label(root, text=f"Lives: {self.lives}", font=("Helvetica", 16))
self.lives_label.pack(side=tk.RIGHT, padx=10)
self.entry = tk.Entry(root, font=("Helvetica", 16))
self.entry.pack(pady=10)
self.entry.bind("<KeyRelease>", self.check_typing)
# Display the first phrase
self.phrase_text_ids = [] # List of text item IDs for clearing
self.render_phrase()
# Start the game
self.move_phrase()
def generate_phrase(self):
"""Generate a phrase/sequence based on the current level."""
group = self.level_to_group[self.current_level]
if isinstance(group, list): # Intermixed levels
combined_group = "".join(qwerty_rows[g] for g in group)
return "".join(random.choices(combined_group, k=5))
else:
return generate_random_letters(group)
def update_level(self):
"""Update the level based on the player's score."""
if self.score < 10:
self.current_level = 1 # Home Row Left Hand
elif 10 <= self.score < 20:
self.current_level = 2 # Home Row Right Hand
elif 20 <= self.score < 30:
self.current_level = 3 # Intermixed Home Row
elif 30 <= self.score < 40:
self.current_level = 4 # Top Row Left Hand
elif 40 <= self.score < 50:
self.current_level = 5 # Top Row Right Hand
elif 50 <= self.score < 60:
self.current_level = 6 # Intermixed Top Row
elif 60 <= self.score < 70:
self.current_level = 7 # Bottom Row Left Hand
elif 70 <= self.score < 80:
self.current_level = 8 # Bottom Row Right Hand
elif 80 <= self.score < 90:
self.current_level = 9 # Intermixed Bottom Row
else:
self.current_level = 10 # Mixed Rows
def render_phrase(self):
"""Render the phrase with color-coded feedback."""
self.clear_phrase() # Clear the previous rendering
# Break down the phrase
correct_part = self.phrase[:self.current_index]
remaining_part = self.phrase[self.current_index:]
# Render correct characters
x = 50 # Starting x-coordinate
for char in correct_part:
text_id = self.canvas.create_text(x, self.phrase_y, text=char, font=("Helvetica", 16), fill="green", anchor="w")
self.phrase_text_ids.append(text_id)
x += self.canvas.bbox(text_id)[2] - self.canvas.bbox(text_id)[0]
# Render remaining characters
for char in remaining_part:
text_id = self.canvas.create_text(x, self.phrase_y, text=char, font=("Helvetica", 16), fill="black", anchor="w")
self.phrase_text_ids.append(text_id)
x += self.canvas.bbox(text_id)[2] - self.canvas.bbox(text_id)[0]
def clear_phrase(self):
"""Clear the previously rendered phrase from the canvas."""
for text_id in self.phrase_text_ids:
self.canvas.delete(text_id)
self.phrase_text_ids = []
def move_phrase(self):
if not self.game_running:
return
# Move the phrase down
self.phrase_y += 5
self.render_phrase()
# Check if the phrase reached the bottom
if self.phrase_y > 400:
self.lives -= 1
self.update_lives()
self.reset_phrase()
# Schedule the next move
self.root.after(self.phrase_speed // 10, self.move_phrase)
def check_typing(self, event):
if not self.game_running:
return
typed_text = self.entry.get()
# Check the current character
if len(typed_text) > 0:
current_char = typed_text[-1] # The last character typed
if current_char == self.phrase[self.current_index]:
# Correct character
self.current_index += 1
self.entry.delete(0, tk.END) # Clear the input box
if self.current_index == len(self.phrase):
# Phrase completed
self.score += 1
self.update_score()
self.increase_speed()
self.update_level()
self.reset_phrase()
else:
# Incorrect character
self.render_phrase_with_error(current_char)
def render_phrase_with_error(self, incorrect_char):
"""Render the phrase with the incorrect character highlighted."""
self.clear_phrase()
# Break down the phrase
correct_part = self.phrase[:self.current_index]
remaining_part = self.phrase[self.current_index:]
# Render correct characters
x = 50 # Starting x-coordinate
for char in correct_part:
text_id = self.canvas.create_text(x, self.phrase_y, text=char, font=("Helvetica", 16), fill="green", anchor="w")
self.phrase_text_ids.append(text_id)
x += self.canvas.bbox(text_id)[2] - self.canvas.bbox(text_id)[0]
# Render the next character in red if incorrect
if len(remaining_part) > 0:
text_id = self.canvas.create_text(x, self.phrase_y, text=remaining_part[0], font=("Helvetica", 16), fill="red", anchor="w")
self.phrase_text_ids.append(text_id)
x += self.canvas.bbox(text_id)[2] - self.canvas.bbox(text_id)[0]
# Render the rest of the phrase
for char in remaining_part[1:]:
text_id = self.canvas.create_text(x, self.phrase_y, text=char, font=("Helvetica", 16), fill="black", anchor="w")
self.phrase_text_ids.append(text_id)
x += self.canvas.bbox(text_id)[2] - self.canvas.bbox(text_id)[0]
def increase_speed(self):
"""Increase the speed when the score reaches a multiple of the threshold."""
if self.score % self.speed_increase_threshold == 0:
self.phrase_speed = max(self.minimum_speed, self.phrase_speed - self.speed_increment)
def reset_phrase(self):
if self.lives <= 0:
self.game_over()
else:
self.phrase = self.generate_phrase()
self.current_index = 0
self.phrase_y = 50
self.entry.delete(0, tk.END)
self.render_phrase()
def update_score(self):
self.score_label.config(text=f"Score: {self.score}")
def update_lives(self):
self.lives_label.config(text=f"Lives: {self.lives}")
if self.lives <= 0:
self.game_over()
def game_over(self):
self.game_running = False
self.canvas.create_text(400, 200, text="Game Over!", font=("Helvetica", 32), fill="red")
self.entry.config(state=tk.DISABLED)
# Main application
if __name__ == "__main__":
root = tk.Tk()
game = TypingTutorGame(root)
root.mainloop()