【问题标题】:Why is ANSI printing the code the first time the function is called and printing colours the second time around?为什么 ANSI 在第一次调用函数时打印代码而第二次打印颜色?
【发布时间】:2020-01-29 14:40:45
【问题描述】:

我正在尝试在 connect 4 游戏中更改我的 printf 语句的颜色输出。我已经做了一个功能来设置打印颜色并重置它。它适用于我的大部分代码,但不适用于第一次调用的第一个函数,但从那里开始工作。有问题的函数是主程序中调用的第一个函数。

我尝试了函数的不同定位,将其定位在调用函数的任一侧并将颜色代码直接输入到 printf 函数中,但它总是第一次失败(而且只是第一次)。

#include <stdio.h>
void sprint_green();
void sprint_red();
int main_menu ( void ){
    int a = 0;
    char opt [20];
    sprint_red();
    printf("============================\n");
    printf("    Welcome to Connect4!\n");
    printf("============================\n");
    sprint_reset();
    // Continue asking for an option until a valid option (n/l/q) is entered
    while (a == 0)
    {
        sprint_green();
        printf("(N)ew game\n(L)oad game\n(Q)uit");
        sprint_reset();
        fgets(opt, 20, stdin);
    // if 'n', return 1
        if(opt[0] == 'n' || opt[0] == 'N'){
            a = 1;
        }
    // if 'l', return 2
        else if(opt[0] == 'l' || opt[0] == 'L'){
            a = 2;
        }
    // if 'q', return -1
        else if(opt[0] == 'q' || opt[0] == 'Q'){
            a = -1;
            printf("\nGoodbye!\n");
        }
    // if anything else, give error message and ask again..
        else
        {
            printf("Invalid option\n");
        }
    }
    system("cls");
    return(a);
}

int main (void)
{
    int i;

    for(i = 0; i < 5; i++)
    {
        main_menu();
    }
}

void sprint_green()
{
  printf("\033[1;32m");
}

void sprint_red()
{
  printf("\033[1;31m");
}

void sprint_reset()
{
  printf("\033[0m");
}

前三个 printf 语句应以红色打印,下一个将以绿色打印。然而第一次它被称为打印ANSI颜色代码。

