如何使用节点获得对终端的完全可视化控制?
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
。一个好的答案可以显示一个简单的例子:
- 正在清屏
- 在屏幕中间随意画一些say
- 响应键盘事件。
更新 2 - 我创建了一个实用程序库来帮助执行此操作:node-cli-character-matrix.
为此您需要使用 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,您将需要另一个答案(或问题)。
我想使用 node 将经典游戏 'Snake' 制作成 CLI 游戏。
为此,我想逃离 'printing text to the screen' 终端,并控制终端的完全控制(在视觉可能的有限范围内)。
很高兴(实际上,更喜欢)让终端充当等宽字符矩阵。
终端仍应接收键盘事件。
基本上,想一想 Jest 在手表模式下的工作方式,或者当您执行 npm init
.
我想找到这个问题的答案很容易 - 但我不知道 google 的术语是什么。
更新 我知道我会为此使用 process.stdout
。一个好的答案可以显示一个简单的例子:
- 正在清屏
- 在屏幕中间随意画一些say
- 响应键盘事件。
更新 2 - 我创建了一个实用程序库来帮助执行此操作:node-cli-character-matrix.
为此您需要使用 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,您将需要另一个答案(或问题)。