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; });