import { GetCurrentTerminal } from '../app' import type { Item } from '../fs/Item' import type { SimpleStream } from '../utils/SimpleStream' import { Program } from './Program' type SmokeState = { y: number x: number ptrn: number kind: number } type SlOptions = { accident: boolean fly: boolean logo: boolean c51: boolean } export class Sl extends Program { private static readonly D51_HEIGHT = 10 private static readonly D51_FUNNEL = 7 private static readonly D51_LENGTH = 83 private static readonly D51_PATTERNS = 6 private static readonly LOGO_HEIGHT = 6 private static readonly LOGO_FUNNEL = 4 private static readonly LOGO_LENGTH = 84 private static readonly LOGO_PATTERNS = 6 private static readonly C51_HEIGHT = 11 private static readonly C51_FUNNEL = 7 private static readonly C51_LENGTH = 87 private static readonly C51_PATTERNS = 6 private static readonly SMOKE_PATTERNS = 16 private static readonly MAX_SMOKE = 1000 private smokes: SmokeState[] = [] private smokeSum = 0 public async Exec( _: SimpleStream, stdout: SimpleStream, __: Item, args: string[], ): Promise { const options = this.parseOptions(args) const terminal = GetCurrentTerminal() const cols = Math.max(1, Math.floor(terminal.GetWidthCells())) const lines = Math.max(1, Math.floor(terminal.GetHeightCells())) this.resetSmoke() // Clear screen and move cursor home (ANSI CSI) stdout.emit('\x1b[2J\x1b[H') for (let x = cols - 1; ; --x) { const ok = options.logo ? this.addSl(x, stdout, cols, lines, options) : options.c51 ? this.addC51(x, stdout, cols, lines, options) : this.addD51(x, stdout, cols, lines, options) if (!ok) break await this.sleep(40) } stdout.emit(`\x1b[${Math.max(1, lines)};1H\n`) return 0 } private parseOptions(args: string[]): SlOptions { const options: SlOptions = { accident: false, fly: false, logo: false, c51: false, } for (const arg of args.slice(1)) { if (!arg.startsWith('-')) continue for (const flag of arg.slice(1)) { switch (flag) { case 'a': options.accident = true break case 'F': options.fly = true break case 'l': options.logo = true break case 'c': options.c51 = true break } } } return options } private addSl( x: number, stdout: SimpleStream, cols: number, lines: number, options: SlOptions, ): boolean { if (x < -Sl.LOGO_LENGTH) return false let y = Math.max(0, Math.floor(lines / 2) - 3) let py1 = 0 let py2 = 0 let py3 = 0 if (options.fly) { y = Math.trunc(x / 6) + lines - Math.trunc(cols / 6) - Sl.LOGO_HEIGHT py1 = 2 py2 = 4 py3 = 6 } const pattern = Math.floor((Sl.LOGO_LENGTH + x) / 3) % Sl.LOGO_PATTERNS for (let i = 0; i <= Sl.LOGO_HEIGHT; i++) { this.drawString(stdout, cols, lines, y + i, x, Sl.LOGO[pattern][i]) this.drawString(stdout, cols, lines, y + i + py1, x + 21, Sl.LOGO_COAL[i]) this.drawString(stdout, cols, lines, y + i + py2, x + 42, Sl.LOGO_CAR[i]) this.drawString(stdout, cols, lines, y + i + py3, x + 63, Sl.LOGO_CAR[i]) } if (options.accident) { this.addMan(stdout, cols, lines, y + 1, x + 14) this.addMan(stdout, cols, lines, y + 1 + py2, x + 45) this.addMan(stdout, cols, lines, y + 1 + py2, x + 53) this.addMan(stdout, cols, lines, y + 1 + py3, x + 66) this.addMan(stdout, cols, lines, y + 1 + py3, x + 74) } this.addSmoke(stdout, cols, lines, y - 1, x + Sl.LOGO_FUNNEL) return true } private addD51( x: number, stdout: SimpleStream, cols: number, lines: number, options: SlOptions, ): boolean { if (x < -Sl.D51_LENGTH) return false let y = Math.max(0, Math.floor(lines / 2) - 5) let dy = 0 if (options.fly) { y = Math.trunc(x / 7) + lines - Math.trunc(cols / 7) - Sl.D51_HEIGHT dy = 1 } const pattern = (Sl.D51_LENGTH + x) % Sl.D51_PATTERNS for (let i = 0; i <= Sl.D51_HEIGHT; i++) { this.drawString(stdout, cols, lines, y + i, x, Sl.D51[pattern][i]) this.drawString(stdout, cols, lines, y + i + dy, x + 53, Sl.D51_COAL[i]) } if (options.accident) { this.addMan(stdout, cols, lines, y + 2, x + 43) this.addMan(stdout, cols, lines, y + 2, x + 47) } this.addSmoke(stdout, cols, lines, y - 1, x + Sl.D51_FUNNEL) return true } private addC51( x: number, stdout: SimpleStream, cols: number, lines: number, options: SlOptions, ): boolean { if (x < -Sl.C51_LENGTH) return false let y = Math.max(0, Math.floor(lines / 2) - 5) let dy = 0 if (options.fly) { y = Math.trunc(x / 7) + lines - Math.trunc(cols / 7) - Sl.C51_HEIGHT dy = 1 } const pattern = (Sl.C51_LENGTH + x) % Sl.C51_PATTERNS for (let i = 0; i <= Sl.C51_HEIGHT; i++) { this.drawString(stdout, cols, lines, y + i, x, Sl.C51[pattern][i]) this.drawString(stdout, cols, lines, y + i + dy, x + 55, Sl.C51_COAL[i]) } if (options.accident) { this.addMan(stdout, cols, lines, y + 3, x + 45) this.addMan(stdout, cols, lines, y + 3, x + 49) } this.addSmoke(stdout, cols, lines, y - 1, x + Sl.C51_FUNNEL) return true } private addMan( stdout: SimpleStream, cols: number, lines: number, y: number, x: number, ) { const man = [['', '(O)'], ['Help!', '\\O/']] const index = Math.floor((Sl.LOGO_LENGTH + x) / 12) % 2 for (let i = 0; i < 2; i++) this.drawString(stdout, cols, lines, y + i, x, man[index][i]) } private addSmoke( stdout: SimpleStream, cols: number, lines: number, y: number, x: number, ) { if (x % 4 !== 0) return for (let i = 0; i < this.smokeSum; i++) { const smoke = this.smokes[i] this.drawString(stdout, cols, lines, smoke.y, smoke.x, Sl.SMOKE_ERASER[smoke.ptrn]) smoke.y -= Sl.SMOKE_DY[smoke.ptrn] smoke.x += Sl.SMOKE_DX[smoke.ptrn] smoke.ptrn = smoke.ptrn < Sl.SMOKE_PATTERNS - 1 ? smoke.ptrn + 1 : smoke.ptrn this.drawString(stdout, cols, lines, smoke.y, smoke.x, Sl.SMOKE[smoke.kind][smoke.ptrn]) } this.drawString(stdout, cols, lines, y, x, Sl.SMOKE[this.smokeSum % 2][0]) if (this.smokeSum < Sl.MAX_SMOKE) { this.smokes[this.smokeSum] = { y, x, ptrn: 0, kind: this.smokeSum % 2 } this.smokeSum++ } } private drawString( stdout: SimpleStream, cols: number, lines: number, y: number, x: number, text: string, ) { if (y < 0 || y >= lines) return let cursorX = x let out = text if (cursorX < 0) { out = out.slice(-cursorX) cursorX = 0 } if (cursorX >= cols || out.length === 0) return if (cursorX + out.length > cols) out = out.slice(0, cols - cursorX) stdout.emit(`\x1b[${y + 1};${cursorX + 1}H${out}`) } private resetSmoke() { this.smokes = [] this.smokeSum = 0 } private sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)) } private static readonly LOGO: string[][] = [ [ ' ++ +------ ', ' || |+-+ | ', ' /---------|| | | ', ' + ======== +-+ | ', ' _|--O========O~\\-+ ', '//// \\_/ \\_/ ', ' ', ], [ ' ++ +------ ', ' || |+-+ | ', ' /---------|| | | ', ' + ======== +-+ | ', ' _|--/O========O\\-+ ', '//// \\_/ \\_/ ', ' ', ], [ ' ++ +------ ', ' || |+-+ | ', ' /---------|| | | ', ' + ======== +-+ | ', ' _|--/~O========O-+ ', '//// \\_/ \\_/ ', ' ', ], [ ' ++ +------ ', ' || |+-+ | ', ' /---------|| | | ', ' + ======== +-+ | ', ' _|--/~\\------/~\\-+ ', '//// \\_O========O ', ' ', ], [ ' ++ +------ ', ' || |+-+ | ', ' /---------|| | | ', ' + ======== +-+ | ', ' _|--/~\\------/~\\-+ ', '//// \\O========O/ ', ' ', ], [ ' ++ +------ ', ' || |+-+ | ', ' /---------|| | | ', ' + ======== +-+ | ', ' _|--/~\\------/~\\-+ ', '//// O========O_/ ', ' ', ], ] private static readonly LOGO_COAL = [ '____ ', '| \\@@@@@@@@@@@ ', '| \\@@@@@@@@@@@@@_ ', '| | ', '|__________________| ', ' (O) (O) ', ' ', ] private static readonly LOGO_CAR = [ '____________________ ', '| ___ ___ ___ ___ | ', '| |_| |_| |_| |_| | ', '|__________________| ', '|__________________| ', ' (O) (O) ', ' ', ] private static readonly D51 = [ [ ' ==== ________ ___________ ', ' _D _| |_______/ \\__I_I_____===__|_________| ', ' |(_)--- | H\\________/ | | =|___ ___| ', ' / | | H | | | | ||_| |_|| ', ' | | | H |__--------------------| [___] | ', ' | ________|___H__/__|_____/[][]~\\_______| | ', ' |/ | |-----------I_____I [][] [] D |=======|__ ', '__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__ ', ' |/-=|___|= || || || |_____/~\\___/ ', ' \\_/ \\O=====O=====O=====O_/ \\_/ ', ' ', ], [ ' ==== ________ ___________ ', ' _D _| |_______/ \\__I_I_____===__|_________| ', ' |(_)--- | H\\________/ | | =|___ ___| ', ' / | | H | | | | ||_| |_|| ', ' | | | H |__--------------------| [___] | ', ' | ________|___H__/__|_____/[][]~\\_______| | ', ' |/ | |-----------I_____I [][] [] D |=======|__ ', '__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__ ', ' |/-=|___|=O=====O=====O=====O |_____/~\\___/ ', ' \\_/ \\__/ \\__/ \\__/ \\__/ \\_/ ', ' ', ], [ ' ==== ________ ___________ ', ' _D _| |_______/ \\__I_I_____===__|_________| ', ' |(_)--- | H\\________/ | | =|___ ___| ', ' / | | H | | | | ||_| |_|| ', ' | | | H |__--------------------| [___] | ', ' | ________|___H__/__|_____/[][]~\\_______| | ', ' |/ | |-----------I_____I [][] [] D |=======|__ ', '__/ =| o |=-O=====O=====O=====O \\ ____Y___________|__ ', ' |/-=|___|= || || || |_____/~\\___/ ', ' \\_/ \\__/ \\__/ \\__/ \\__/ \\_/ ', ' ', ], [ ' ==== ________ ___________ ', ' _D _| |_______/ \\__I_I_____===__|_________| ', ' |(_)--- | H\\________/ | | =|___ ___| ', ' / | | H | | | | ||_| |_|| ', ' | | | H |__--------------------| [___] | ', ' | ________|___H__/__|_____/[][]~\\_______| | ', ' |/ | |-----------I_____I [][] [] D |=======|__ ', '__/ =| o |=-~O=====O=====O=====O\\ ____Y___________|__ ', ' |/-=|___|= || || || |_____/~\\___/ ', ' \\_/ \\__/ \\__/ \\__/ \\__/ \\_/ ', ' ', ], [ ' ==== ________ ___________ ', ' _D _| |_______/ \\__I_I_____===__|_________| ', ' |(_)--- | H\\________/ | | =|___ ___| ', ' / | | H | | | | ||_| |_|| ', ' | | | H |__--------------------| [___] | ', ' | ________|___H__/__|_____/[][]~\\_______| | ', ' |/ | |-----------I_____I [][] [] D |=======|__ ', '__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__ ', ' |/-=|___|= O=====O=====O=====O|_____/~\\___/ ', ' \\_/ \\__/ \\__/ \\__/ \\__/ \\_/ ', ' ', ], [ ' ==== ________ ___________ ', ' _D _| |_______/ \\__I_I_____===__|_________| ', ' |(_)--- | H\\________/ | | =|___ ___| ', ' / | | H | | | | ||_| |_|| ', ' | | | H |__--------------------| [___] | ', ' | ________|___H__/__|_____/[][]~\\_______| | ', ' |/ | |-----------I_____I [][] [] D |=======|__ ', '__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__ ', ' |/-=|___|= || || || |_____/~\\___/ ', ' \\_/ \\_O=====O=====O=====O/ \\_/ ', ' ', ], ] private static readonly D51_COAL = [ ' ', ' ', ' _________________ ', ' _| \\_____A ', ' =| | ', ' -| | ', '__|________________________|_ ', '|__________________________|_ ', ' |_D__D__D_| |_D__D__D_| ', ' \\_/ \\_/ \\_/ \\_/ ', ' ', ] private static readonly C51 = [ [ ' ___ ', ' _|_|_ _ __ __ ___________', ' D__/ \\_(_)___| |__H__| |_____I_Ii_()|_________|', ' | `---\' |:: `--\' H `--\' | |___ ___| ', ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ', ' || | :: H +=====+ | |:: ...| ', '| | _______|_::-----------------[][]-----| | ', '| /~~ || |-----/~~~~\\ /[I_____I][][] --|||_______|__', '------\'|oOo|=[]=- || || | ||=======_|__', '/~\\____|___|/~\\_| O=======O=======O |__|+-/~\\_| ', '\\_/ \\_/ \\____/ \\____/ \\____/ \\_/ ', ' ', ], [ ' ___ ', ' _|_|_ _ __ __ ___________', ' D__/ \\_(_)___| |__H__| |_____I_Ii_()|_________|', ' | `---\' |:: `--\' H `--\' | |___ ___| ', ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ', ' || | :: H +=====+ | |:: ...| ', '| | _______|_::-----------------[][]-----| | ', '| /~~ || |-----/~~~~\\ /[I_____I][][] --|||_______|__', '------\'|oOo|=[]=- O=======O=======O | ||=======_|__', '/~\\____|___|/~\\_| || || |__|+-/~\\_| ', '\\_/ \\_/ \\____/ \\____/ \\____/ \\_/ ', ' ', ], [ ' ___ ', ' _|_|_ _ __ __ ___________', ' D__/ \\_(_)___| |__H__| |_____I_Ii_()|_________|', ' | `---\' |:: `--\' H `--\' | |___ ___| ', ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ', ' || | :: H +=====+ | |:: ...| ', '| | _______|_::-----------------[][]-----| | ', '| /~~ || |-----/~~~~\\ /[I_____I][][] --|||_______|__', '------\'|oOo|==[]=- O=======O=======O | ||=======_|__', '/~\\____|___|/~\\_| || || |__|+-/~\\_| ', '\\_/ \\_/ \\____/ \\____/ \\____/ \\_/ ', ' ', ], [ ' ___ ', ' _|_|_ _ __ __ ___________', ' D__/ \\_(_)___| |__H__| |_____I_Ii_()|_________|', ' | `---\' |:: `--\' H `--\' | |___ ___| ', ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ', ' || | :: H +=====+ | |:: ...| ', '| | _______|_::-----------------[][]-----| | ', '| /~~ || |-----/~~~~\\ /[I_____I][][] --|||_______|__', '------\'|oOo|===[]=- O=======O=======O | ||=======_|__', '/~\\____|___|/~\\_| || || |__|+-/~\\_| ', '\\_/ \\_/ \\____/ \\____/ \\____/ \\_/ ', ' ', ], [ ' ___ ', ' _|_|_ _ __ __ ___________', ' D__/ \\_(_)___| |__H__| |_____I_Ii_()|_________|', ' | `---\' |:: `--\' H `--\' | |___ ___| ', ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ', ' || | :: H +=====+ | |:: ...| ', '| | _______|_::-----------------[][]-----| | ', '| /~~ || |-----/~~~~\\ /[I_____I][][] --|||_______|__', '------\'|oOo|===[]=- || || | ||=======_|__', '/~\\____|___|/~\\_| O=======O=======O |__|+-/~\\_| ', '\\_/ \\_/ \\____/ \\____/ \\____/ \\_/ ', ' ', ], [ ' ___ ', ' _|_|_ _ __ __ ___________', ' D__/ \\_(_)___| |__H__| |_____I_Ii_()|_________|', ' | `---\' |:: `--\' H `--\' | |___ ___| ', ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ', ' || | :: H +=====+ | |:: ...| ', '| | _______|_::-----------------[][]-----| | ', '| /~~ || |-----/~~~~\\ /[I_____I][][] --|||_______|__', '------\'|oOo|==[]=- || || | ||=======_|__', '/~\\____|___|/~\\_| O=======O=======O |__|+-/~\\_| ', '\\_/ \\_/ \\____/ \\____/ \\____/ \\_/ ', ' ', ], ] private static readonly C51_COAL = [ ' ', ' ', ' ', ' _________________ ', ' _| \\_____A ', ' =| | ', ' -| | ', '__|________________________|_ ', '|__________________________|_ ', ' |_D__D__D_| |_D__D__D_| ', ' \\_/ \\_/ \\_/ \\_/ ', ' ', ] private static readonly SMOKE = [ [ '( )', '( )', '( )', '( )', '( )', '( )', '( )', '( )', '()', '()', 'O', 'O', 'O', 'O', 'O', ' ', ], [ '(@@@)', '(@@@@)', '(@@@@)', '(@@@)', '(@@)', '(@@)', '(@)', '(@)', '@@', '@@', '@', '@', '@', '@', '@', ' ', ], ] private static readonly SMOKE_ERASER = [ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ] private static readonly SMOKE_DY = [2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] private static readonly SMOKE_DX = [-2, -1, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3] }