Typing Speed Test in Python
Intermediate · Games · about 45 minutes to build · 114 lines
Type three sentences as fast as you can and see your speed in words per minute, your accuracy and the words you got wrong.
The program
Press Run and answer its questions in the box that appears under the code.
Open it in the full editor to save it, share it and keep building.
Sample run
What the program printed, and what was typed (in yellow).
Typing speed test
Type 3 sentences as fast and as accurately as you can.
Round 1 of 3. Type this sentence:
Python code is meant to be read by people as well as computers.
Press Enter to start the clock:
Type it here: Python code is meant to be read by people as well as computers.
Time: 14.4 seconds
Speed: 53 words per minute
Accuracy: 100%
Round 2 of 3. Type this sentence:
Practice a little every day and your speed will grow.
Press Enter to start the clock:
Type it here: Practise a litle every day and your speed will grow.
Time: 11.9 seconds
Speed: 52 words per minute
Accuracy: 96%
Mistake: "Practise" should be "Practice"
Mistake: "litle" should be "little"
Round 3 of 3. Type this sentence:
Small steps every day add up to big results.
Press Enter to start the clock:
Type it here: Small steps every day add to big results
Time: 9.3 seconds
Speed: 52 words per minute
Accuracy: 91%
Mistake: missed "up"
Mistake: "results" should be "results."
Summary
Average speed: 52 words per minute
Average accuracy: 96%
Play again? (y/n) maybe
Please type y or n: n
Goodbye.How it is built
1. Words per minute
Typing tests count every 5 characters as one word, so long words count for more than short ones. Dividing by the minutes taken gives the speed.
def words_per_minute(typed, seconds): """Speed in words per minute, where a word is 5 characters.""" return (len(typed) / CHARS_PER_WORD) / (seconds / 60)
2. Accuracy
SequenceMatcher lines the typed text up with the sentence, so one missing letter does not make every letter after it look wrong. The sizes of the matching blocks are added up and divided by the longer of the two lengths.
def accuracy(target, typed): """Percentage of characters that match, using difflib to line them up.""" matcher = SequenceMatcher(None, target, typed) matched = sum(block.size for block in matcher.get_matching_blocks()) return 100 * matched / max(len(target), len(typed))
3. Listing the mistakes
The same matcher works on lists of words. get_opcodes() describes how to turn one list into the other: replace means a mistyped word, delete a missed word and insert an extra one.
def word_mistakes(target, typed): """List the words that were mistyped, missed or added.""" want, got = target.split(), typed.split() mistakes = [] for tag, w1, w2, g1, g2 in SequenceMatcher(None, want, got).get_opcodes(): expected, actual = " ".join(want[w1:w2]), " ".join(got[g1:g2]) if tag == "replace": mistakes.append(f'"{actual}" should be "{expected}"') elif tag == "delete": mistakes.append(f'missed "{expected}"') elif tag == "insert": mistakes.append(f'extra "{actual}"') return mistakes
4. One timed round
time.perf_counter() is read when the player presses Enter to start and again when the answer arrives; the difference is the typing time. A round finished in under a second is too quick to be real typing, so it shows no speed.
def play_round(number, sentence): """Time one sentence; return (speed, accuracy), or None if skipped.""" print(f"\nRound {number} of {ROUNDS}. Type this sentence:\n") print(" " + sentence + "\n") input("Press Enter to start the clock: ") start = time.perf_counter() typed = input("Type it here: ").strip() seconds = time.perf_counter() - start if not typed: print("Nothing was typed, so this round is skipped.") return None score = accuracy(sentence, typed) if seconds < MIN_SECONDS: speed = None print(" Time: under 1 second, too quick to time") else: speed = words_per_minute(typed, seconds) print(f" Time: {seconds:.1f} seconds") print(f" Speed: {speed:.0f} words per minute") print(f" Accuracy: {score:.0f}%") mistakes = word_mistakes(sentence, typed) for mistake in mistakes[:MAX_MISTAKES_SHOWN]: print(f" Mistake: {mistake}") if len(mistakes) > MAX_MISTAKES_SHOWN: print(f" ...and {len(mistakes) - MAX_MISTAKES_SHOWN} more") return speed, score
5. The summary
Each round returns a (speed, accuracy) tuple, or None when nothing was typed. The summary averages the speeds that were timed and all the accuracies.
def show_summary(results): """Print the average speed and accuracy over the rounds that counted.""" print("\nSummary") if not results: print(" No rounds were typed.") return speeds = [speed for speed, _ in results if speed is not None] scores = [score for _, score in results] if speeds: average = sum(speeds) / len(speeds) print(f" Average speed: {average:.0f} words per minute") print(f" Average accuracy: {sum(scores) / len(scores):.0f}%")
Make it bigger
- Show net speed as well, which takes one word per minute off for each mistake.
- Save the best speed in a file and tell the player when they beat it.
- Add a hard mode with longer sentences full of capitals, digits and punctuation.
- Let the player choose how many rounds to play.
What it practises
- time.perf_counter
- difflib.SequenceMatcher
- random.sample
- returning tuples
- averages