Using countdown() and input() at the same time

My level of expertise: I just learned the Python fundamentals from Eric Matthew’s Crash Course book.

I want to ask the user for input under the pressure of a countdown, but I don’t know how to have both run and displayed on the screen at the same time (whether these are on the same line or not)

I have asked ChatGPT, and it suggested I might use something called threading, but since it’s not able to generate working code with it, I don’t trust learning how threading(is that what I need?) works from it.

For reference, below is my logic for the countdown:

def countdown(duration):
    
    while duration > 0:
        print(duration, end="\r")
        duration-= 1
        time.sleep(1)

Oh, that ChatGPT magic. It reminds me of the Disney movie series Aladdin. There’s two genies, a good genie and a bad genie. The good genie is your friend and might do things for you even without being asked. You know he’s good because he’s blue and the quality of his voice acting. The bad genie will also grant your wishes, but he’ll take your words literally. When Abis Mal wished for the legendary sunken treasure ship of Gordumare, he was taken to the bottom of the ocean, to the location of the ship. In a similar way, ChatGPT gave you what you asked for, not what you want.

Multi-threading is a wonderful and exciting topic in programming, and most large applications employ it to take full advantage of modern hardware to achieve high performance. This is a topic that deserves a whole course of study, but this is what you should know about it: if your programming problem isn’t about performance, then threading most likely is not the answer.

Anyway, I took a shot at this problem, here’s what I came up with (edit: made better use of curses in this sample code):

import time
import curses
import string

current_window = curses.initscr()
current_window.nodelay(True)
curses.curs_set(0)

time_limit = 30

ms_per_second = 1000
nanoseconds_per_second = 1000000 * ms_per_second

start_time = time.monotonic_ns()
end_time = start_time + time_limit * nanoseconds_per_second 

input_buffer = ""
current_guess = ""

game_won = False

current_window.insstr(0, 0, "What is the answer to life, the universe, and everything?")
current_window.insstr(1, 0, "You have " + str(time_limit) + " seconds to guess")

while time.monotonic_ns() < end_time:
    time_elapsed = (time.monotonic_ns() - start_time) / nanoseconds_per_second

    time_left = time_limit - time_elapsed

    try:
        pressed_key = current_window.getch()

        if pressed_key == 0x7F: # backspace key
            input_buffer = input_buffer[:-1]
        elif pressed_key == 0x0a: # enter key
            current_guess = input_buffer
            input_buffer = ""
        elif pressed_key == 0x1b: #escape key
            break
        else:
            key_chr = chr(pressed_key)
            if key_chr in string.printable:
                input_buffer += chr(pressed_key)
    except:
        pass

    if current_guess != "":
        if current_guess == "42":
            game_won = True
            break
        else:
           current_window.move(3,0)
           current_window.clrtoeol()
           current_window.insstr(3,0, "Your guess of " + current_guess + " was incorrect")

    current_window.move(2, 0)
    current_window.clrtoeol()
    current_window.insstr(2, 0, "Time left: " + str(int(time_left)) + " seconds. Your guess: " + str(input_buffer) + "_")

curses.endwin()

if game_won:
    print("You win!")
else:
    print("Better luck next time!")

1 Like

This problem is a little tricky for two reasons: You need to wait for multiple things at once (the input and the countdown timer ticks) and you need to update multiple things displayed on the screen.

It’s much easier if you’re building a graphical user interface with an event loop that helps you with these things. For example, with Python’s built-in Tkinter library:

import tkinter as tk
from tkinter.messagebox import showinfo
from types import SimpleNamespace

def on_submit():
    state.answer = entry.get()
    button["state"] = "disabled"
    root.after_cancel(state.timer_id)
    showinfo(parent=root, message="Your answer: " + state.answer)

def on_timer():
    label["text"] = f"Time remaining: {state.countdown} seconds"
    if state.countdown == 0:
        showinfo(parent=root, message="Time's up!")
    else:
        state.countdown -= 1
        state.timer_id = root.after(1000, on_timer)

state = SimpleNamespace(answer=None, countdown=10, timer_id=None)

root = tk.Tk()
entry = tk.Entry(root)
button = tk.Button(root, text="Submit", command=on_submit)
label = tk.Label(root)

entry.grid(row=0)
button.grid(row=0, column=1)
label.grid(columnspan=2)

on_timer()
entry.focus()
root.mainloop()
3 Likes