我终于学会了黑客帝国里的矩阵雨

bold: () => ${ctlEsc}1m,

color: c => ${ctlEsc}${c};1m,

colors: {

fgRgb: (r, g, b) => ${ctlEsc}38;2;${r};${g};${b}m,

bgRgb: (r, g, b) => ${ctlEsc}48;2;${r};${g};${b}m,

fgBlack: () => ansi.color(30),

fgRed: () => ansi.color(31),

fgGreen: () => ansi.color(32),

fgYellow: () => ansi.color(33),

fgBlue: () => ansi.color(34),

fgMagenta: () => ansi.color(35),

fgCyan: () => ansi.color(36),

fgWhite: () => ansi.color(37),

bgBlack: () => ansi.color(40),

bgRed: () => ansi.color(41),

bgGreen: () => ansi.color(42),

bgYellow: () => ansi.color(43),

bgBlue: () => ansi.color(44),

bgMagenta: () => ansi.color(45),

bgCyan: () => ansi.color(46),

bgWhite: () => ansi.color(47),

},

};

module.exports = ansi;

这里面 ansi 对象上的每一个方法不做过多解释了。我们看到,每个方法都是返回一个奇怪的字符串,通过这些字符串可以改变命令行的显示效果。

这些字符串其实是一个个控制字符组成的控制序列。那什么是控制字符呢?我们应该都知道 ASC 字符集,这个字符集里面除了定义了一些可见字符以外,还有很多不可见的字符,就是控制字符。这些控制字符可以控制打印机、命令行等设备的显示和动作。

有两个控制字符集,分别是 CO 字符集和 C1 字符集。C0 字符集是 0x00 到 0x1F 这两个十六进制数范围内的字符,而 C1 字符集是 0x80 到 0x9F 这两个十六进制数范围内的字符。C0 和 C1 字符集内的字符和对应的功能可以在这里查到,我们不做详细描述了。

