我正在尝试用 C 语言制作一款 2 人国际象棋游戏,该游戏可以在终端中轻松运行。我在字体方面遇到了很多问题,所以我找到了一种好看的字体(最重要的是等宽字体),然后使用 FontForge 将国际象棋符号添加到 LucidaConsole 字体中。

我将符号从 192 改为 217,因为我确信在游戏中我不需要这些符号:

我创建了一个字符串数组:

char board[64][4] = {
    "È","Ö","É","Ó","Æ","Õ","Ê","Ô",
    "×","Ë","×","Ë","×","Ë","×","Ë",
    "Ø","Ù","Ø","Ù","Ø","Ù","Ø","Ù",
    "Ù","Ø","Ù","Ø","Ù","Ø","Ù","Ø",
    "Ø","Ù","Ø","Ù","Ø","Ù","Ø","Ù",
    "Ù","Ø","Ù","Ø","Ù","Ø","Ù","Ø",
    "Å","Ñ","Å","Ñ","Å","Ñ","Å","Ñ",
    "Î","Ä","Ï","Á","Ì","Ã","Ð","Â"
};

for(int i = 0; i < 64; i++) {
    printf("%s", board[i]);
    if( (i+1) % 8 == 0 ) {
        printf("\n");
    }
} 

然后在终端中,我期望它看起来像一个普通的棋盘,但我得到的却是这样的:

Alt+0192另外,我实际上可以使用从到的代码手写它们Alt+0217

我会以某种方式完成编码部分,但如何让这些符号在终端上正确显示?

21

  • 1
    另外,为什么[64][4]?我觉得这可能有关。您是否只是增加了4直到它在编译时停止抱怨?因为,如果这些字符(正如您似乎认为的那样)都是 192 到 217 之间的所有字符(并且 192 到 217 之间有 26 个字符,而不是 64 个。这是另一个我们无法在没有任何代码的情况下理解的谜团),那么您只需要 2 个字节(字符本身和终端 0,因为它们是字符串)。但是,如果您在不知情的情况下实际上使用 utf-8 作为输入(毕竟,几十年来这一直是所有操作系统的标准),


    – 

  • 1
    那么,确实,对于其中一些字符,您需要超过 1 个字节(乍一看,在 utf-8 中,所有这些字符最多只有 2 个字节长,因此,仍然[3]应该足够了。但也许我错过了一个 3 个字节长的字符)


    – 

  • 4
    此外,unicode 已经有了象棋棋子的代码。请参见


    – 

  • 2
    @chrslg “(192 和 217 之间有 24 个字符,而不是 64 个。这又是一个谜,没有代码我们就无法理解)” <<< 不是一个谜。棋盘是一个由 64 个棋子组成的数组,但只有 24 个可能的不同棋子:{兵、马、象、车、后、王}x{黑棋、白棋}x{黑格、白格}


    – 


  • 2
    您可以更改打印文本的背景颜色。然后 unicode 字符应该可以正常工作。


    – 


最佳答案
2

文本编码不匹配

您的终端设置为使用文本编码(每个字符一个字节),但您的应用程序输出的字节代表编码文本,而对于您使用的字符,每个字符都被编码为两个字节。

文本编码是“带外”信息:通信双方必须事先就此达成一致。如果他们不同意(正如这里发生的那样),您将得到无意义的输出。这就是为什么您应该尽可能避免处理文本编码,而做到这一点的方法是使用 Unicode。

使用 Unicode

好消息:您的终端支持 Unicode,那么为什么不让它为您处理渲染呢?Unicode 已在代码点 U+2654 至 U+265F 处定义了国际象棋符号。您需要将棋子代码点编码为 UTF-8,或者您也可以从中剪切并粘贴到您的代码中:

  "♔","♕","♖","♗","♘","♙",  //  WHITE k q r b k p
  "♚","♛","♜","♝","♞","♟︎"   //  BLACK k q r b k p

您还需要配置您的终端以使用 UTF-8 编码(查找名为“文本编码”的设置)。

顺便说一句,破解字体是个糟糕的主意。我的意思是除了你(在你的系统上)没有人可以使用该程序,而且你也不能使用该终端正确显示文本。

…并使用终端颜色代码

的答案很好地概括了它们的工作原理。

确实,这是一个编码问题,解决方案是使用 Unicode。但我们没有足够的信息来准确指出编码不匹配(或不匹配)的具体位置。

  • 源文件编码可能不是编译器所认为的。
  • 编译器所针对的执行字符集可能不合适。
  • 执行 printf 时,C 运行时库的区域设置可能会产生干扰。默认区域设置是“C”,但我们不知道这是否是当前设置。
  • C 运行时实现可能试图转换为宽字符,以调用 Windows 控制台 API 的“宽”版本来输出文本,或者可能调用其中一个“ANSI”API 让 Windows 进行转换。如果它调用的是 ANSI API,那么它是指定正确的代码页还是让 Windows 推断它?
  • 终端的代码页可能正确,也可能不正确。
  • 字体中的字形重新映射是否正确完成?
  • 字体链接或字体回退是否会干扰程序运行时的字体选择?

您想从端到端控制编码。

  1. 确保源文件是 UTF-8 编码的。在 Posix 领域,这可能是既定事实。但在 Windows 上并非如此。在您的 IDE 或编辑器中,应该有一种方法可以在保存文件时选择编码。您可能可以选择使用或不使用“签名”来保存它。(签名是以 UTF-8 编码的 Unicode 字节顺序标记。)即使是 Unicode 联盟也不鼓励使用签名,但在 Windows 上它会很有用。请自行选择。

  2. 告诉编译器您的源代码是 UTF-8 编码的。您还想告诉它使用 UTF-8 作为执行字符集。在当前版本的 MSVC 中,有一个 /UTF-8 命令行选项。我相信所有其他编译器都有类似的设置。

  3. 确保 C 运行时库的语言环境是 C 语言环境。这是默认设置。确保不要更改它,并且不要使用可能更改它的其他库。

  4. 现在,可以像在 Posix 环境中一样为 Windows 编写文本模式应用程序:通过标准 IO 使用 UTF-8(和终端转义序列)。但是,由于许多旧程序不以这种方式工作,因此您必须先选择加入:

    1. 调用SetConsoleOutputCP(CP_UTF8)以告诉终端您将以 UTF-8 发送文本。(程序结束时我会恢复原始代码页。)

    2. 您可能还想将输入代码页设置为 CP_UTF8。

    3. 如果您想使用终端转义序列(允许您控制颜色),您必须像这样选择:

      HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
      DWORD mode = 0;
      GetConsoleMode(hOut, &mode);
      SetConsoleMode(hOut, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);