[1;31m============================
    Welcome to Connect4!
============================
[0m[1;32m(N)ew game
(L)oad game
(Q)uit[0m

但是,在运行一次游戏并重新启动(不关闭终端)后,这些功能可以正常工作。

【问题讨论】:

  • 代码无法编译,因为它的某些部分丢失了。请删除所有不需要证明您的问题的部分,即创建minimal reproducible example
  • \ 是 C 字符串中的保留字符。要将其包含在字符串中,您需要在其前面加上另一个 \,因此 printf("\\e[1;31m%c\\e[0m", print);
  • @user694733 感谢您的反馈。我现在已经将它编辑为一个可编译的版本,它重现了同样的错误。
  • 修复代码后(添加#include &lt;stdlib.h&gt; 并转发声明sprint_reset())我得到了正确的结果。也许在程序开始时运行cls
  • 在 Linux 上它可以工作,除了不存在的 cls。在 Windows cmd 窗口中,在执行 cls 之前,转义字符被打印为可见字符,之后转义序列被正确处理。在 Windows Mingw64 shell 窗口中,我看到一个缓冲问题,cls 似乎不起作用。程序退出时出现输出,但所有颜色都正确。非便携式cls不起作用。使用_IONBF 添加setvbuf 调用后,输出立即出现。显然结果取决于终端的类型,cmd 窗口在cls 之后表现不同。

标签: c colors printf


【解决方案1】:

默认情况下,Windows 命令外壳不会启动 vt100 仿真。正如@Bodo 在他的回答中指出的那样,这可以通过运行shell 命令cls 来触发。但是,从技术上讲,shell 命令根本不需要是 cls 甚至是有效命令。您只需一个空的system(" ") 呼叫即可触发它!这也是可移植的,因为它除了暂时启动一个 shell 实例并终止它之外什么都不做。因此它在 Windows 或 Linux 环境中应该同样可以正常工作。

修复代码:

#include <stdio.h>
#include <stdlib.h> // for system()

void sprint_green();
void sprint_red();
int main_menu ( void ){
    int a = 0;
    char opt [20];
    system(" "); // Trigger ANSI emulation
    sprint_red();
    printf("============================\n");
    printf("    Welcome to Connect4!\n");
    printf("============================\n");
    sprint_reset();
    // Continue asking for an option until a valid option (n/l/q) is entered
    while (a == 0)
    {
        sprint_green();
        printf("(N)ew game\n(L)oad game\n(Q)uit");
        sprint_reset();
        fgets(opt, 20, stdin);
    // if 'n', return 1
        if(opt[0] == 'n' || opt[0] == 'N'){
            a = 1;
        }
    // if 'l', return 2
        else if(opt[0] == 'l' || opt[0] == 'L'){
            a = 2;
        }
    // if 'q', return -1
        else if(opt[0] == 'q' || opt[0] == 'Q'){
            a = -1;
            printf("\nGoodbye!\n");
        }
    // if anything else, give error message and ask again..
        else
        {
            printf("Invalid option\n");
        }
    }
    system("cls");
    return(a);
}

int main (void)
{
    int i;

    for(i = 0; i < 5; i++)
    {
        main_menu();
    }
}

void sprint_green()
{
  printf("\033[1;32m");
}

void sprint_red()
{
  printf("\033[1;31m");
}

void sprint_reset()
{
  printf("\033[0m");
}

【讨论】:

  • 我只观察到调用system("cls"); 似乎激活了ANSI 仿真。你有没有解释为什么任何system(something); 具有相同的效果。任何地方都有文档吗?
  • @Bodo 不幸的是,我不知道为什么会这样。几年前,当我在同一个问题上苦苦挣扎时,有人建议system(" " ) 并且成功了!由于它解决了我的问题,我没有进一步调查。我最好的猜测是命令 shell 在运行 any shell 命令之前会打开 ANSI 仿真。或者它在从自身实例中调用时使用SetConsoleMode() 开启ANSI 仿真。
  • @Bodo 我刚刚在 Windows 10 机器上测试了它,它仍然可以工作!如果该行被注释掉,则不再检测到转义序列。它不适合你吗?
  • 这个我没试过。我唯一担心的是,依赖未记录的解决方案可能很危险,因为它可能会在将来发生变化,或者它可能取决于您不知道的其他事情。这就是为什么参考相关文档会有所帮助的原因。
  • @Bodo 我在官方 MS 文档中也没有看到 system("cls")。这整个行为很可能是某事的意外副作用,而不是记录在案的功能。尽管如此,这是一个已经存在并且已知可以工作几年的解决方案(尽管有几个 Windows 更新),所以我在这方面没有发现重大风险。这可能是让完全相同的代码在 Windows 和 Linux 环境中工作的唯一方法,我觉得这非常好。所以我让读者自己选择他觉得舒服的权衡。
【解决方案2】:

根据
https://solarianprogrammer.com/2019/04/08/c-programming-ansi-escape-codes-windows-macos-linux-terminals/

https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences?redirectedfrom=MSDN
必须将 Windows 控制台置于 ANSI 转义模式才能处理转义序列而不是打印它们。

显然执行cls 命令似乎可以做到这一点。

您可以通过设置标志ENABLE_VIRTUAL_TERMINAL_PROCESSING 以编程方式使用GetConsoleMode()SetConsoleMode() 设置终端模式

从引用的 Microsoft 页面复制的代码示例:

#include <stdio.h>
#include <wchar.h>
#include <windows.h>

int main()
{
    // Set output mode to handle virtual terminal sequences
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hOut == INVALID_HANDLE_VALUE)
    {
        return GetLastError();
    }

    DWORD dwMode = 0;
    if (!GetConsoleMode(hOut, &dwMode))
    {
        return GetLastError();
    }

    dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    if (!SetConsoleMode(hOut, dwMode))
    {
        return GetLastError();
    }

    // Try some Set Graphics Rendition (SGR) terminal escape sequences
    wprintf(L"\x1b[31mThis text has a red foreground using SGR.31.\r\n");
    wprintf(L"\x1b[1mThis text has a bright (bold) red foreground using SGR.1 to affect the previous color setting.\r\n");
    /* ... more examples removed */

    return 0;
}

显然此解决方案仅适用于 Windows,但运行 system("cls") 也是不可移植的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-26
    • 2017-02-08
    • 2015-01-28
    • 1970-01-01
    相关资源
    最近更新 更多