如何使用节点获得对终端的完全可视化控制?

How do I get full visual control of a terminal with node?

我想使用 node 将经典游戏 'Snake' 制作成 CLI 游戏。

为此,我想逃离 'printing text to the screen' 终端,并控制终端的完全控制(在视觉可能的有限范围内)。

很高兴(实际上,更喜欢)让终端充当等宽字符矩阵。

终端仍应接收键盘事件。

基本上,想一想 Jest 在手表模式下的工作方式,或者当您执行 npm init.

时的 npm

我想找到这个问题的答案很容易 - 但我不知道 google 的术语是什么。

更新 我知道我会为此使用 process.stdout。一个好的答案可以显示一个简单的例子:

更新 2 - 我创建了一个实用程序库来帮助执行此操作:node-cli-character-matrix.

在后台它使用 ink for visual display and keypress 来捕获键盘输入。

为此您需要使用 process.stdout(绘制)和 process.stdin(按键事件)。
对于动画(如蛇的爬行),您需要记住之前的状态,清空终端,然后编写一个新的、已更改的状态。 我在 axel lib, it contains a link to example - game blitzr

中看到了一个很好的例子

输出

对于 unix 终端(Linux、MacOS、Windows 上的 WSL),它们只是使用发送到标准输出的正常字节序列来控制一切。这些称为转义序列。

此协议是从 Teletype 协议(您在电影中看到的那些打字机)演变而来的 VT 协议族。 VT代表视频终端(因为当时的常规终端是点阵打印机或电传打字机),是DEC销售的一系列产品。

销量最高的型号是 VT100。所以现在最流行的软件终端模拟的协议都是基于VT100协议。

如果您 google“VT100 转义码”,您将找到有关如何控制终端的文档。

一旦软件开始模拟这些硬件终端,人们就希望对支持的功能进行标准化(生产了各种终端,例如:VT52、VT100、VT102 等),因此 ANSI 为文本终端制定了标准 X3.64 (后来也被 ECMA 标准化为 ECMA-48)。因此,您也可以选择 google“ANSI 转义码”来获得您想要的内容。

维基百科实际上有一个相当不错的页面:https://en.wikipedia.org/wiki/ANSI_escape_code. However I personally prefer to use something simpler like the following and avoid all the historical stuff on Wikipedia: https://espterm.github.io/docs/VT100%20escape%20codes.html

(注意:一些文档将 ESC 字符表示为 ^[,即使它只是一个字节:0x1B

请注意 Linux 进一步扩展了 ANSI 控制代码以包含更多功能,例如鼠标支持和 RGB 颜色:https://man7.org/linux/man-pages/man4/console_codes.4.html

转义码的工作方式很简单。打印一个不可打印的字符来告诉终端要做什么。例如清除您发送的屏幕:

ESC [ 2 J

或者简单地说 javascript:

console.log("\x1b[2J");

当然 console.log() 也会在打印您的字符串后发送换行符("\n"0x0A),但上面的行应该有效。显然要避免节点上的换行符,您可以使用:

process.stdout.write("\x1b[2J"); // clear screen

在终端的某个地方随机打印一些东西,例如在 row=5 column=20 处打印一个笑脸,就是:

ESC [ 5 ; 2 0 H 

当然假设您的终端支持 utf8(对于笑脸,如果您只打印常规 ASCII 字符,它可以在没有 utf8 支持的情况下工作)。或者在节点中:

process.stdout.write("\x1b[5;20H");

您可以轻松地使其可重复使用:

function goto (x, y) {
    process.stdout.write(`\x1b[${y};${x}H`);
}

function print (txt) {
    process.stdout.write(txt);
}

goto(20,5);
print('');

当然,有很多库可以帮助您控制终端。其中一些也适用于 Windows 控制台(我上面的示例通常不适用于 Windows 控制台,但我听说您可以在 Windows 10 的终端中启用 ANSI 支持,我不要使用 Windows)。一些比较受欢迎的是:

  • chalk 简单地为您的文本着色(但非常漂亮 API),
  • blessed这是一个全功能控制台UI框架,
  • terminal-kit 这是另一个功能齐全的控制台框架
  • 我必须提到 ink,它允许你在控制台上使用 React.js(它实际上不是 React,只是 React 兼容 API),这让我很兴奋。

输入

从终端获取原始输入并不是那么简单。通常你的 stdin 默认情况下会被终端缓冲。这意味着在按下 Enter 键之前您将看不到任何键盘事件。如果你想从键盘上按下原始键,你需要关闭行缓冲。不幸的是,node 没有任何内置的东西来帮助你做到这一点。幸运的是,unix 确实有标准工具来启用原始输入。

以下命令启用和禁用原始模式,并且已知可在 Linux 和 BSD(例如 MacOS)上运行:

# turn on raw mode, turn off echo
stty raw -echo

# turn off raw mode, turn on echo
stty -raw echo

以下是 node.js 中接受原始键盘输入的简单程序(如果您想知道 ctrl-x 或箭头键或 F3 键的代码是什么,这很有用):

#! /usr/bin/env node

const exec = require('child_process').execSync;

exec('stty raw -echo', {
    stdio: 'inherit' // this is important!
});

process.stdin.on('data', input => {

    console.log(`${input.toString('hex')}\r`);
    // note: We add \r because in raw mode the terminal won't
    //       do it automatically for us.

    // Example of how to handle key presses:
    // Since we are in raw mode the terminal won't send the
    // kill signal to our process when we press ctrl-c so
    // we need to handle ctrl-c manually:
    if (input == '\x03') { // ctrl-c
        process.exit();
    }
});

process.on('exit', () => {
    // remember to restore normal terminal behavior:
    exec('stty -raw echo',{
        stdio: 'inherit' // this is important!
    });
});

您可以在 stdin.on('data') 回调中添加所有 UI 控制器逻辑。

当然,对于 Windows,您将需要另一个答案(或问题)。