6
\$\begingroup\$

This script allows you to practice your typing skills, as soon as you finish typing a new word appears for a non-stop typing experience. The code has in memory the top 100 words as per the Pareto Principle and Zipf's Law, of these, the top 10 are more common than the rest.

To start, type start.

Tested in Linux, Python 3.4 but should work in any version in any OS.

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk
import random

FONT = (None, 50)
WORDS = ['the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have', 'I', 'it', 'for', 'not', 'on', 'with', 'he', 'as', 'you', 'do', 'at', 'this', 'but', 'his', 'by', 'from', 'they', 'we', 'say', 'her', 'she', 'or', 'an', 'will', 'my', 'one', 'all', 'would', 'there', 'their', 'what', 'so', 'up', 'out', 'if', 'about', 'who', 'get', 'which', 'go', 'me', 'when', 'make', 'can', 'like', 'time', 'no', 'just', 'him', 'know', 'take', 'people', 'into', 'year', 'your', 'good', 'some', 'could', 'them', 'see', 'other', 'than', 'then', 'now', 'look', 'only', 'come', 'its', 'over', 'think', 'also', 'back', 'after', 'use', 'two', 'how', 'our', 'work', 'first', 'well', 'way', 'even', 'new', 'want', 'because', 'any', 'these', 'give', 'day', 'most', 'us']

