一次性打印多行输入后跟字符串,无需等待“Enter”提示

-2 投票
1 回答
54 浏览
提问于 2025-04-14 15:23

我需要写一个简单的Python程序,它可以在终端的第一行和第二行显示用户输入的内容,然后在第四行显示一个输出字符串,这个输出字符串会在两个输入都输入完后更新。当用户输入第一个内容时,光标会直接跳到第二个输入,下面的所有文本都要能看得见。一旦输入了第二个内容,输出字符串就会更新,而不是在新的一行打印。它的样子大概是这样的:

第一行显示“魔法瓶游戏”,第三行是选择源瓶的输入,第四行是选择“目标瓶”的输入,第五行是错误信息(如果输入无效),还有一个多行字符串显示当前游戏状态

现在输入的内容是在游戏状态打印后调用的,但作业要求它们和游戏状态同时提示,并且要在各自的行上输入(第一个输入在它的行上,第二个输入在下一行)。

输入函数看起来是这样的。我还没有处理错误信息的部分,所以现在只是简单打印:

def origin_input():
    origin = input('Select the source flask: ')
    while not origin.isdigit():
        print('Invalid input.')
        origin = input('Select the source flask: ')

    origin = int(origin)

    if origin > FLASK_COUNT or origin < 1:
        print('Invalid input')

        return origin_input()
    
    return origin

def target_input(origin, flasks):
    target = input('Select the target flask: ')
    while not target.isdigit():
        print('Invalid input.')
        target = input('Select the target flask: ')

    target = int(target)
    if target > FLASK_COUNT or target < 1:
        print('Invalid input: Input out of range.')

        return target_input(origin, flasks)
    
    if target == origin:
        print('Target cannot be the same as the source.')
        return target_input(origin, flasks)
    
    if flasks[target - 1].sealed:  # accounts for 0 index
        print('Cannot pour into a sealed flask.')
        return target_input(origin, flasks)
    
    if flasks[target - 1].is_full():
        print('Target flask is full.')
        return target_input(origin, flasks)
    
    return target

输出字符串生成器是这样的。尽管在最后用了print(out_string, end='\r', flush=True),它还是在新的一行打印,所以我还在想办法让它正常工作:

def get_flask_str(flasks):
    print('\n')

    flask_list = []

    # generate flask strings
    for i, flask in enumerate(flasks):
        chem_str = '+--+' + f'\n  {i + 1} '
        for chem in flask.chemicals[::-1]:  # go backwards b/c the top is first in the list
            
            chem_str = f'|{chem}|\n' + chem_str

        if flask.sealed:
            chem_str = '+--+\n' + chem_str
            continue

        elif len(flask.chemicals) <= MAX_CHEM:  # only if flask is not completely full
            diff = MAX_CHEM - len(flask.chemicals) + 1  # account for extra space
            while diff > 0:
                chem_str = '|  |\n' + chem_str
                diff -= 1

        flask_list.append(chem_str)
        
    # concat multiline strings:
    lines_dict = {}

    for flask_string in flask_list:
        lines = flask_string.split('\n')
        for i, line in enumerate(lines):
            if i not in lines_dict:
                lines_dict[i] = [line]
            
            else:
                lines_dict[i].append(line)

    out_list = []
    for i in lines_dict:
        line = lines_dict[i]
        out_list.append('  '.join(line))

    out_string = ''
    for string in out_list[::-1]:  # flip right-side up
        out_string = string + f'\n{out_string}'
    
    print(out_string, end='\r', flush=True)

这是在主函数中调用这些函数的方式:

gameover = False
    while not gameover:  # no gameover clause for now
        get_flask_str(flasks)
        origin = origin_input()
        target = target_input(origin, flasks)

        oflask = flasks[origin - 1]
        tflask = flasks[target - 1]

        oflask.pour(tflask)
        if tflask.can_seal():
            print('Sealed target flask')  # temporary line

这里是当前的输出样本:

当前游戏状态被打印,接着是下一行提示输入源瓶,然后是再下一行提示输入目标瓶,最后是一行空行,之后是更新后的游戏状态

我尝试过使用没有任何参数的单个输入命令,但显然这不行。我不太确定如何解决Python在输入完成之前就等待输入的问题。

1 个回答

0

这是我决定采用的解决方案:在现实情况下,更好的方法是使用像tkinter这样的图形用户界面库,但这个项目要求输出必须在终端中进行。

通过使用ANSI转义字符,可以利用os模块来控制光标的位置。

在程序的开头,先在终端中初始化ANSI转义字符:

os.system("")

你还可以定义一个包含各种ANSI转义字符的字典:

ANSI = {
"RED": "\033[31m",
"GREEN": "\033[32m",
"BLUE": "\033[34m",
"HRED": "\033[41m",
"HGREEN": "\033[42m",
"HBLUE": "\033[44m",
"UNDERLINE": "\033[4m",
"RESET": "\033[0m",
"CLEARLINE": "\033[0K"
}

然后,定义一些函数来清屏、在特定的x和y位置打印内容,以及将光标移动到特定的x和y位置:

def cl():
    
    if os.name == "nt":  # windows
        os.system("cls")
    else:  # unix systems
        os.system("clear")

def loc_print(x, y, text):
    print (f"\033[{y};{x}H{text}")

def loc_cursor(x, y):
    print(f"\033[{y};{x}H", end='')

在我的情况下,这三个功能足以满足我在终端中的需求。请原谅我糟糕的代码:

gameover = False
err_msg = ''
while not gameover:
    cl()
    game_state = get_flask_str(flasks)
    loc_print(0, 0, 'Magical Flask Game')
    loc_print(0, 3, 'Select source Flask: ')
    loc_print(0, 4, 'Select target Flask: ')
    loc_print(0, 5, err_msg)
    loc_print(0, 6, game_state)

    loc_cursor(22, 3)
    origin = input()

    err_msg = origin_valid(origin)
    if err_msg != '':
        continue

    origin = int(origin)
    cl()
    loc_print(0, 0, 'Magical Flask Game')
    loc_print(0, 3, f'Select source Flask: {origin}')
    loc_print(0, 4, 'Select target Flask: ')
    loc_print(0, 5, '')
    loc_print(0, 6, game_state)

    loc_cursor(22, 4)
    target = input()

    err_msg = target_valid(target, origin, flasks)
    while err_msg != '':

        cl()
        loc_print(0, 0, 'Magical Flask Game')
        loc_print(0, 3, f'Select source Flask: {origin}')
        loc_print(0, 4, 'Select target Flask: ')
        loc_print(0, 5, err_msg)
        loc_print(0, 6, game_state)

        loc_cursor(22, 4)
        target = input()
        err_msg = target_valid(target, origin, flasks)

    target = int(target)
        

    oflask = flasks[origin - 1]
    tflask = flasks[target - 1]

    oflask.pour(tflask)
    if tflask.can_seal():
        err_msg = 'Sealed target flask'

    if check_win(flasks):
        cl()
            
        game_state = get_flask_str(flasks)
        loc_print(0, 0, 'Magical Flask Game')
        loc_print(0, 3, f'Select source Flask: {origin}')
        loc_print(0, 4, f'Select target Flask: {target}')
        loc_print(0, 5, '')
        loc_print(0, 6, game_state)
        loc_print(0, 13, 'You Win')
        gameover = True

The loc_cursor(x, y)函数可以改变光标的位置,以及在这个位置上写入的内容。虽然这段代码确实需要一些整理,但它正好实现了我想要的效果。

撰写回答