如何使用 Windows.h 读取和显示扩展的 ASCII 符号
How to read and display extended ASCII symbols with Windows.h
我正在开发一款使用 ASCII 符号作为像素的主机游戏。此游戏的地图存储在 .txt
文件中:
████████████████
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
████████████████
为了显示地图,我正在逐行从文件 demo.txt
中读取它并将每个字符写入 CHAR_INFO *screen
:
void setScreen(const char* layoutFile, const char* levelDataFile) {
std::ifstream levelData(levelDataFile);
levelData >> width >> height;
field = {0, 0, (SHORT)width, (SHORT)height};
screen = new CHAR_INFO[width * height];
levelData.close();
std::ifstream layout(layoutFile); //reading from a file `demo.txt`
std::string line;
for (int j = 0; j < height; j++) {
getline(layout, line);
for(int i = 0; i < width; i++) {
screen[j * width + i].Char.AsciiChar = line[i]; //writing each character of a line to screen
screen[j * width + i].Attributes = BACKGROUND_GREEN;
}
}
layout.close();
}
之后,我使用以下函数显示地图(map.getScreen()
returns 指向屏幕数组的指针):
WriteConsoleOutputA(
console.getHOut(),
map.getScreen(),
{ (SHORT)map.getWidth(), (SHORT)map.getHeight() },
{ 0,0 },
&map.getField()
);
但问题是█
显示为�
,输出如下:
����������������
���
���
���
���
���
���
���
���
���
���
���
���
���
���
����������������
我试过的一些东西:
SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8);
SetConsoleCutputCP(1251);
setlocale(LC_ALL, "");
std::locale::global(std::locale(std::locale::empty(), new std::codecvt_utf8<wchar_t>));
来自评论:
File is encoded in UTF-8
.
这有很大的不同。您不仅不处理 ASCII 字符(值最多 127),而且甚至不处理扩展的 ASCII 字符(值最多 255)。您正在处理 Unicode,特别是字符编号 9608 (a.k.a.U+2588)。这远远超出了单个 char
所能代表的范围。然而,当您从 line[i]
.
分配时,您正在存储单个 char
'█'
的 UTF-8 表示由三个字节组成:0xE2
、0x96
和 0x88
。这就是为什么您的输出在板的左侧显示三个“未知字符”符号,在右侧显示 none。那些“未知字符”符号来自一个 UTF-8 字符的三个字节。然后你会有 width-2
空格后跟三个更多的“未知字符”,除非你在 width-3
空格后停止复制字符。所以你永远不会遇到你的董事会的“真实”右边界。 (检查 line
的长度并将其与 width
进行比较——对于中间行,您应该看到 line.size()
是 width+4
。对于第一行和最后一行,您应该看到 line.size()
是 3*width
。)
部分解决方案是使用 Char.UnicodeChar
而不是 Char.AsciiChar
。但是,UnicodeChar
(我认为)只有两个字节,所以它不能容纳三字节的 UTF-8 编码。您可能必须转换为 UTF-16。如果您只需要几个字符,查找 table 可能与通用解决方案一样有效。通过口述等效项将文件中的字符更改为真正的 ASCII 字符。例如,也许你可以说 '#'
代表一个完整的块。这具有作为单字节的优势,因此您的逻辑大部分都有效。您只需要添加一个翻译功能,例如
WCHAR convert(char c)
{
switch ( c ) {
case '#': return u'[=10=]x2588'; // Full block (█)
// Etc.
}
return c; // If no translation is needed
}
然后在存储你的地图数据时,你会调用这个翻译,如
screen[j * width + i].Char.UnicodeChar = convert(line[i]);
最后一步是确保您的控制台支持 UTF-16。哦,使用 WriteConsoleOutputW()
而不是 WriteConsoleOutputA()
。
我正在开发一款使用 ASCII 符号作为像素的主机游戏。此游戏的地图存储在 .txt
文件中:
████████████████
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
████████████████
为了显示地图,我正在逐行从文件 demo.txt
中读取它并将每个字符写入 CHAR_INFO *screen
:
void setScreen(const char* layoutFile, const char* levelDataFile) {
std::ifstream levelData(levelDataFile);
levelData >> width >> height;
field = {0, 0, (SHORT)width, (SHORT)height};
screen = new CHAR_INFO[width * height];
levelData.close();
std::ifstream layout(layoutFile); //reading from a file `demo.txt`
std::string line;
for (int j = 0; j < height; j++) {
getline(layout, line);
for(int i = 0; i < width; i++) {
screen[j * width + i].Char.AsciiChar = line[i]; //writing each character of a line to screen
screen[j * width + i].Attributes = BACKGROUND_GREEN;
}
}
layout.close();
}
之后,我使用以下函数显示地图(map.getScreen()
returns 指向屏幕数组的指针):
WriteConsoleOutputA(
console.getHOut(),
map.getScreen(),
{ (SHORT)map.getWidth(), (SHORT)map.getHeight() },
{ 0,0 },
&map.getField()
);
但问题是█
显示为�
,输出如下:
����������������
���
���
���
���
���
���
���
���
���
���
���
���
���
���
����������������
我试过的一些东西:
SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8);
SetConsoleCutputCP(1251);
setlocale(LC_ALL, "");
std::locale::global(std::locale(std::locale::empty(), new std::codecvt_utf8<wchar_t>));
来自评论:
File is encoded in
UTF-8
.
这有很大的不同。您不仅不处理 ASCII 字符(值最多 127),而且甚至不处理扩展的 ASCII 字符(值最多 255)。您正在处理 Unicode,特别是字符编号 9608 (a.k.a.U+2588)。这远远超出了单个 char
所能代表的范围。然而,当您从 line[i]
.
char
'█'
的 UTF-8 表示由三个字节组成:0xE2
、0x96
和 0x88
。这就是为什么您的输出在板的左侧显示三个“未知字符”符号,在右侧显示 none。那些“未知字符”符号来自一个 UTF-8 字符的三个字节。然后你会有 width-2
空格后跟三个更多的“未知字符”,除非你在 width-3
空格后停止复制字符。所以你永远不会遇到你的董事会的“真实”右边界。 (检查 line
的长度并将其与 width
进行比较——对于中间行,您应该看到 line.size()
是 width+4
。对于第一行和最后一行,您应该看到 line.size()
是 3*width
。)
部分解决方案是使用 Char.UnicodeChar
而不是 Char.AsciiChar
。但是,UnicodeChar
(我认为)只有两个字节,所以它不能容纳三字节的 UTF-8 编码。您可能必须转换为 UTF-16。如果您只需要几个字符,查找 table 可能与通用解决方案一样有效。通过口述等效项将文件中的字符更改为真正的 ASCII 字符。例如,也许你可以说 '#'
代表一个完整的块。这具有作为单字节的优势,因此您的逻辑大部分都有效。您只需要添加一个翻译功能,例如
WCHAR convert(char c)
{
switch ( c ) {
case '#': return u'[=10=]x2588'; // Full block (█)
// Etc.
}
return c; // If no translation is needed
}
然后在存储你的地图数据时,你会调用这个翻译,如
screen[j * width + i].Char.UnicodeChar = convert(line[i]);
最后一步是确保您的控制台支持 UTF-16。哦,使用 WriteConsoleOutputW()
而不是 WriteConsoleOutputA()
。