はじめに
この本を参考に、ターミナルでAtomが動くバチクソかっこいいサンプルをDenoに移植しました。
こんな感じで動きます。
ページをめくるたびに失神するほどかっこいいので、みんなで読んで失神しよう。
実行してみる
denoはURLを渡すだけで直接実行できます。
何はともあれ実行してみてください。
deno run --unstable https://gist.githubusercontent.com/zakuroishikuro/dc92d9d6a873e24f40ace683df177245/raw/6b48afbcd41d228ab52e6b4b8f802292a9abce0d/atom.ts
画面の大きさを取得するDeno.consoleSize
を使うために--unstable
フラグが必要。
画面をデカくすればするほどカッコいいよ。
Ctrl+cで終了してね。
元のソースはこれ。
どうやって動かしてるの?
画面全体に文字列を出力して、消して、また出力してるだけ。
消すってなんだよ!?
普通の文字とは違う特殊な文字(エスケープシーケンス)を出力すると、何も表示されず不思議なことが起きます。
ここで使ってるのは二つ。
-
"\x1b[2J"
: 画面全体を消去 -
"\x1b[1:1H"
: カーソルを左上に移動
他にもいろいろあります。一覧はこちら。
もっと詳しく知りたい人はこちら。歴史的経緯も分かってめちゃくちゃ面白い。
で、JavaScriptで文字列を出力するといったらconsole.log
なんだけど
これ使うと最後に常に改行されるので、改行せずに文字列を出力する関数を作っておく。
文字列をUint8Arrayに変換して標準出力(Deno.stdout)にブチ込む。
const encoder = new TextEncoder();
/** console.logだと毎回改行されて困るので、改行せず文字列を出力する関数 */
async function print(str: string) {
const bytes = encoder.encode(str);
await Deno.stdout.write(bytes);
}
これでprint("\x1b[2J")
すれば改行せずに画面を消せる。
ちなみにRubyでは改行するのがputs
関数で、改行しないのがprint
関数なのでそれに倣う。
sleep関数でn秒ディレイする
ループ内で待つ処理を挟むために、awaitでn秒待つsleep関数を作りました。
setIntervalでやってもいいんだけど、元のイカすソースに似せたかった。
/** n秒待つ関数 (await忘れずに) */
function sleep(sec: number) {
return new Promise((res) => {
setTimeout(() => res(null), sec * 1000);
});
}
これでawait sleep(1)
すれば処理が1秒中断されます。
Denoではトップレベルawaitが有効なのでasyncするために関数の中で実行しなくて済む。
サブキャラクターレンダリング
半角文字は縦に長い。
半角1文字をタテに2ドットあると見立ててコンマやコロンなどを駆使し1文字で2ドット分の情報を描画する手法。
こういうとき以外で使うかは知らない。
ドット | サブ |
---|---|
□ □ |
[ ] |
■ □ |
['] |
□ ■ |
[,] |
■ ■ |
[;] |
点字 使ったらもっとすごそう
ソース全体
肝心の処理部分。
複素数が必要だったのでSkypackからmathjsをimportしてる。
// deno run --unstable https://raw.githubusercontent.com/zakuroishikuro/sharin/main/asobu/atom.ts
//
// original source code:
// https://github.com/mame/trance-book/blob/master/8-1/subcharacter-rendering-demo.rb
//
// from:
// あなたの知らない超絶技巧プログラミングの世界 by 遠藤侑介
// 8-1-2 アスキーアートの生成(1):サブキャラクターレンダリング
import { complex, multiply } from "https://cdn.skypack.dev/mathjs";
/** n秒待つ関数 (await忘れずに) */
function sleep(sec: number) {
return new Promise((res) => {
setTimeout(() => res(null), sec * 1000);
});
}
const encoder = new TextEncoder();
/** console.logだと毎回改行されて困るので、改行せず文字列を出力する関数 */
async function print(str: string) {
const bytes = encoder.encode(str);
await Deno.stdout.write(bytes);
}
// コンソールの大きさを取得。--unstalbeフラグが必要。
const {rows} = Deno.consoleSize(Deno.stdout.rid)
const S = rows; // 描画領域のの
const A = S / 2.1; // 長径
const B = S / 8.0; // 短径
print("\x1b[2J"); // 画面クリア
let n = 0;
while (true) {
// バッファ
const s = [...Array(S)].map(() => [..." ".repeat(S * 2)]);
[...Array(100)].forEach((_, i) => {
// 楕円の媒介変数表示
const t = Math.PI * 2 * (i + n) / 100;
const e = complex(A * Math.cos(t), B * Math.sin(t));
// 楕円を 3 つ描く
[-1, 0, 1].forEach((j) => {
// 楕円を傾ける
const e2 = multiply(
e,
complex({ r: 1.0, phi: Math.PI * 2 / 3 * j + n / 500 }),
);
// ドットを置く
const x = Math.floor(e2.re * 2 + S);
const y = Math.floor(e2.im * 2 + S);
const row = s[Math.floor(y / 2)];
if (i === 99) {
[row[x - 1], row[x]] = "()";
} else if (y % 2 == 0) {
row[x] = row[x] == " " || row[x] == "'" ? "'" : ";";
} else {
row[x] = row[x] == " " || row[x] == "," ? "," : ";";
}
});
});
print("\x1b[1;1H" + s.map((row) => row.join("")).join("\n"));
await sleep(0.03);
n++;
}
移植しただけの自分にはたいして解説できねぇ。すまねぇ。
ソースコードとにらめっこして解読してほしい。ごめんなさい。
おしり。