new_word = lambda: random.choice(random.choice((WORDS[0:len(WORDS)//10], WORDS)))

root = tk.Tk()
root.title("Typing trainer")

text_to_copy = tk.Label(root, text = "start", font=FONT)
text_to_copy.pack()

typing_ground = tk.Entry(root, font=FONT)
typing_ground.pack()

points = tk.Label(root, text = "Score: 0", font=FONT)
points.pack()

def if_equal_delete_and_take_other_phrase(ev):
    if text_to_copy.cget("text") == typing_ground.get():
        typing_ground.delete(0, 'end')
        text_to_copy['text'] = new_word()
        points['text'] = "Score: " + str(int(points['text'].split(': ')[-1]) + 1)

root.bind('<Key>', if_equal_delete_and_take_other_phrase)

root.mainloop()
\$\endgroup\$
6
\$\begingroup\$

I love seeing functionality like this packed so concisely. Really nice job.

UX

When I ran your code, it ran fine, but a usability problem is that the text entry box does not have focus, you have to click on it to make it have the active focus/cursor. You should add this to your script:

typing_ground.focus_set()

I ran it with python and python3, both worked fine.

Another thing that came up is that it occasionally happens that the next word is the same as the previous word. You should add a re-selection of a new word until it is different, otherwise the experience is "odd" (the word stays the same but your entry widget clears... and when you're typing you are not really looking at the entry box, just the target word.... and since it does not change, you think you've made a mistake.

Variables

Your code uses text entry and display components as variables. This code makes me cringe:

points['text'] = "Score: " + str(int(points['text'].split(': ')[-1]) + 1)

Instead of having a score variable, you store the score in the partial text value of the score displaying widget. Really? You do the same thing with the text to copy. Currently your code is:

def if_equal_delete_and_take_other_phrase(ev):
    if text_to_copy.cget("text") == typing_ground.get():
        typing_ground.delete(0, 'end')
        text_to_copy['text'] = new_word()
        points['text'] = "Score: " + str(int(points['text'].split(': ')[-1]) + 1)

Would it really be so hard to do:

target = "start"
score = 0
def if_equal_delete_and_take_other_phrase(ev):
    global target
    global score
    if target == typing_ground.get():
        score = score + 1
        target = new_word()
        typing_ground.delete(0, 'end')
        text_to_copy['text'] = target
        points['text'] = "Score: " + str(score)

Lambda

The lambda here is simply gratuitous:

new_word = lambda: random.choice(random.choice((WORDS[0:len(WORDS)//10], WORDS)))

It is never used in an anonymous way, and really there's no use for this format at all. It makes the reader expect something "funny", and there's confusion when the only funny thing is the declaration above.

Talk about confusing.... why is the first 10% of the WORDS list more important than the next 10%? The first 10% of the words appear with significantly more frequency because you not only choose the random word from the array, but you choose to use either the first 10% only, or the whole thing. In other words, half of the choices only ever come from the first 10% of the words.

I cannot think of any reason for that, and it makes the code complicated, and it is a head scratcher, and as a consequence, the code maintainer (me) would be cursing your name and hexing your bytestrings. For all things sweet and dainty, at least leave a comment, please. As for me, I'll just rewrite that crap as:

def new_word():
    return random.choice(WORDS)

Hmmm.... let me add in the non-repeating word function too:

def new_word():
    while True:
        nword = random.choice(WORDS)
        if nword != target:
            return nword

Conclusion

I added a #!/usr/bin/python3 line too. It's nice to not have to type these things in linux at least.

The code I ended up with is:

#!/usr/bin/python3
try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk
import random

FONT = (None, 50)
WORDS = ['the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have',
         'I', 'it', 'for', 'not', 'on', 'with', 'he', 'as', 'you',
         'do', 'at', 'this', 'but', 'his', 'by', 'from', 'they',
         'we', 'say', 'her', 'she', 'or', 'an', 'will', 'my', 'one',
         'all', 'would', 'there', 'their', 'what', 'so', 'up', 'out',
         'if', 'about', 'who', 'get', 'which', 'go', 'me', 'when',
         'make', 'can', 'like', 'time', 'no', 'just', 'him', 'know',
         'take', 'people', 'into', 'year', 'your', 'good', 'some',
         'could', 'them', 'see', 'other', 'than', 'then', 'now',
         'look', 'only', 'come', 'its', 'over', 'think', 'also',
         'back', 'after', 'use', 'two', 'how', 'our', 'work',
         'first', 'well', 'way', 'even', 'new', 'want', 'because',
         'any', 'these', 'give', 'day', 'most', 'us']


target = "start"
score = 0

root = tk.Tk()
root.title("Typing trainer")

text_to_copy = tk.Label(root, text = target, font=FONT)
text_to_copy.pack()

typing_ground = tk.Entry(root, font=FONT)
typing_ground.pack()

points = tk.Label(root, text = "Score: " + str(score), font=FONT)
points.pack()

def new_word():
    while True:
        nword = random.choice(WORDS)
        if nword != target:
            return nword

def if_equal_delete_and_take_other_phrase(ev):
    global target
    global score
    if target == typing_ground.get():
        score = score + 1
        target = new_word()
        typing_ground.delete(0, 'end')
        text_to_copy['text'] = target
        points['text'] = "Score: " + str(score)

root.bind('<Key>', if_equal_delete_and_take_other_phrase)

typing_ground.focus_set()

root.mainloop()
\$\endgroup\$
  • \$\begingroup\$ I like this review, +1, typing_ground.focus_set() is a real user experience improvement and using lambda is confusing. About the top words, there is a reason for it. It is called Pareto principle, the top 10 words are used much more than to words from 10 -> 100, so I show them more often (indeed this should have been a comment). About the cringing points['text'] = "Score: " + str(int(points['text'].split(': ')[-1]) + 1) I preferred some trickiness over two global variables, as global state is a thing I really don't like. \$\endgroup\$ – Caridorc Sep 19 '15 at 9:31
  • \$\begingroup\$ Also, may I apply the improvements I see fit of your answer on my GitHub repo? \$\endgroup\$ – Caridorc Sep 19 '15 at 9:36
  • 1
    \$\begingroup\$ Of course you can apply the improvements - oh, and the top 10 words are different to the top 10%. currently you have 10% selected. \$\endgroup\$ – rolfl Sep 19 '15 at 11:19
  • \$\begingroup\$ yes, but 10% of 100 is 10 in this particular case \$\endgroup\$ – Caridorc Sep 19 '15 at 12:43
  • \$\begingroup\$ I never counted the words, but still, instead of doing the WORDS[0:len(WORDS)//10] you can do just WORDS[0:10] then. \$\endgroup\$ – rolfl Sep 19 '15 at 12:49
2
\$\begingroup\$

This line violates PEP8 limit of 79-80 characters per line.:

WORDS = ['the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have', 'I', 'it', 'for', 'not', 'on', 'with', 'he', 'as', 'you', 'do', 'at', 'this', 'but', 'his', 'by', 'from', 'they', 'we', 'say', 'her', 'she', 'or', 'an', 'will', 'my', 'one', 'all', 'would', 'there', 'their', 'what', 'so', 'up', 'out', 'if', 'about', 'who', 'get', 'which', 'go', 'me', 'when', 'make', 'can', 'like', 'time', 'no', 'just', 'him', 'know', 'take', 'people', 'into', 'year', 'your', 'good', 'some', 'could', 'them', 'see', 'other', 'than', 'then', 'now', 'look', 'only', 'come', 'its', 'over', 'think', 'also', 'back', 'after', 'use', 'two', 'how', 'our', 'work', 'first', 'well', 'way', 'even', 'new', 'want', 'because', 'any', 'these', 'give', 'day', 'most', 'us']

Preferably, you can style it in a fashion similar to this:

WORDS = [
    'the', 'be', 'to', 'of',
    'and', 'a', 'in', 'that',
    ...
]

Other than that, there's not really much else I can find that's wrong with this. If there's anything else that you want me to review, just mention it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.