Last active
April 16, 2023 02:35
-
-
Save juliusgeo/1f11f9d43f436a4a81238ac29b07005b to your computer and use it in GitHub Desktop.
A pretty nice typing test in 80 lines of Python.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os,select,tty,sys | |
from time import time | |
from functools import partial | |
# Need this because Unix-esque OSes are different from Windows. | |
try: | |
from msvcrt import getch as cf | |
except ImportError: | |
tty.setcbreak(sys.stdin.fileno()) | |
cf=partial(sys.stdin.read,1) | |
# Our class containing our funcs that format text. | |
class EscChars: | |
bold=lambda i:f"\033[1m{i}\033[0m" | |
boldred=lambda i:f"\033[1m\033[91m{i}\033[0m" | |
blink=lambda i:f"\033[4m{i}\033[0m" | |
# Read our fortune that we will be testing on. | |
fortune=os.popen(f"fortune {' '.join(sys.argv[1:])}").read().rstrip() | |
wrong_buffer,start_time, typed, fortune_idx="",0,[],0 | |
# The function that draws the thing at the bottom showing your current WPM. | |
banner=lambda typed_length, delta, num_mistakes:f"\nWPM: {(typed_length/5.0)/delta:.2f}, raw WPM: {((typed_length+num_mistakes)/5.0)/delta:.2f}, errors: {num_mistakes}" | |
# Format our rights and wrongs in the best way. | |
def formatted_output(wrong_buffer_str,fortune_str,fortune_idx=0): | |
text=''.join([EscChars.bold(s) if type==True else EscChars.boldred(s) for s,type in typed]) | |
if wrong_buffer_str: | |
text+=EscChars.boldred(wrong_buffer_str) | |
if fortune_idx<len(fortune): | |
text+=EscChars.blink(fortune_str[fortune_idx]) | |
text+=fortune_str[fortune_idx+1:] | |
return text | |
# We define a single string variable to hold the current "screen buffer" contents. This allows us to call | |
# `os.system("clear")` and then immediately print the string with no delay. This reduces the flickering effect on some | |
# terminals. | |
while fortune_idx<len(fortune): | |
i,_,_=select.select([sys.stdin],[],[],.1) | |
if i: | |
cycle="" | |
# If we're at the beginning (in the temporal or spatial sense), reset | |
if start_time==0 or fortune_idx == 0: | |
start_time=time() | |
cur=cf() | |
# Deletion logic | |
if ord(cur)==127: | |
if wrong_buffer: | |
wrong_buffer=wrong_buffer[:-1] | |
elif typed: | |
s,type=typed.pop(-1) | |
if len(s)>1: | |
typed.append((s[:-1],type)) | |
fortune_idx-=1 | |
# Correct addition logic | |
elif cur==fortune[fortune_idx]: | |
if wrong_buffer: | |
typed.append((wrong_buffer,False)) | |
wrong_buffer="" | |
typed.append((cur,True)) | |
fortune_idx+=1 | |
# Incorrect addition logic | |
else: | |
if cur.isprintable(): | |
fortune_idx+=1 | |
wrong_buffer+=cur | |
# Calculate all of our stats BEFORE calling "clear". | |
typed_length=sum([len(i[0]) if i[1]==True else 0 for i in typed])+len(wrong_buffer) | |
fortune_idx=max(fortune_idx,0) | |
num_mistakes=sum([len(i[0]) if i[1]==False else 0 for i in typed])+len(wrong_buffer) | |
cycle=formatted_output(wrong_buffer,fortune,fortune_idx) | |
delta=(time()-start_time)/60 | |
cycle+=banner(typed_length, delta, num_mistakes) | |
# Redraw the screen. | |
os.system("clear") | |
print(cycle) | |
os.system("clear") | |
print( | |
f"Summary:\n{banner(typed_length, delta, num_mistakes)}" | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It requires the

fortune
utility. If you're on MacOS you can dobrew install fortune
, otherwise if you're using a BSD variant you will likely already have the utility. If you're on Windows you're on your own. Here's a demo!