104 lines
No EOL
2.8 KiB
TypeScript
104 lines
No EOL
2.8 KiB
TypeScript
import { ANSI } from "../../../../config/apps/terminal.config";
|
|
import { randomFromArray, removeFromArray } from "../../../_utils/array.utils";
|
|
import { randomRange } from "../../../_utils/math.utils";
|
|
import { Vector2 } from "../../../math/vector2";
|
|
import { Command } from "../command";
|
|
import { Stream } from "../stream";
|
|
|
|
const ANIMATION_SPEED = 1.25;
|
|
const SCREEN_WIDTH = 75;
|
|
const SCREEN_HEIGHT = 20;
|
|
const CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.*\\/()#@&$!?%°:<>[]";
|
|
|
|
const PARTICLES = {
|
|
framesBetweenSpawn: 30,
|
|
fallSpeed: 1,
|
|
minSize: 5,
|
|
maxSize: 25
|
|
};
|
|
|
|
type Particle = {
|
|
position: Vector2,
|
|
size: number;
|
|
};
|
|
|
|
function generateScreen(frame: number, screen: string[][], particles: Particle[]): string {
|
|
// Spawn new particles
|
|
if (frame % PARTICLES.framesBetweenSpawn) {
|
|
const newParticle: Particle = {
|
|
position: new Vector2(randomRange(0, SCREEN_WIDTH), SCREEN_HEIGHT).round(),
|
|
size: Math.round(randomRange(PARTICLES.minSize, PARTICLES.maxSize))
|
|
};
|
|
particles.push(newParticle);
|
|
}
|
|
|
|
// Move and render particles
|
|
particles.forEach((particle) => {
|
|
particle.position.y -= PARTICLES.fallSpeed;
|
|
|
|
// Clean previous render
|
|
for (let i = 0; i < PARTICLES.fallSpeed; i++) {
|
|
const positionX = particle.position.x;
|
|
const positionY = particle.position.y + i + particle.size;
|
|
|
|
if (positionY < SCREEN_HEIGHT && positionY >= 0)
|
|
screen[positionY][positionX] = " ";
|
|
}
|
|
|
|
// Remove offscreen particles
|
|
if (particle.position.y + particle.size <= 0 || particle.position.x >= SCREEN_WIDTH)
|
|
return removeFromArray(particle, particles);
|
|
|
|
for (let i = 0; i < particle.size; i++) {
|
|
const character = randomFromArray(CHARACTERS.split(""));
|
|
let color = i == 0 ? ANSI.fg.white : ANSI.fg.green;
|
|
|
|
if (i > particle.size / 2)
|
|
color = ANSI.fg.green + ANSI.decoration.dim;
|
|
|
|
const positionX = particle.position.x;
|
|
const positionY = particle.position.y + i;
|
|
|
|
if (positionX < SCREEN_WIDTH && positionY < SCREEN_HEIGHT && positionY >= 0) {
|
|
screen[positionY][positionX] = color + character + ANSI.reset;
|
|
}
|
|
}
|
|
});
|
|
|
|
return [...screen.map((row) => row.join(""))].reverse().join("\n");
|
|
}
|
|
|
|
export const cmatrix = new Command()
|
|
.setManual({
|
|
purpose: "Show a scrolling 'Matrix' like screen",
|
|
usage: "cmatrix",
|
|
})
|
|
.setExecute(function() {
|
|
const stream = new Stream();
|
|
const particles: Particle[] = [];
|
|
|
|
// Create screen
|
|
const screen: string[][] = [];
|
|
for (let y = 0; y < SCREEN_HEIGHT; y++) {
|
|
const row: string[] = [];
|
|
for (let x = 0; x < SCREEN_WIDTH; x++) {
|
|
row.push(" ");
|
|
}
|
|
screen.push(row);
|
|
}
|
|
|
|
let frame = 0;
|
|
const interval = setInterval(() => {
|
|
const text = generateScreen(frame, screen, particles);
|
|
stream.send(text);
|
|
frame++;
|
|
}, 100 / ANIMATION_SPEED);
|
|
|
|
stream.on(Stream.EVENT_NAMES.stop, () => {
|
|
clearInterval(interval);
|
|
});
|
|
|
|
stream.start();
|
|
|
|
return stream;
|
|
}); |