You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python实现多输入同步显示与游戏状态实时更新终端交互问题

Flask 终端程序布局与输入优化方案

问题概述

需要实现一个终端程序,核心要求如下:

  • 终端第1、2行固定显示两个输入提示,第4行显示游戏状态(烧瓶布局)
  • 完成两次输入后,游戏状态覆盖更新而非换行
  • 输入第一行内容后,光标自动下移到第二行,下方游戏状态保持可见

当前代码存在两个核心问题:

  1. 输入函数在游戏状态打印后调用,无法实现输入提示与状态同时显示
  2. 使用print(out_string, end='\r', flush=True)无法实现多行状态的覆盖更新

核心解决方案

1. 用ANSI转义序列控制终端布局

现代终端支持ANSI转义码实现光标定位、内容清除等操作,这是实现固定布局的关键:

  • \033[n;mH: 将光标移动到第n行第m列
  • \033[K: 清除当前行从光标位置到末尾的内容
  • \033[J: 清除光标位置到屏幕末尾的所有内容
  • \033[?25l/\033[?25h: 隐藏/显示光标(可选,优化输入体验)

2. 重构输入流程:固定提示位置,同步显示状态

  • 先打印固定位置的输入提示,再渲染游戏状态
  • 输入时通过光标定位回到对应输入行,避免打乱整体布局

3. 修复多行状态的覆盖更新

单行\r只能覆盖当前行,多行状态需要先回到状态区域的起始行,清除旧内容后再打印新状态,确保完全覆盖。

修改后的完整代码

全局常量(按需调整)

FLASK_COUNT = 4  # 烧瓶数量
MAX_CHEM = 4     # 烧瓶最大容量

Flask类(补充必要逻辑)

class Flask:
    def __init__(self):
        self.chemicals = []
        self.sealed = False
    
    def is_full(self):
        return len(self.chemicals) >= MAX_CHEM
    
    def pour(self, target_flask):
        if not self.chemicals or target_flask.is_full():
            return
        target_flask.chemicals.append(self.chemicals.pop())
    
    def can_seal(self):
        # 示例逻辑:所有化学物质相同时可密封
        if not self.chemicals:
            return False
        return all(c == self.chemicals[0] for c in self.chemicals)

输入处理函数(重构后)

def get_valid_input(prompt, min_val, max_val, origin=None, flasks=None):
    # 定位光标到对应输入行
    cursor_pos = "\033[1;1H" if origin is None else "\033[2;1H"
    print(cursor_pos, end="")
    
    # 清除当前行旧内容
    print("\033[K", end="")
    user_input = input(prompt)
    
    # 验证输入格式
    while not user_input.isdigit():
        print("\033[K", end="")
        print("Invalid input. ", end="")
        user_input = input(prompt)
    
    num_input = int(user_input)
    
    # 验证输入范围
    if num_input < min_val or num_input > max_val:
        print("\033[K", end="")
        print("Invalid input: Input out of range. ", end="")
        return get_valid_input(prompt, min_val, max_val, origin, flasks)
    
    # 目标输入额外校验
    if origin is not None:
        if num_input == origin:
            print("\033[K", end="")
            print("Target cannot be the same as the source. ", end="")
            return get_valid_input(prompt, min_val, max_val, origin, flasks)
        if flasks[num_input - 1].sealed:
            print("\033[K", end="")
            print("Cannot pour into a sealed flask. ", end="")
            return get_valid_input(prompt, min_val, max_val, origin, flasks)
        if flasks[num_input - 1].is_full():
            print("\033[K", end="")
            print("Target flask is full. ", end="")
            return get_valid_input(prompt, min_val, max_val, origin, flasks)
    
    return num_input

游戏状态渲染函数(重构后)

def render_flasks(flasks):
    flask_list = []
    
    # 生成单个烧瓶的字符串
    for i, flask in enumerate(flasks):
        chem_str = '+--+' + f'\n  {i + 1} '
        for chem in flask.chemicals[::-1]:
            chem_str = f'|{chem}|\n' + chem_str
        
        if flask.sealed:
            chem_str = '+--+\n' + chem_str
        else:
            diff = MAX_CHEM - len(flask.chemicals) + 1
            while diff > 0:
                chem_str = '|  |\n' + chem_str
                diff -= 1
        
        flask_list.append(chem_str)
    
    # 拼接多行烧瓶字符串
    lines_dict = {}
    for flask_str in flask_list:
        lines = flask_str.split('\n')
        for idx, line in enumerate(lines):
            lines_dict[idx] = lines_dict.get(idx, []) + [line]
    
    out_list = ['  '.join(lines_dict[idx]) for idx in lines_dict]
    out_string = '\n'.join(out_list[::-1])
    
    # 定位到第4行,清除旧状态后打印新状态
    print("\033[4;1H", end="")
    print("\033[J", end="")
    print(out_string, flush=True)

主函数(重构后)

def main():
    # 初始化烧瓶
    flasks = [Flask() for _ in range(FLASK_COUNT)]
    # 示例初始数据
    flasks[0].chemicals = ['R', 'R', 'B']
    flasks[1].chemicals = ['B', 'B', 'R']
    flasks[2].chemicals = ['G', 'G']
    
    gameover = False
    print("\033[?25l", end="")  # 隐藏光标(可选)
    
    while not gameover:
        # 打印固定输入提示
        print("\033[1;1H", end="")
        print("\033[K", end="")
        print("Select the source flask: ", end="")
        
        print("\033[2;1H", end="")
        print("\033[K", end="")
        print("Select the target flask: ", end="")
        
        # 渲染游戏状态
        render_flasks(flasks)
        
        # 获取输入
        origin = get_valid_input("", 1, FLASK_COUNT)
        target = get_valid_input("", 1, FLASK_COUNT, origin, flasks)
        
        # 执行倾倒操作
        oflask = flasks[origin - 1]
        tflask = flasks[target - 1]
        oflask.pour(tflask)
        
        # 密封逻辑
        if tflask.can_seal():
            tflask.sealed = True
            # 在状态上方显示提示
            print("\033[3;1H", end="")
            print("\033[K", end="")
            print("Sealed target flask", flush=True)
    
    print("\033[?25h", end="")  # 恢复光标

if __name__ == "__main__":
    main()

关键说明

  • ANSI兼容性:大部分现代终端(Linux/macOS终端、Windows Terminal)都支持ANSI转义码,旧版Windows Cmd需先启用虚拟终端模式
  • 输入行处理:每次输入前清除当前行内容,避免错误提示残留
  • 状态更新:回到第4行后清除下方所有内容,确保新状态完全覆盖旧内容

内容的提问来源于stack exchange,提问作者Abd_Chees e

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 22:46:10