matrix-code
Version:
🐰 Make your CLI rain like the 1999 film "The Matrix".
152 lines (125 loc) • 3.57 kB
JavaScript
const path = require('path');
const ansi = require('ansi');
const color = require('color');
const cursor = ansi(process.stdout);
const chalk = require('chalk');
const cliCursor = require('cli-cursor');
const getPixels = require('get-pixels')
const clear = () => console.log('\033[2J');
cliCursor.hide();
const width = process.stdout.columns;
const height = process.stdout.rows;
const ramp = 'ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘZ:・."=*+-<>¦|çリクㄥϛㄣƐ4日';
const rndFromRange = (min, max) => (Math.random() * (max - min)) + min;
let img;
let imgWidth;
let imgHeight;
const lumAtPixel = (x, y) => {
const idx = ((imgWidth * parseInt(y, 10)) + parseInt(x, 10)) << 2;
if (idx + 4 <= img.data.length) {
const r = img.data[idx]
const g = img.data[idx + 1]
const b = img.data[idx + 2]
const l = color({r, g, b}).hsl().color[2]
return {r, g, b, l};
}
}
const out = (x, y, char, col) => {
if (x < 0 || x > width, y < 0 || y > height) {
return false
}
let [r, g, b] = col;
const pix = lumAtPixel(imgWidth / width * x, imgHeight / height * y);
if (!pix) {return};
const l = 0.4 + (0.6 / 100 * pix.l);
r = parseInt(r * l);
g = parseInt(g * l);
b = parseInt(b * l);
cursor.goto(x, y);
cursor.rgb(r, g, b);
if (l > 0.5) {
cursor.write(chalk.bold.rgb(r,g,b)(char));
} else {
cursor.write(chalk.rgb(r,g,b)(char));
}
}
const loadPixels = path => new Promise((resolve, reject) => {
getPixels(path, (err, pixels) => {
if (err) {
return reject(err)
}
resolve(pixels)
})
})
const genChars = len => {
let chars = '';
for (let i = 0; i < len; i += 1) {
const x = parseInt(Math.random() * ramp.length);
chars += ramp[x];
}
return chars;
};
const Strand = () => {
const length = parseInt(rndFromRange(5, height * 1.5));
const mutations = parseInt(rndFromRange(length * 0.125, length * 0.25));
const fallSpeed = parseInt(rndFromRange(1, 3));
const brightness = parseInt(rndFromRange(200, 255));
let chars = genChars(length);
let x = parseInt(Math.random() * width);
let y = parseInt(Math.random() * height) - (height + 1);
return {
draw: () => {
for (let i = 1; i < length; i += 1) {
const green = parseInt(brightness / length * (length - i));
const col = [0, green, 0]
out(x, y - i, chars[i], col);
}
cursor.bold();
out(x, y, chars[0], [255, 255, 255]);
cursor.reset();
for (let i = 0; i < fallSpeed + 1; i += 1) {
out(x, y - length - i, ' ', [0, 0, 0]);
}
},
fall: () => {
y = parseInt(y + fallSpeed);
if (y > height + length + fallSpeed) {
y = 0;
x = rndFromRange(0, width)
}
},
mutate: () => {
const nextChars = chars.split('');
for (let i = 0; i < mutations; i += 1) {
const index = parseInt(Math.random() * length);
const x = parseInt(Math.random() * ramp.length - 1);
nextChars[index] = ramp[x];
}
chars = nextChars.join('');
}
};
}
const Strands = count => {
const strands = [];
for (let i = 0; i < count; i += 1) {
strands.push(Strand());
}
return {
fall: () => strands.forEach(strand => strand.fall()),
mutate: () => strands.forEach(strand => strand.mutate()),
draw: () => strands.forEach(strand => strand.draw()),
}
}
const strands = Strands(20);
(async () => {
// clear();
img = await loadPixels(path.join(__dirname, 'white-rabbit.jpg'));
imgWidth = img.shape[0];
imgHeight = img.shape[1];
setInterval(() => {
strands.draw();
strands.fall();
strands.mutate();
}, 1000 / 30);
})();