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

Python终端UI游戏:如何优雅修改指定位置打印字符

终端UI游戏菜单的光标(¤)移动优化方案

需求说明

我正在开发一款终端UI游戏,现有Python代码实现了基础菜单,但希望实现符号¤在菜单选项前的移动效果(如示例所示),不想用清屏重绘的 brute-force方式,想找类似BASIC中直接设置网格特定位置字符的实现方法,暂不想使用tkinter,寻求其他优雅方案。

现有基础菜单代码:

from ast import Pass
from re import findall
from time import sleep
from ScrFX import invalidOption
from os import system
from getkey import getkey, keys


def main_menu():
    hobo = "**********************************************\n" #ho horizontal, bo border
    vebo = "**                                          **\n" #ve vertical, bo border
    op1 =  "**             1) Start Game                **\n"
    op2 =  "**             2) Load  Game                **\n"
    op3 =  "**             3) Quit  Game                **\n"
    s = "\n"
    mainmenuscreen = (hobo+(vebo*5)+op1+vebo+op2+vebo+op3+vebo+(vebo*5)+hobo)
    print(mainmenuscreen)
    print("Please choose an option\n")
    def userkey():
        usse = getkey() #us-er, se-lection
        if usse == '1':
            print("Start game")
        elif usse == '2':
            print("load game")
        elif usse == '3':
            system('cls')
            print("Goodbye!")
            quit()
        elif usse == '0':
            print("Secret Menu yay)")
        else:
            invalidOption()
            system('cls')
            print(mainmenuscreen)
            userkey()
        return(usse)
    userkey()

main_menu()

期望的光标效果示例:

op1 =  "**             1) Start Game                **\n"
op2 =  "**       ¤     2) Load  Game                **\n"
op3 =  "**             3) Quit  Game                **\n"

解决方案

方案1:原生ANSI转义序列(无需额外库)

终端支持ANSI转义序列,可以直接控制光标位置并修改指定位置的字符,无需清屏重绘。核心是利用\033[<行>;<列>H移动光标到目标位置,再打印字符覆盖原有内容。

修改后的代码示例:

from time import sleep
from os import system
from getkey import getkey, keys

def main_menu():
    hobo = "**********************************************\n"
    vebo = "**                                          **\n"
    op1 = "**             1) Start Game                **\n"
    op2 = "**             2) Load  Game                **\n"
    op3 = "**             3) Quit  Game                **\n"
    
    mainmenuscreen = hobo + (vebo*5) + op1 + vebo + op2 + vebo + op3 + vebo + (vebo*5) + hobo
    print(mainmenuscreen)
    print("Please choose an option\n")

    # 定义每个选项对应的光标位置(行/列从1开始计数)
    option_positions = {
        '1': (7, 10),  # op1的¤位置
        '2': (9, 10),  # op2的¤位置
        '3': (11, 10)  # op3的¤位置
    }

    current_selected = '1'
    # 初始绘制光标
    print(f"\033[{option_positions[current_selected][0]};{option_positions[current_selected][1]}H¤", end='', flush=True)

    def userkey():
        nonlocal current_selected
        usse = getkey()
        # 清除当前选中项的光标
        print(f"\033[{option_positions[current_selected][0]};{option_positions[current_selected][1]}H ", end='', flush=True)
        
        if usse in ['1', '2', '3']:
            current_selected = usse
            # 在新选中项绘制光标
            print(f"\033[{option_positions[current_selected][0]};{option_positions[current_selected][1]}H¤", end='', flush=True)
            # 执行对应操作
            if usse == '1':
                print("\nStart game")
            elif usse == '2':
                print("\nLoad game")
            elif usse == '3':
                system('cls')
                print("Goodbye!")
                quit()
        elif usse == '0':
            print("\nSecret Menu yay!")
        elif usse == keys.UP:
            # 上方向键切换选项
            current_selected = '3' if current_selected == '1' else str(int(current_selected)-1)
            print(f"\033[{option_positions[current_selected][0]};{option_positions[current_selected][1]}H¤", end='', flush=True)
        elif usse == keys.DOWN:
            # 下方向键切换选项
            current_selected = '1' if current_selected == '3' else str(int(current_selected)+1)
            print(f"\033[{option_positions[current_selected][0]};{option_positions[current_selected][1]}H¤", end='', flush=True)
        else:
            # 无效输入,恢复光标位置
            print(f"\033[{option_positions[current_selected][0]};{option_positions[current_selected][1]}H¤", end='', flush=True)
        userkey()
    
    userkey()

main_menu()

方案2:使用标准库curses(专业终端UI控制)

Python内置的curses库专门用于终端UI开发,无需额外安装,能直接管理窗口、光标位置和键盘输入,比手动编写ANSI序列更稳定、易维护。

简单实现示例:

import curses

def main_menu(stdscr):
    # 清除屏幕
    stdscr.clear()
    # 获取屏幕尺寸
    height, width = stdscr.getmaxyx()

    # 绘制菜单边框和内容
    hobo = "*" * 46
    vebo = "*" + " " * 44 + "*"
    stdscr.addstr(0, 0, hobo)
    for i in range(1, 6):
        stdscr.addstr(i, 0, vebo)
    
    options = [
        ("1) Start Game", 6),
        ("2) Load Game", 8),
        ("3) Quit Game", 10)
    ]
    for text, row in options:
        stdscr.addstr(row, 12, text)
    
    for i in range(12, 17):
        stdscr.addstr(i, 0, vebo)
    stdscr.addstr(17, 0, hobo)

    stdscr.addstr(19, 0, "Please choose an option")

    # 初始选中项索引
    current_idx = 0
    # 绘制初始光标
    cursor_row = options[current_idx][1]
    stdscr.addstr(cursor_row, 8, "¤")

    stdscr.refresh()

    while True:
        key = stdscr.getch()
        # 清除当前光标
        stdscr.addstr(cursor_row, 8, " ")
        if key == curses.KEY_UP and current_idx > 0:
            current_idx -= 1
        elif key == curses.KEY_DOWN and current_idx < len(options)-1:
            current_idx += 1
        elif key == ord('1'):
            current_idx = 0
        elif key == ord('2'):
            current_idx = 1
        elif key == ord('3'):
            current_idx = 2
        elif key == ord('\n'):
            # 确认选择后执行对应操作
            if current_idx == 0:
                stdscr.clear()
                stdscr.addstr(0,0,"Start game")
                stdscr.refresh()
                curses.napms(1000)
                break
            elif current_idx == 1:
                stdscr.clear()
                stdscr.addstr(0,0,"Load game")
                stdscr.refresh()
                curses.napms(1000)
                break
            elif current_idx == 2:
                stdscr.clear()
                stdscr.addstr(0,0,"Goodbye!")
                stdscr.refresh()
                curses.napms(1000)
                return
        # 更新光标位置并绘制
        cursor_row = options[current_idx][1]
        stdscr.addstr(cursor_row, 8, "¤")
        stdscr.refresh()

if __name__ == "__main__":
    curses.wrapper(main_menu)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 23:31:06