0

I've implemented a memory game where the user has to sort numbers in his head while a timer of 5 sec is running.

Please see code below:

    from random import randint
    from threading import Timer

    def timeout():
        print("Time over\n#####################\n")

    while True:
        list = []
        for i in range(5):
            list.append(randint(1000, 10000))

        t = Timer(5, timeout)
        t.start()

        print(list)
        print('[ 1  ,  2  ,  3  ,  4  ,  5  ]')

        solution = sorted(list)[2]
        print('please select the 3rd largest number in this row (1-5):')
        input_string = input()
        selection = int(input_string)

        if solution == list[selection - 1]:
            print("Your guess is correct\n")
        else:
            print("Your guess is wrong\n")

        t.join()

Here is the game interaction itself (please ignore the syntax highlighting):

USER@HOST:~/$ python3 memory_game.py
[8902, 8655, 1680, 6763, 4489]
[ 1  ,  2  ,  3  ,  4  ,  5  ]
please select the 3rd largest number in this row (1-5):
4
Your guess is correct

Time over
#####################

[5635, 3810, 1114, 5042, 1481]
[ 1  ,  2  ,  3  ,  4  ,  5  ]
please select the 3rd largest number in this row (1-5):
4
Your guess is wrong

Time over
#####################

[6111, 1430, 7999, 3352, 2742]
[ 1  ,  2  ,  3  ,  4  ,  5  ]
please select the 3rd largest number in this row (1-5):
23
Traceback (most recent call last):
  File "memory_game.py", line 24, in <module>
    if solution == list[selection - 1]:
IndexError: list index out of range
Time over
#####################

Can anybody help me with these things:
1. 'Time over' should only be written if the player needs more than 5 sec for the answer. If the player solves it in time the next challenge should appear silently.
2. If the player does not write any guess and presses 'Enter' the program terminates with error message: Traceback (most recent call last): File "memory_game.py", line 22, in selection = int(input_string) ValueError: invalid literal for int with base 10:''
3. If the player enters any random number the program quits with an 'Index out of range error' - I couldn't find out where to put try: except: Any help would be appreciated - Thanks!

1 Answers1

0

As for your questions:

  1. You can accomplish that with t.cancel() (stop the timer and do not call the function) instead of t.join() (wait until the thread has finished; this will ALWAYS result in a timeout, of course)
  2. (and 3.) These are basically the same -- put the query message and all input handling into a while loop, and break out of it once you know that the input is valid

...

  1. As an extra, the "time over" message wasn't really doing anything useful (e.g. you could still enter a valid answer after the time over occurred. I "fixed" that in a brute force way by making the program exit if the timeout is hit. Instead of doing that, you can also use a global variable to store whether the timeout was hit or not and handle that in your input accordingly (make sure to make it threadsafe using e.g. a mutex).

In general, it might be easier to turn around the structure of the program -- let the main thread handle the timeout and verification of the input, let the thread only handle the input (this way, it's easy to kill the thread to stop the input from being handled).

Of course, using the select module, one could implement this even nicer without threads (have one pipe that gets written to by a thread/timer, and the standard input, then select both for reading and it will block until either user input or the timeout occurs).

And maybe someone will post a nice asyncio-based solution? ;)

Here's the modified solution (modifying only as little as possible to get it to work, one could refactor other parts to make it nicer in general):

import random                                                                                                            
import threading                                                                                                         
import os                                                                                                                

def timeout():                                                                                                           
    print("Time over\n#####################\n")                                                                          
    # You can't use sys.exit() here, as this code is running in a thread                                                 
    # See also: https://stackoverflow.com/a/905224/1047040                                                               
    os._exit(0)                                                                                                          

while True:                                                                                                              
    list = []                                                                                                            
    for i in range(5):                                                                                                   
        list.append(random.randint(1000, 10000))                                                                         

    t = threading.Timer(5, timeout)                                                                                      
    t.start()                                                                                                            

    print(list)                                                                                                          
    print('[ 1  ,  2  ,  3  ,  4  ,  5  ]')                                                                              

    solution = sorted(list)[2]                                                                                           
    while True:                                                                                                          
        try:                                                                                                             
            print('please select the 3rd largest number in this row (1-5):')                                             
            input_string = input()                                                                                       
            selection = int(input_string)                                                                                
            chosen = list[selection - 1]                                                                                 
            # If we arrive here, the input was valid, break out of the loop                                              
            break                                                                                                        
        except Exception as e:                                                                                           
            # Tell the user that the input is wrong; feel free to remove "e"                                             
            # from the print output                                                                                      
            print('Invalid input:', e)                                                                                   

    if solution == chosen:                                                                                               
        print("Your guess is correct\n")                                                                                 
    else:                                                                                                                
        print("Your guess is wrong\n")                                                                                   

    # Make sure to cancel the thread, otherwise guessing correct or wrong                                                
    # will block the CLI interface and still write "time out" at the end                                                 
    t.cancel()                                                                                                           
Thomas Perl
  • 2,178
  • 23
  • 20