上面代码中,\x1b[ 其实是一个组合,\x1b 定义了 ESC 键,后跟 [ 表示这是一个控制序列导入器(Control Sequence Introducer,CSI)。在 \x1b[ 后面的所有字符都会被命令行解析为控制字符。

常用的控制序列有这些:

| 序列 | 功能 |

| — | — |

| CSI n A | 向上移动 n(默认为 1) 个单元 |

| CSI n A | 向下移动 n(默认为 1) 个单元 |

| CSI n C | 向前移动 n(默认为 1) 个单元 |

| CSI n D | 向后移动 n(默认为 1) 个单元 |

| CSI n E | 将光标移动到 n(默认为 1) 行的下一行行首 |

| CSI n F | 将光标移动到 n(默认为 1) 行的前一行行首 |

| CSI n G | 将光标移动到当前行的第 n(默认为 1)列 |

|

CSI n ; m H

| 移动光标到指定位置,第 n 行,第 m 列。n 和 m 默认为 1,即 CSI ;5H 与 CSI 1;5H 等同。 |

| CSI n J | 清空屏幕。如果 n 为 0(或不指定),则从光标位置开始清空到屏幕末尾;如果 n 为 1,则从光标位置清空到屏幕开头;如果 n 为 2,则清空整个屏幕;如果 n 为 3,则不仅清空整个屏幕,同时还清空滚动缓存。 |

| CSI n K | 清空行,如果 n 为 0(或不指定),则从光标位置清空到行尾;如果 n 为 1,则从光标位置清空到行头;如果 n 为 2,则清空整行,光标位置不变。 |

| CSI n S | 向上滚动 n (默认为 1)行 |

| CSI n T | 向下滚动 n (默认为 1)行 |

| CSI n ; m f | 与 CSI n ; m H 功能相同 |

| CSI n m | 设置显示效果,如 CSI 1 m 表示设置粗体,CSI 4 m 为添加下划线。 |

我们可以通过 CSI n m 控制序列来控制显示效果,在设置一种显示以后,后续字符都会沿用这种效果,直到我们改变了显示效果。可以通过 CSI 0 m 来清楚显示效果。常见的显示效果可以在SGR (Select Graphic Rendition) parameters parameters") 查到,这里受篇幅限制就不做赘述了。

上面的代码中,还定义了一些颜色,我们看到颜色的定义都是一些数字,其实每一个数字都对应一种颜色,这里列一下常见的颜色。

| 前景色 | 背景色 | 名称 | 前景色 | 背景色 | 名称 |

| — | — | — | — | — | — |

| 30 | 40 | 黑色 | 90 | 100 | 亮黑色 |

| 31 | 41 | 红色 | 91 | 101 | 亮红色 |

| 32 | 42 | 绿色 | 92 | 102 | 亮绿色 |

| 33 | 43 | 黄色 | 93 | 103 | 亮黄色 |

| 34 | 44 | 蓝色 | 94 | 104 | 亮蓝色 |

| 35 | 45 | 品红色(Magenta) | 95 | 105 | 亮品红色(Magenta) |

| 36 | 46 | 青色(Cyan) | 96 | 106 | 亮青色(Cyan) |

| 37 | 47 | 白色 | 97 | 107 | 亮白色 |

上面的代码中,使用了 CSI n;1m 的形式来定义颜色,其实是两种效果的,一个是具体颜色值,一个是加粗,一些命令行实现中会使用加粗效果来定义亮色。比如,如果直接定义 CSI 32 m 可能最终展示的是暗绿色,我们改成 CSI 32;1m 则将显示亮绿色。

颜色支持多种格式,上面的是 3-bit 和 4-bit 格式,同时还有 8-bit 和 24-bit。代码中也有使用样例,这里不再赘述了。

矩阵渲染


在 matrix-rain 的代码中,index.js 里的核心功能是 MatrixRain 这个类:

class MatrixRain {

constructor(opts) {

this.transpose = opts.direction === h;

this.color = opts.color;

this.charRange = opts.charRange;

this.maxSpeed = 20;

this.colDroplets = [];

this.numCols = 0;

this.numRows = 0;

// handle reading from file

if (opts.filePath) {

if (!fs.existsSync(opts.filePath)) {

throw new Error(${opts.filePath} doesn't exist);

}

this.fileChars = fs.readFileSync(opts.filePath, utf-8).trim().split(``);

this.filePos = 0;

this.charRange = file;

}

}

generateChars(len, charRange) {

// by default charRange == ascii

let chars = new Array(len);

if (charRange === ascii) {

for (let i = 0; i < len; i++) {

chars[i] = String.fromCharCode(rand(0x21, 0x7E));

}

} else if (charRange === braille) {

for (let i = 0; i < len; i++) {

chars[i] = String.fromCharCode(rand(0x2840, 0x28ff));

}

} else if (charRange === katakana) {

for (let i = 0; i < len; i++) {

chars[i] = String.fromCharCode(rand(0x30a0, 0x30ff));

}

} else if (charRange === emoji) {

// emojis are two character widths, so use a prefix

const emojiPrefix = String.fromCharCode(0xd83d);

for (let i = 0; i < len; i++) {

chars[i] = emojiPrefix + String.fromCharCode(rand(0xde01, 0xde4a));

}

} else if (charRange === file) {

for (let i = 0; i < len; i++, this.filePos++) {

this.filePos = this.filePos < this.fileChars.length ? this.filePos : 0;

chars[i] = this.fileChars[this.filePos];

}

}

return chars;

}

makeDroplet(col) {

return {

col,

alive: 0,

curRow: rand(0, this.numRows),

height: rand(this.numRows / 2, this.numRows),

speed: rand(1, this.maxSpeed),

chars: this.generateChars(this.numRows, this.charRange),

};

}

resizeDroplets() {

[this.numCols, this.numRows] = process.stdout.getWindowSize();

// transpose for direction

if (this.transpose) {

[this.numCols, this.numRows] = [this.numRows, this.numCols];

}

// Create droplets per column

// add/remove droplets to match column size

if (this.numCols > this.colDroplets.length) {

for (let col = this.colDroplets.length; col < this.numCols; ++col) {

// make two droplets per row that start in random positions

this.colDroplets.push([this.makeDroplet(col), this.makeDroplet(col)]);

}

} else {

this.colDroplets.splice(this.numCols, this.colDroplets.length - this.numCols);

}

}

writeAt(row, col, str, color) {

// Only output if in viewport

if (row >=0 && row < this.numRows && col >=0 && col < this.numCols) {

const pos = this.transpose ? ansi.cursorPos(col, row) : ansi.cursorPos(row, col);

write(${pos}${color || ``}${str || ``});

}

}

renderFrame() {

最后

俗话说,好学者临池学书,不过网络时代,对于大多数的我们来说,我倒是觉得学习意识的觉醒很重要,这是开始学习的转折点,比如看到对自己方向发展有用的信息,先收藏一波是一波,比如如果你觉得我这篇文章ok,先点赞收藏一波。这样,等真的沉下心来学习,不至于被找资料分散了心神。慢慢来,先从点赞收藏做起,加油吧!

另外,给大家安排了一波学习面试资料:

image

image

以上就是本文的全部内容,希望对大家的面试有所帮助,祝大家早日升职加薪迎娶白富美走上人生巅峰!
``}`);

}

}

renderFrame() {

最后

俗话说,好学者临池学书,不过网络时代,对于大多数的我们来说,我倒是觉得学习意识的觉醒很重要,这是开始学习的转折点,比如看到对自己方向发展有用的信息,先收藏一波是一波,比如如果你觉得我这篇文章ok,先点赞收藏一波。这样,等真的沉下心来学习,不至于被找资料分散了心神。慢慢来,先从点赞收藏做起,加油吧!

另外,给大家安排了一波学习面试资料:

[外链图片转存中…(img-fXp1e881-1720109180129)]

[外链图片转存中…(img-bZpvwqx8-1720109180130)]

以上就是本文的全部内容,希望对大家的面试有所帮助,祝大家早日升职加薪迎娶白富美走上人生巅峰!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值