
Created 2025-10-21
/** A Quick Example! ๐ถ */
//โ "dog"
const text = echo("dog");
//โ [ "d", "o", "g" ]
const chars = echo(text.split(""));
//โ "god"
echo(chars.slice().reverse().join(""));

Created 2025-10-14
//โ {---CLE}{CI----}{-IRCLE}{CIR---}{------}{------}{CIRCLE}{C-----}{CIRCLE}{--
//โ ---}{CIRC--}{----LE}{CIRC--}{------}{------}{------}{CIRCLE}{------}{CIRC--
//โ {-IRCLE}{------}{CIRCLE}{------}{CIRCLE}{CI----}{----LE}{CIRC--}{---CLE}{C-
//โ -LE}{CI----}{-IRCLE}{------}{CIRCLE}{CIRCLE}{CI----}{--RCLE}{C-----}{CIRCLE
//โ {CIRCLE}{----LE}{CIR---}{--RCLE}{CIRCLE}{CIRCLE}{------}{CIRCLE}{----LE}{CI
//โ CLE}{C-----}{CIRCLE}{-----E}{CIRCL-}{-----E}{CIRCL-}{----LE}{CIR---}{-IRCLE
//โ {CIRCL-}{---CLE}{C-----}{CIRCLE}{------}{---CLE}{CIR---}{-IRCLE}{-----E}{CI
//โ CLE}{------}{CIRCL-}{---CLE}{C-----}{------}{-IRCLE}{-----E}{CIR---}{--RCLE
//โ {CIRC--}{--RCLE}{------}{CIRC--}{---CLE}{------}{CIRC--}{--RCLE}{------}{CI
//โ CLE}{------}{CIRC--}{--RCLE}{------}{CIRC--}{--RCLE}{------}{CIRC--}{--RCLE
//โ {CIRC--}{--RCLE}{------}{CIRC--}{---CLE}{------}{CIRC--}{--RCLE}{------}{CI
//โ CLE}{------}{CIRCL-}{---CLE}{C-----}{------}{-IRCLE}{-----E}{CIR---}{--RCLE
//โ {CIRCL-}{---CLE}{C-----}{CIRCLE}{------}{---CLE}{CIR---}{-IRCLE}{-----E}{CI
//โ CLE}{C-----}{CIRCLE}{-----E}{CIRCL-}{-----E}{CIRCL-}{----LE}{CIR---}{-IRCLE
//โ {CIRCLE}{----LE}{CIR---}{--RCLE}{CIRCLE}{CIRCLE}{------}{CIRCLE}{----LE}{CI
//โ -LE}{CI----}{-IRCLE}{------}{CIRCLE}{CIRCLE}{CI----}{--RCLE}{C-----}{CIRCLE
//โ {-IRCLE}{------}{CIRCLE}{------}{CIRCLE}{CI----}{----LE}{CIRC--}{---CLE}{C-
//โ ---}{CIRC--}{----LE}{CIRC--}{------}{------}{------}{CIRCLE}{------}{CIRC--
{
const backgroundChars = '{------}';
const foregroundChars = '{CIRCLE}';
const layers = recho.number(3, {min: 1, max: 6, step: 1});
const frame = new Array(rows).fill(0).map((_,r,rowArray)=>{
return new Array(cols).fill(0).map((_,c,colArray)=>{
const t = progress(frameCount);
const y = r/rowArray.length-0.5;
const x = (c/colArray.length-0.5)*aspectRatio;
const backgroundCharOffset = r*Math.floor(backgroundChars.length/2);
const backgroundCharIndex = (c+backgroundCharOffset)%backgroundChars.length;
const foregroundCharOffset = r*Math.floor(foregroundChars.length/2);
const foregroundCharIndex = (c+foregroundCharOffset)%foregroundChars.length;
const radius = Math.sqrt(x*x+y*y);
const char = Math.sin(radius*TAU*layers+t*TAU) > 0 ?
backgroundChars[backgroundCharIndex] : foregroundChars[foregroundCharIndex];
return char;
}).join('');
}).join('\n');
echo(frame);
}
//โ ____________/______________\//\\/___________//\\//____________\\/__________
//โ _________\//______________//\\//\\//_____\//\\//\\_______________\_________
//โ _______\//\\______________\\//\\//\\//_\//\\//\\//_______________/\\/______
//โ ____\\//\\//______________//\\//\\//\__/\\//\\//\\______________\\//\\/____
//โ __\\//\\//\\______________\\//\\//\\____//\\//\\//______________//\\//\\/__
//โ _\//\\//\\//\______________/\\//\\//____\\//\\//\\______________\\//\\//\\/
//โ //\\//\\//\\//_______/\\//\______/\\_____/\______/\\//_________\//\\//\\//\
//โ \\//\\//\\//\\____//\\//\\//\___________________\\//\\//\_____//\\//\\//\\/
//โ //\\//\\//\\//\\//\\//\\//\\//\_______________\\//\\//\\//\\_/\\//\\//\\//\
//โ \\//\\//\\//\\/__\//\\//\\//\\//\__/\\//\___\\//\\//\\//\\/___//\\//\\//\\/
//โ //\\//\\//\\//\_//\\//\\//\\//_______________/\\//\\//\\//\\//\\//\\//\\//\
//โ \\//\\//\\//\\_____/\\//\\//___________________/\\//\\//\\____//\\//\\//\\/
//โ //\\//\\//\\/_________\\//\______/\_____//\______/\\//\_______\\//\\//\\//\
//โ _\//\\//\\//______________//\\//\\//____\\//\\//\______________/\\//\\//\\/
//โ ___\//\\//\\______________\\//\\//\\____//\\//\\//______________//\\//\\//_
//โ _____\//\\//______________//\\//\\//\__/\\//\\//\\______________\\//\\//___
//โ _______\//\_______________\\//\\//\\/_\\//\\//\\//______________//\\/______
//โ __________/_______________//\\//\\/_____\\//\\//\\______________\\/________
{
const backgroundChars = '_';
const foregroundChars = '//\\\\';
const layers = recho.number(2, {min: 1, max: 6, step: 1});
const layersAmplitude = recho.number(1, {min: 0, max: 3, step: 1});
const waveDensity = recho.number(1, {min: 0, max: 3, step: 1});
const waveAmplitude = recho.number(1, {min: 0, max: 2, step: 0.5});
const frame = new Array(rows).fill(0).map((_,r,rowArray)=>{
return new Array(cols).fill(0).map((_,c,colArray)=>{
const t = progress(frameCount);
const y = r/rowArray.length-0.5;
const x = (c/colArray.length-0.5)*aspectRatio;
const backgroundCharOffset = r*Math.floor(backgroundChars.length/2);
const backgroundCharIndex = (c+backgroundCharOffset)%backgroundChars.length;
const foregroundCharOffset = r*Math.floor(foregroundChars.length/2);
const foregroundCharIndex = (c+foregroundCharOffset)%foregroundChars.length;
const radius = Math.sqrt(x*x+y*y);
const char = Math.sin(radius*TAU*layers+TAU*layersAmplitude*Math.sin(t*TAU)) >
waveAmplitude*Math.sin(t*TAU+waveDensity*TAU*Math.atan(x/y)) ?
backgroundChars[backgroundCharIndex] : foregroundChars[foregroundCharIndex];
return char;
}).join('');
}).join('\n');
echo(frame);
}

Created 2025-09-30

Created 2025-09-29
const radius = recho.number(4, {min: 3, max: 10});
//โ xxxxx
//โ xx xx
//โ xx xx
//โ x x
//โ x x
//โ x x
//โ xx xx
//โ xx xx
//โ xxxxx
{
const size = radius * 2 + 1;
let result = '';
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const distance = Math.sqrt((x - radius) ** 2 + (y - radius) ** 2);
result += Math.abs(distance - radius) < 0.5 ? 'x' : ' ';
}
result += '\n';
}
echo(result.trimEnd());
}
// Any JavaScript numeric literal is supported. If you use hexadecimal, octal,
// or binary numeric literals, the base format of the number will be preserved.
//โ "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
{
const leading = "โ".repeat(Math.clz32(bitMask));
const remaining = bitMask.toString(2).replaceAll(/1/g, "โ").replaceAll(/0/g, "โ");
echo(leading + remaining);
}
const bitMask = recho.number(0b111100010101, {step: 0b10101010});

Created 2025-09-27

Created 2025-09-26
*/
const a = 1;
//โ 2
const b = echo(a + 1);
const c = a + b;
//โ 3
echo(c);

Created 2025-09-26
//โ [ "0.70", "0.42", "0.93", "0.50", "0.59", "0.89", "0.03", "0.85", "0.38", "0.21" ]
let numbers = echo(new Array(10).fill(0).map(() => Math.random().toFixed(2)));
//โ 0.93
let max = echo(Math.max(...numbers));
let t = 10;
//โ 9.3
const scale = echo(max * t);

Created 2025-09-25
//โ false
echo(0.1 + 0.2 === 0.3);
// https://github.com/denysdovhan/wtfjs?tab=readme-ov-file#precision-of-01--02
//โ "baNaNa"
echo("b" + "a" + +"a" + "a");
// https://github.com/denysdovhan/wtfjs?tab=readme-ov-file#banana
//โ "fail"
echo(
(![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]]
)
// https://github.com/denysdovhan/wtfjs?tab=readme-ov-file#its-a-fail
//โ 15
echo(parseInt("f*ck", 16));
// https://github.com/denysdovhan/wtfjs?tab=readme-ov-file#parseint-is-a-bad-guy
//โ "1,2,34,5,6"
echo([1, 2, 3] + [4, 5, 6]);
// https://github.com/denysdovhan/wtfjs?tab=readme-ov-file#adding-arrays
//โ "first"
foo: {
echo("first");
break foo;
echo("second");
}
// https://github.com/denysdovhan/wtfjs?tab=readme-ov-file#labels
//โ 1
//โ 2
//โ 3
//โ 4
//โ 5
a: b: c: d: e: f: g: echo(1), echo(2), echo(3), echo(4), echo(5);
// https://github.com/denysdovhan/wtfjs?tab=readme-ov-file#labels
//โ [ 1, 10, 3 ]
echo([10, 1, 3].sort());
// https://github.com/denysdovhan/wtfjs?tab=readme-ov-file#default-behavior-arrayprototypesort

Created 2025-09-23
const response = await fetch("https://raw.githubusercontent.com/dariusk/corpora/refs/heads/master/data/art/isms.json");
//โ {
//โ description: "A list of modernist art isms.",
//โ isms: [ "abstract expressionism", "academic", "action painting", "aestheticism", "art deco", "art nouveau", "avant-garde", "barbizon school", "baroque", "bauhaus", "biedermeier", "caravaggisti", "carolingian", "classicism", "cloisonnism", "cobra", "color field painting", "conceptual art", "cubism", "cubo-futurism", "dada", "dadaism", "de stijl", "deformalism", "der blaue reiter", "die brรผcke", "divisionism", "eclecticism", "ego-futurism", "existentialism", "expressionism", "fauvism", "fluxus", "formalism", "futurism", "geometric abstraction", "gothic art", "grรผnderzeit", "hard-edge painting", "historicism", "hudson river school", "humanism", "hyperrealism", "idealism", "illusionism", "immagine&poesia", "impressionism", "incoherents", "installation art", "international gothic", "intervention art", "jugendstil", "kinetic art", "land art", "les nabis", "lettrism", "lowbrow", "luminism", "lyrical abstraction", "mail art", "manierism", "mannerism", "maximalism", "merovingian", "metaphysical art ", "minimalism", "modern art", "modernism", "monumentalism", "multiculturalism", "naturalism", "neo-classicism", "neo-dada", "neo-expressionism", "neo-fauvism", "neo-geo", "neo-impressionism", "neo-minimalism", "neoclassicism", "neoism", "neue slowenische kunst", "new media art", "new objectivity", "nonconformism", "nouveau realisme", "op art", "orphism", "ottonian", "outsider art", "performance art", "perspectivism", "photorealism", "pointilism", "pop art", "post-conceptualism", "post-impressionism", "post-minimalism", "post-painterly abstraction", "post-structuralism", "postminimalism", "postmodern art", "postmodernism", "pre-raphaelites", "precisionism", "primitivism", "purism", "rayonism", "realism", "relational art", "remodernism", "renaissance", "rococo", "romanesque", "romanticism", "russian futurism", "russian symbolism", "scuola romana", "secularism", "situationist international", "social realism", "socialist realism", "sound art", "street art", "structuralism", "stuckism international", "stuckism", "superflat", "superstroke", "suprematism", "surrealism", "symbolism", "synchromism", "synthetism", "systems art", "tachism", "tachisme", "tonalism", "video art", "video game art", "vorticism", "young british artists" ]
//โ }
const data = echo(await response.json(), {indent: 2, limit: Infinity});
//โ "academic"
echo(data.isms[frame % data.isms.length]);
const frame = recho.interval(1000);

Created 2025-09-23
const nlp = await recho.require("compromise");
//โ View { ptrs: undefined }
const doc = echo(nlp("she sells seashells by the seashore."));
//โ View { ptrs: [ [ 0, 1, 2, "sold|0JG000003", "sold|0JG000003" ] ] }
echo(doc.verbs().toPastTense());
//โ "she sells seashells by the seashore."
echo(doc.text());
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Finding and Matching Patterns
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Next, we can find and match patterns in the sentence. Similar to regex, but
* we match with meaning instead of symbols.
*/
//โ true
echo(doc.has("she #Verb"));
//โ "she sells seashells by the seashore."
echo(doc.match("she #Verb seashells by the seashore").text());
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Getting Data
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* We can extend the library with plugins. Then we can get the data from the
* sentence.
*/
const plg = await recho.require("compromise-speech");
nlp.extend(plg);
const doc2 = nlp("Milwaukee has certainly had its share of visitors..");
doc2.compute("syllables");
//โ [
//โ {
//โ text: "Milwaukee",
//โ terms: [
//โ {
//โ text: "Milwaukee",
//โ pre: "",
//โ post: " ",
//โ tags: [ "Noun", "Singular", "Place", "ProperNoun", "City" ],
//โ normal: "milwaukee",
//โ index: [ 0, 0 ],
//โ id: "milwaukee|0IE00000J",
//โ chunk: "Noun",
//โ dirty: true,
//โ syllables: [ "mil", "waukee" ]
//โ }
//โ ]
//โ }
//โ ]
echo(doc2.places().json(), {indent: 2, limit: Infinity});
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Avoiding Brittle Parsers
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* We can avoid the problems of brittle parsers:
*/
const doc3 = nlp("we're not gonna take it..");
//โ true
echo(doc3.has("gonna"));
//โ true
echo(doc3.has("going to"));
//โ Contractions { ptrs: [] }
echo(doc.contractions().expand());
//โ "she sells seashells by the seashore."
echo(doc.text());
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Whipping Stuff Around
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* We can whip stuff around like it's data:
*/
//โ "ninety five thousand and seventy two"
{
const doc = nlp("ninety five thousand and fifty two");
doc.numbers().add(20);
echo(doc.text());
}
/**
* because it's actually is:
*/
//โ "the purple dinosaurs"
{
const doc = nlp("the purple dinosaur");
doc.nouns().toPlural();
echo(doc.text());
}

Created 2025-09-23
//โ %%
//โ +-* #*+##%
//โ #--:..:=::-=# %+*
//โ #*:-::+*=-+ #*-:::-%
//โ #=:-=---:**= #-:::::::+
//โ %----------*%=----+#%
//โ :-:----:::: #==
//โ *::-::=::-::*#=+*%
//โ #----:-=--::-- ##
//โ +--:::::------%
//โ ##+#% %#*=+#
//โ
//โ
//โ
//โ
//โ
//โ
//โ
//โ
{
echo(recho.inspect(`โก Type: ${pokemon.types[0].type.name.toUpperCase()}`, {quote: false}));
echo(recho.inspect(`โ๏ธ Weight: ${pokemon.weight}`, {quote: false}));
echo(recho.inspect(`๐ Height: ${pokemon.height}`, {quote: false}));
echo(recho.inspect("", {quote: false}));
img2ASCIIString(pokemon.sprites.front_default).then(echo);
}
/**
* Originally, I wanted to create an example to visualize a Pokemon in ASCII
* art. But I found it's not easy to tell under the current transformation
* from pixels to characters. So I decided to make it a guessing game.
*
* In addition, this is also a good showcase for the following features:
*
* - How to use Data API in Recho
* - How to draw image in Recho
* - How to use inputs in Recho
*/
const pokemon = await getPokemon(search || selected);
async function getPokemon(name) {
const pokemon = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`);
return pokemon.json();
}
async function img2ASCIIString(url, cols = 60, rows = 25) {
const asciiChars = " %#*+=-:. "; // From black to white.
// Load the image.
const img = await new Promise((resolve, reject) => {
const image = new Image();
image.crossOrigin = "Anonymous";
image.onload = () => resolve(image);
image.onerror = reject;
image.src = url;
});
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
const imgData = ctx.getImageData(0, 0, img.width, img.height).data;
const cellWidth = img.width / cols;
const cellHeight = img.height / rows;
let ascii = "";
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
// Compute the range of the cell.
const startX = Math.floor(x * cellWidth);
const startY = Math.floor(y * cellHeight);
const endX = Math.floor((x + 1) * cellWidth);
const endY = Math.floor((y + 1) * cellHeight);
// Compute the brightness of the cell.
let sum = 0;
let count = 0;
for (let py = startY; py < endY; py++) {
for (let px = startX; px < endX; px++) {
const idx = (py * img.width + px) * 4;
const r = imgData[idx];
const g = imgData[idx + 1];
const b = imgData[idx + 2];
const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
sum += brightness;
count++;
}
}
const avg = sum / count;
const charIndex = Math.floor((avg / 255) * (asciiChars.length - 1));
ascii += asciiChars[charIndex];
}
ascii += "\n";
}
return ascii;
}

Created 2025-09-21
//โ .............@..................................................................................
//โ ....................@...........................................................................
//โ ....@.......@...................................................................................
//โ ....@..............@.........@..........................@.......................................
//โ ...........@..................................................@.................................
//โ ....@.............@........@...................@.........@....@.................................
//โ ........................@.............................................@.........................
//โ ................................................@........@....@.................................
//โ ....@....@.....@.................................@...................@..........................
//โ ...................@................................................@...........................
//โ ..................................@......................@......................................
//โ .....................................................@.......@..................................
//โ .............................@...................................@..............................
//โ ................................................................................................
//โ ......................@................@...@....................................................
//โ ..............@.................................................................................
//โ ....@............................................@..............................................
//โ ......................................................@.........................................
//โ .............................................................@..................................
//โ ................................................................................................
//โ ................................................................................................
//โ ................................................................................................
//โ ................................................................................................
//โ ................................................................................................
//โ ................................................................................................
//โ ................................................................................................
//โ ................................................................................................
//โ ................................................................................................
//โ ................................................................................................
handPose.detectStart(video, (hands) => {
const buffer = d3.range(width * height).map(() => ".");
for (let i = 0; i < hands.length; i++) {
const hand = hands[i];
for (let j = 0; j < hand.keypoints.length; j++) {
const points = hand.keypoints[j];
const x = ~~scaleX(points.x);
const y = ~~scaleY(points.y);
if (x > 0 && x < width && y > 0 && y < height) buffer[y * width + x] = "@";
}
}
let output = "";
for (let i = 0; i < height; ++i) {
for (let j = 0; j < width; ++j) output += buffer[i * width + j];
output += i === height - 1 ? "" : "\n";
}
clear();
echo(output);
});
{
invalidation.then(() => removeCapture(video));
}
// Use p5 to create a capture video element.
function createCapture(width, height) {
return new Promise((resolve) => {
new p5((p) => {
p.setup = () => {
p.noCanvas();
p.noLoop();
const video = p.createCapture(p.VIDEO);
video.size(width, height);
video.hide();
resolve(video);
};
});
});
}
// Remove the capture video element.
function removeCapture(video) {
if (video) {
if (video.elt && video.elt.srcObject) {
video.elt.srcObject.getTracks().forEach((track) => track.stop());
}
video.remove();
}
}
const ml5 = await recho.require("https://unpkg.com/ml5@1/dist/ml5.js");
const p5 = await recho.require("https://unpkg.com/p5@1.2.0/lib/p5.js");
const d3 = await recho.require("d3");

Created 2025-09-14
//โ *+===~~----::::::
//โ #*++==~~~----:::::...::
//โ @%#**++==~~~---:::::........:
//โ @%##**++==~~~----::::.........:
//โ @@%%##**++===~~~---:::::.........::
//โ @@@@%##**+++==~~~~----:::::........::
//โ @@@@@%%##**+++==~~~~----:::::::....:::-
//โ @@@@@%%###**+++===~~~-----::::::::::::-
//โ @@@@@@%%###**+++===~~~~------::::::::--
//โ @@@@@@@%%###***+++===~~~~~------------~
//โ @@@@@@@@@%%##***++++====~~~~~~------~~~
//โ @@@@@@@@@@%%###***++++=====~~~~~~~~~~==
//โ @@@@@@@@@@@@%%###****+++++============+
//โ @@@@@@@@@@@@@%%%###****+++++++++++++*
//โ @@@@@@@@@@@@@@%%%#####************#
//โ @@@@@@@@@@@@@@@%%%%############
//โ @@@@@@@@@@@@@@@@@@%%%%%%%%%%@
//โ @@@@@@@@@@@@@@@@@@@@@@@
//โ @@@@@@@@@@@@@@@@@
//โ
echo(
shader((vPos) => {
const z2 = 1 - vPos.dot(vPos);
if (z2 > 0) {
const dir = new Vector3(1, 1, 1);
const p = new Vector3(vPos.x, vPos.y, Math.sqrt(z2));
const intensity = 0.5 * Math.max(0, p.dot(dir));
return gray2ASCII(new Vector4(intensity, intensity, intensity, 1));
}
return gray2ASCII(new Vector4(0, 0, 0, 0));
}),
);
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Ambient Light
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* To make the sphere appear smoother, we can add ambient lighting to the
* sphere. Ambient light is constant lighting that is not affected by the
* direction of the light source.
*
* Here we simply add the ambient light to the diffuse light to obtain the
* final lighting. The sphere appears brighter, and the transitions between
* layers are more natural.
*/
//โ
//โ +=~~~---::::....:
//โ *+==~~---::::..........
//โ #**++==~~---::::..... ..
//โ %#*++===~~---::::..... .
//โ %%##*+++==~~~---::::...... ..
//โ %%%##**++===~~----::::....... ..:
//โ %%%%##**+++==~~~~---:::::.............:
//โ %%%%%##**+++===~~~----:::::...........:
//โ %%%%%%##**+++===~~~-----::::::......:::
//โ %%%%%%%##***++====~~~-----::::::::::::-
//โ %%%%%%%%##***+++====~~~~-------:::::---
//โ %%%%%%%%%###***+++====~~~~~----------~~
//โ %%%%%%%%%%%###***++++====~~~~~~~~~~~~~=
//โ %%%%%%%%%%%%###****++++=============+
//โ %%%%%%%%%%%%%####****+++++++===+++*
//โ %%%%%%%%%%%%%%####*******+++***
//โ %%%%%%%%%%%%%%%%%############
//โ %%%%%%%%%%%%%%%%%%%%%%%
//โ %%%%%%%%%%%%%%%%%
//โ
echo(
shader((vPos) => {
const z2 = 1 - vPos.dot(vPos);
const skyColor = new Vector3(0.5, 0.85, 1.5);
const ambient = new Vector3(0.2, 0.1, 0.05);
if (z2 > 0) {
const dir = new Vector3(1, 1, 1);
const p = new Vector3(vPos.x, vPos.y, Math.sqrt(z2));
const intensity = 0.5 * Math.max(0, p.dot(dir));
const diffuse = skyColor.clone().multiplyScalar(intensity);
const light = diffuse.add(ambient);
return gray2ASCII(new Vector4(light.x, light.y, light.z, 1));
}
return gray2ASCII(new Vector4(0, 0, 0, 0));
}),
);
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Rotating Sphere
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Next, we make the sphere rotate around the y-axis. We can use the time
* variable to compute the direction of the light. The key is to change the `x`
* and `z` components of the light direction.
*
* We define a time variable `uTime` using `recho.now()`, which is a generator
* that yields the current time continuously. Every time the time variable
* changes, the referencing block (shader) will be re-evaluated, resulting in
* smooth animations.
*/
const uTime = recho.now();
//โ
//โ ####*****+++==~~-
//โ %%%%%%%%%####***++==~~-
//โ %%%%%%%%%%%%%%%%###**+++==~-:
//โ %%%%%%%%%%%%%%%%%%%%##***++==~-
//โ %%%%%%%%%%%%%%%%%%%%%%%%##***++=~~:
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%##**++==~-
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##**+==~-
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##**++=~
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##**+==
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#**+=
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##**+
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##*+
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#*+
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#*
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//โ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//โ %%%%%%%%%%%%%%%%%%%%%%%
//โ %%%%%%%%%%%%%%%%%
//โ
echo(
shader((vPos) => {
const theta = uTime / 400;
const sqrt2 = Math.sqrt(2);
const dx = Math.cos(theta) * sqrt2;
const dz = Math.sin(theta) * sqrt2;
const z2 = 1 - vPos.dot(vPos);
const skyColor = new Vector3(0.5, 0.85, 1.5);
const ambient = new Vector3(0.2, 0.1, 0.05);
if (z2 > 0) {
const dir = new Vector3(dx, 1, dz);
const p = new Vector3(vPos.x, vPos.y, Math.sqrt(z2));
const intensity = 0.5 * Math.max(0, p.dot(dir));
const diffuse = skyColor.clone().multiplyScalar(intensity);
const light = diffuse.add(ambient);
return gray2ASCII(new Vector4(light.x, light.y, light.z, 1));
}
return gray2ASCII(new Vector4(0, 0, 0, 0));
}),
);
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Moving Sphere
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Let's not only rotate the sphere, but also move it. This can be achieved by
* updating the position and radius of the sphere.
*/
//โ
//โ
//โ
//โ
//โ
//โ
//โ
//โ
//โ
//โ
//โ
//โ #***
//โ %%%%%%%####***
//โ %%%%%%%%%%%%%###***
//โ %%%%%%%%%%%%%%%%###**+
//โ %%%%%%%%%%%%%%%%%%%###**
//โ %%%%%%%%%%%%%%%%%%%%%%##**
//โ %%%%%%%%%%%%%%%%%%%%%%%##*
//โ %%%%%%%%%%%%%%%%%%%%%%%%#*
//โ %%%%%%%%%%%%%%%%%%%%%%%%#
//โ %%%%%%%%%%%%%%%%%%%%%%%#
//โ %%%%%%%%%%%%%%%%%%%%%%
//โ %%%%%%%%%%%%%%%%%%
//โ %%%%%%%%%%%%
//โ
//โ
//โ
//โ
//โ
//โ
//โ
echo(
shader(
(vPos) => {
const now = uTime / 2000;
const theta = uTime / 400;
const sqrt2 = Math.sqrt(2);
const dx = Math.cos(theta) * sqrt2;
const dz = Math.sin(theta) * sqrt2;
const r = 0.3 + 0.2 * Math.sin(4 * now);
const x = vPos.x + 0.5 * Math.sin(5 * now);
const y = vPos.y + 0.5 * Math.sin(7 * now);
const z2 = r * r - (x * x + y * y);
const skyColor = new Vector3(0.5, 0.85, 1.5);
const ambient = new Vector3(0.2, 0.1, 0.05);
if (z2 > 0) {
const dir = new Vector3(dx, 1, dz);
const p = new Vector3(x, y, Math.sqrt(z2));
const intensity = 0.5 * Math.max(0, p.dot(dir));
const diffuse = skyColor.clone().multiplyScalar(intensity);
const light = diffuse.add(ambient);
return gray2ASCII(new Vector4(light.x, light.y, light.z, 1));
}
return gray2ASCII(new Vector4(0, 0, 0, 0));
},
[61, 31],
),
);
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* More Spheres with more Lights
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Our final example is more complex, featuring multiple spheres and multiple
* lights. However, the concept remains the same as the previous examples.
* The only difference is that for each cell, we need to compute the color for
* each sphere, then select the one closest to the screen as the final color.
*/
//โ
//โ ####***++
//โ **###### ########***
//โ ########### ############
//โ ############# ###########
//โ ############# #########
//โ ############# #
//โ #########
//โ
//โ ####****+
//โ ########***
//โ ###########*
//โ ###########
//โ #########
//โ ###
//โ %%*****+++=
//โ %%********+++
//โ %%%***********+
//โ %%%***********
//โ %%%*********
//โ %%%*** ##***++
//โ %%###****++
//โ %%#######****
//โ %%##########**
//โ *#****+++ %###########
//โ ######****+ %%#######
//โ #########***
//โ ###########
//โ ##########
//โ #####
//โ
echo(
shader(
(vPos) => {
const scale = 5;
const now = uTime / 1000;
const ambient = new Vector3(0.2, 0.1, 0.05);
let fragColor = new Vector4(0, 0, 0, 0);
let zMax = -1000;
const L1 = new Vector3(Math.sin(now * 2), 1, 0).normalize();
const L2 = new Vector3(-1, -0.1, 0).normalize();
for (let i = 0; i < 10; i++) {
const x = scale * vPos.x + 4 * Math.sin(11.8 * i + 100.3 + 0.3 * now);
const y = scale * vPos.y + 4 * Math.sin(10.3 * i + 200.6 + 0.3 * now);
const z = scale * vPos.y + 4 * Math.cos(10.3 * i + 200.6 + 0.3 * now);
const r = 1 * 1 - (x * x + y * y);
const z1 = z + r / (scale * scale);
if (r > 0 && z1 > zMax) {
zMax = z1;
const p = new Vector3(x, y, Math.sqrt(r));
let D1 = p.clone().dot(L1);
let D2 = p.clone().dot(L2);
D1 = 0.4 * Math.max(0, D1 * Math.abs(D1));
D2 = 0.4 * Math.max(0, D1 * Math.abs(D2));
const d1 = new Vector3(0.2, 0.5, 1).multiplyScalar(D1);
const d2 = new Vector3(0.2, 0.5, 1).multiplyScalar(D2);
const diffuse = d1.add(d2);
const color = new Vector3(0.5 + 0.5 * Math.sin(i), 0.5 + 0.5 * Math.sin(4 * i), 0.5 + 0.5 * Math.sin(5 * i));
const light = color.multiply(diffuse.add(ambient));
return gray2ASCII(new Vector4(Math.sqrt(light.x), Math.sqrt(light.y), Math.sqrt(light.z), 1));
}
}
return gray2ASCII(fragColor);
},
[61, 31],
),
);
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Summary
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* In this tutorial, we have learned how to use shaders to create basic
* computer graphics effects. We have learned how to use the diffuse reflection
* model to compute lighting, how to use ambient lighting to make the sphere
* appear smoother, how to use the time variable to create animations, and how
* to use multiple spheres and lights to create more complex effects.
*
* I hope you have enjoyed this tutorial. If you have any questions, please
* feel free to comment on https://github.com/recho-dev/notebook/issues/98.
*/

Created 2025-09-13
//โ ๐ฉ๐ฉ๐ฉ๐ฉ๐ฉ dog
//โ ๐ฉ๐ฉ๐ฉ๐ฉ cat
//โ ๐ฉ๐ฉ๐ฉ mouse
//โ ๐ฉ playing
//โ ๐ฉ yard
//โ ๐ฉ barked
//โ ๐ฉ loudly
//โ ๐ฉ ran
//โ ๐ฉ quickly
//โ ๐ฉ hid
//โ ๐ฉ under
//โ ๐ฉ bench
//โ ๐ฉ kept
//โ ๐ฉ looking
//โ ๐ฉ jumped
//โ ๐ฉ small
//โ ๐ฉ fence
//โ ๐ฉ followed
//โ ๐ฉ bird
//โ ๐ฉ watched
//โ ๐ฉ silently
//โ ๐ฉ moved
echo(entries.map(([word, count]) => "๐ฉ".repeat(count) + " " + word).join("\n"));
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* References
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Here are the links to the JavaScript APIs we used:
*
* - `string.toLowerCase()`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase
* - `string.replace(pattern, replacement)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
* - `string.split(splitter)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split
* - `array.filter(callback)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
* - `array.includes(value)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
* - `array.reduce(callback, initialValue)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
* - `Object.entries(object)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
* - `string.repeat(value)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
* - `array.join(separator)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join
*/

Created 2025-09-12
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
//โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐
{
let output = "";
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
const [x, y] = [pos(j), pos(i)];
const r = Math.hypot(x, y);
const theta = Math.atan2(y, x) / (Math.PI * 2);
const phase = now / 3000;
const l = ((r + theta - phase) % 1) * -360;
const index = ~~rotate(l);
output += moons[index];
}
output += i == size - 1 ? "" : "\n";
}
echo(output);
}
const d3 = recho.require("d3");
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* References
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* [1] https://observablehq.com/d/a741f9a27e8c0e73
*/

Created 2025-09-12
//โ โ โ โโโโปโโโโโโโปโโโโโโโปโโโโโโโปโโโโโโโปโโโโโ โ
//โ โ January โ ใใใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใใใ โ
//โ โ February โ ใใใใใใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใใใ โ
//โ โ March โ ใใใใใใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ โ
//โ โ April โ ใใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใใใใใ โ
//โ โ May โ ใใใใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใใ โ
//โ โ June โ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใใใใใใใ โ
//โ โ July โ ใใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใใใใ โ
//โ โ August โ ใใใใใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใ โ
//โ โ September โ ใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใใใใใใ โ
//โ โ October โ ใใใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใใใ โ
//โ โ November โ ใใใใใใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใ โ
//โ โ December โ ใ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ใใใใใ โ
{
const matrix = _.times(12, () => _.times(6 * 7, () => SPACE));
for (const day of days) {
const row = day.getMonth();
const column = day.getDate() + months[row].getDay();
matrix[row][column] = getMoonEmoji(day);
}
// Remove the leading and trailing spaces from the header and each row.
const head = Math.min(...matrix.map((xs) => xs.findIndex((x) => x !== SPACE)));
const tail = Math.max(...matrix.map((xs) => xs.findLastIndex((x) => x !== SPACE))) + 1;
echo(line(" ".repeat(longestLength), header.slice(head, tail)), {quote: false});
echo(matrix.map((x, i) => line(alignedMonthNames[i], x.slice(head, tail).join(""))).join("\n"));
}
function line(...items) {
return "โ " + items.join(" โ ") + " โ";
}
const header = "โโโโโปโโ".repeat(7);
const months = d3.timeMonths(theFirstDay, theLastDay);
const longestLength = monthNames.reduce((x, y) => Math.max(x, y.length), 0);
const monthNames = months.map(d3.timeFormat("%B"));
const alignedMonthNames = monthNames.map((n) => n.padStart(longestLength, " "));
const theFirstDay = d3.timeYear(new Date(year, 0, 1));
const theLastDay = d3.timeYear.offset(theFirstDay, 1);
const days = d3.timeDays(theFirstDay, theLastDay);
const SPACE = "\u3000"; // Full-width space
function getMoonEmoji(date) {
const index = Math.round(suncalc.getMoonIllumination(date).phase * 8);
return String.fromCodePoint(0x1f311 + (index === 8 ? 0 : index));
}
const suncalc = recho.require("suncalc");
const d3 = recho.require("d3");
const _ = recho.require("lodash");

Created 2025-09-11
//โ โโโฌโโโโโโโโฌโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
//โ โ โ โ โ
//โ โ โ โโโฌโโ โ โโโ โโโโโโโ โโโโโโโ โโโ โ
//โ โ โ โ โ โ โ โ โ โ โ
//โ โ โ โ โ โโโโโค โโโค โโโ โโโ โโโโโโโค โโโค
//โ โ โ โ โ โ โ โ โ โ โ โ
//โ โ โโโ โ โ โโโ โ โ โ โโโโโฌโโ โโโโโดโโ โ
//โ โ โ โ โ โ โ โ โ โ โ
//โ โโโโโโโค โ โ โโโ โโโค โ โ โ โ โ โโโฌโโโโค
//โ โ โ โ โ โ โ โ โโ โ โ โ โ โ
//โ โ โโโโโ โ โ โ โโโ โโโโโ โ โ โโโ โโโ โ
//โ โ โ โ โ โ โ โ โ โ โ
//โ โ โ โโโฌโโดโโโโดโโโโ โ โโโโโดโโค โโโค โ โโโค
//โ โ โ โ โ โ โ โ โ โ
//โ โ โโโ โโโ โโโฌโโ โโโผโโโโโโ โ โ โ โโโ โ
//โ โ โ โ โ โ โ โ โ โ โ
//โ โ โโโดโโ โโโ โ โโโ โ โโโโโฌโโดโโ โโโฌโโ โ
//โ โ โ โ โ โ โ โ โ โ
//โ โ โโโ โ โ โโโ โโโดโโโโ โโโ โ โโโโโ โโโ
//โ โ โ โ โ
//โ โโโโโดโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโ
{
frame;
if (!maze.walk()) maze.regenerate();
echo(maze.render());
}
const width = 18;
const height = 10;
const maze = new Maze(width, height);
const frame = recho.interval(100);
const cell = () => ({top: true, right: true, bottom: true, left: true});
class Maze {
constructor(width, height) {
this.width = width;
this.height = height;
this.initialize();
this.generate();
}
initialize() {
this.grid = _.times(this.height, () => _.times(this.width, cell));
this.visited = _.times(this.height, () => _.times(this.width, _.constant(false)));
this.path = null;
this.step = 0;
}
regenerate() {
this.initialize();
this.generate();
}
generate() {
this.carvePassage(0, 0);
this.grid[0][0].left = false;
this.grid[this.height - 1][this.width - 1].right = false;
}
walk() {
if (this.path === null) {
this.path = this.findPath();
this.step = 0;
return true;
} else if (this.step + 1 < this.path.length) {
this.step += 1;
return true;
} else {
return false;
}
}
carvePassage(x, y) {
this.visited[y][x] = true;
for (const neighbor of _.shuffle(this.getUnvisitedNeighbors(x, y))) {
if (!this.visited[neighbor.y][neighbor.x]) {
// Remove walls between current cell and neighbor
this.removeWall(x, y, neighbor.x, neighbor.y);
// Recursively visit neighbor
this.carvePassage(neighbor.x, neighbor.y);
}
}
}
getUnvisitedNeighbors(x, y) {
const neighbors = [];
if (y > 0 && !this.visited[y - 1][x]) neighbors.push({x: x, y: y - 1}); // up
if (x < this.width - 1 && !this.visited[y][x + 1]) neighbors.push({x: x + 1, y: y}); // right
if (y < this.height - 1 && !this.visited[y + 1][x]) neighbors.push({x: x, y: y + 1}); // down
if (x > 0 && !this.visited[y][x - 1]) neighbors.push({x: x - 1, y: y}); // left
return neighbors;
}
removeWall(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
if (dx === 1) {
// Moving right
this.grid[y1][x1].right = false;
this.grid[y2][x2].left = false;
} else if (dx === -1) {
// Moving left
this.grid[y1][x1].left = false;
this.grid[y2][x2].right = false;
} else if (dy === 1) {
// Moving down
this.grid[y1][x1].bottom = false;
this.grid[y2][x2].top = false;
} else if (dy === -1) {
// Moving up
this.grid[y1][x1].top = false;
this.grid[y2][x2].bottom = false;
}
}
findPath() {
const queue = [[0, 0, [{x: 0, y: 0}]]];
const visited = new Set([`${0},${0}`]);
while (queue.length > 0) {
const [x, y, path] = queue.shift();
if (x === this.width - 1 && y === this.height - 1) return path;
for (const {dx, dy, direction} of directions) {
const nx = x + dx;
const ny = y + dy;
const key = `${nx},${ny}`;
if (
nx >= 0 &&
nx < this.width &&
ny >= 0 &&
ny < this.height &&
!visited.has(key) &&
!this.grid[y][x][direction]
) {
visited.add(key);
queue.push([nx, ny, [...path, {x: nx, y: ny}]]);
}
}
}
return null;
}
render() {
const pathSet = new Set();
if (this.path !== null && this.step < this.path.length) {
pathSet.add(`${this.path[this.step].x},${this.path[this.step].y}`);
}
// Create output grid (2x dimensions + 1 for walls)
const outputHeight = this.height * 2 + 1;
const outputWidth = this.width * 2 + 1;
const output = _.times(outputHeight, () => _.times(outputWidth, _.constant(chars.space)));
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const cell = this.grid[y][x];
const ox = x * 2;
const oy = y * 2;
if (cell.top) output[oy][ox + 1] = chars.horizontal;
if (cell.bottom) output[oy + 2][ox + 1] = chars.horizontal;
if (cell.left) output[oy + 1][ox] = chars.vertical;
if (cell.right) output[oy + 1][ox + 2] = chars.vertical;
if (pathSet.has(`${x},${y}`)) output[oy + 1][ox + 1] = chars.ghost;
}
}
// Draw corners and junctions
for (let y = 0; y < outputHeight; y += 2) {
for (let x = 0; x < outputWidth; x += 2) {
const top = y > 0 && output[y - 1][x] === chars.vertical;
const bottom = y < outputHeight - 1 && output[y + 1][x] === chars.vertical;
const left = x > 0 && output[y][x - 1] === chars.horizontal;
const right = x < outputWidth - 1 && output[y][x + 1] === chars.horizontal;
const connections = (top ? 1 : 0) + (right ? 1 : 0) + (bottom ? 1 : 0) + (left ? 1 : 0);
if (connections === 0) {
output[y][x] = chars.space;
} else if (connections === 1) {
if (top || bottom) output[y][x] = chars.vertical;
else output[y][x] = chars.horizontal;
} else if (connections === 2) {
if (top && bottom) output[y][x] = chars.vertical;
else if (left && right) output[y][x] = chars.horizontal;
else if (top && right) output[y][x] = chars.bottomLeft;
else if (top && left) output[y][x] = chars.bottomRight;
else if (bottom && right) output[y][x] = chars.topLeft;
else if (bottom && left) output[y][x] = chars.topRight;
} else if (connections === 3) {
if (!top) output[y][x] = chars.teeDown;
else if (!right) output[y][x] = chars.teeLeft;
else if (!bottom) output[y][x] = chars.teeUp;
else if (!left) output[y][x] = chars.teeRight;
} else {
output[y][x] = chars.cross;
}
}
}
output[1][0] = chars.startMarker;
output[outputHeight - 2][outputWidth - 1] = chars.endMarker;
return output.map((row) => row.join("")).join("\n");
}
}
const directions = [
{dx: 0, dy: -1, direction: "top"},
{dx: 1, dy: 0, direction: "right"},
{dx: 0, dy: 1, direction: "bottom"},
{dx: -1, dy: 0, direction: "left"},
];
const chars = {
horizontal: "โ",
vertical: "โ",
topLeft: "โ",
topRight: "โ",
bottomLeft: "โ",
bottomRight: "โ",
teeUp: "โด",
teeDown: "โฌ",
teeLeft: "โค",
teeRight: "โ",
cross: "โผ",
space: " ",
ghost: "โ",
startMarker: " ",
endMarker: " ",
};
const _ = recho.require("lodash");

Created 2025-09-09
//โ ๐๐จ
//โ ๐ข๐จ
//โ ๐ถโโ๏ธ๐จ
//โ ๐๐จ
//โ ๐๐จ
{
const x = (count) => 40 - (count % 40);
echo("๐๐จ".padStart(x(snail)));
echo("๐ข๐จ".padStart(x(turtle)));
echo("๐ถโโ๏ธ๐จ".padStart(x(human)));
echo("๐๐จ".padStart(x(car)));
echo("๐๐จ".padStart(x(rocket)));
}

Created 2025-09-03
//โ
//โ Great Britain
//โ pigs -| ๐
//โ cattle -| ๐ ๐ ๐ ๐
//โ sheep -| ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐
//โ
//โ United States
//โ pigs -| ๐ ๐ ๐ ๐ ๐ ๐
//โ cattle -| ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐
//โ sheep -| ๐ ๐ ๐ ๐ ๐ ๐ ๐
//โ
//โ Live stock (millions)
//โ
echo(output);
/**
* Next, let's dive into how `output` is generated.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Preparing the data
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* First, we need to prepare the data. We're going to use the following dataset
* to create the chart. It's a tiny tubular dataset, with each row representing
* a animal in a country and the count of the animal.
*/
const data = [
{animal: "pigs", country: "Great Britain", count: 1354979},
{animal: "cattle", country: "Great Britain", count: 3962921},
{animal: "sheep", country: "Great Britain", count: 10931215},
{animal: "pigs", country: "United States", count: 6281935},
{animal: "cattle", country: "United States", count: 9917873},
{animal: "sheep", country: "United States", count: 7084151},
];
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Importing D3
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Then we import D3 to help us with the data processing. In Recho, you can
* typically use `recho.require(name)` to import an external library.
*
* > Ref. https://recho.dev/docs/libraries-imports
* > Ref. https://d3js.org/
*/
const d3 = recho.require("d3");
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Generating the bars
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* We'll get started with generating the bars. There are three main tasks here:
*
* 1. Mapping animals types to their corresponding emojis.
* 2. Mapping the counts to the number of emojis.
* 3. Generating the bars based on the emojis and the number.
*
* Here is the implementation:
*/
//โ [ 0, 1, 2, 3, 4, 5 ]
const I = echo(d3.range(data.length));
//โ { cattle: "๐", sheep: "๐", pigs: "๐" }
const emoji = echo({cattle: "๐", sheep: "๐", pigs: "๐"});
//โ [ "๐", "๐", "๐", "๐", "๐", "๐" ]
const E = echo(data.map((d) => emoji[d.animal]));
//โ [ 1, 4, 11, 6, 10, 7 ]
const V = echo(data.map((d) => Math.round(d.count / 1e6)));
//โ [ "๐ ", "๐ ๐ ๐ ๐ ", "๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ", "๐ ๐ ๐ ๐ ๐ ๐ ", "๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ", "๐ ๐ ๐ ๐ ๐ ๐ ๐ " ]
const bars = echo(I.map((i) => `${E[i]} `.repeat(V[i])));
/** This is the chart we got so far. */
//โ ๐
//โ ๐ ๐ ๐ ๐
//โ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐
//โ ๐ ๐ ๐ ๐ ๐ ๐
//โ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐
//โ ๐ ๐ ๐ ๐ ๐ ๐ ๐
echo(bars.join("\n"));
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Adding the labels
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Next step is to add the labels to the bars. We need to collect all the
* animal types by a set, and compute a margin left to make sure the labels
* are aligned. Then concatenate the labels to the bars with a separator: `-|`.
*/
//โ [ "pigs", "cattle", "sheep" ]
const L = echo(Array.from(new Set(data.map((d) => d.animal))));
//โ 6
const marginLeft = echo(d3.max(L, (d) => d.length));
//โ [ " pigs", "cattle", " sheep", " pigs", "cattle", " sheep" ]
const labels = echo(data.map((d) => d.animal.padStart(marginLeft, " ")));
//โ [ " pigs -| ๐ ", " cattle -| ๐ ๐ ๐ ๐ ", " sheep -| ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ", " pigs -| ๐ ๐ ๐ ๐ ๐ ๐ ", " cattle -| ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ", " sheep -| ๐ ๐ ๐ โฆ
const rows = echo(I.map((i) => " " + labels[i] + " -| " + bars[i]));
/** Now the chart looks like this. */
//โ pigs -| ๐
//โ cattle -| ๐ ๐ ๐ ๐
//โ sheep -| ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐
//โ pigs -| ๐ ๐ ๐ ๐ ๐ ๐
//โ cattle -| ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐
//โ sheep -| ๐ ๐ ๐ ๐ ๐ ๐ ๐
echo(rows.join("\n"));
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Generating the titles
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Technically speaking, the chart is a facet chart, which means it contains
* multiple charts. The first one is for Great Britain, and the second one is
* for United States. In order to differentiate the two charts, we need to add
* the titles and some spacing.
*/
//โ 45
const width = echo(d3.max(rows, (d) => d.length));
//โ [ "Great Britain", "United States" ]
const T = echo(Array.from(new Set(data.map((d) => d.country))));
//โ [ " Great Britain", " United States" ]
const titles = echo(T.map((t) => t.padStart(Math.ceil(width / 2 + 2), " ")));
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Final output
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Finally, we can concatenate the titles, the rows, and the live stock caption
* to get the final output!
*/
//โ
//โ Great Britain
//โ pigs -| ๐
//โ cattle -| ๐ ๐ ๐ ๐
//โ sheep -| ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐
//โ
//โ United States
//โ pigs -| ๐ ๐ ๐ ๐ ๐ ๐
//โ cattle -| ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐
//โ sheep -| ๐ ๐ ๐ ๐ ๐ ๐ ๐
//โ
//โ Live stock (millions)
//โ
const output = echo(
[
" ",
titles[0], // Great Britain
...rows.slice(0, 3),
" ",
titles[1], // United States
...rows.slice(3),
" ",
"Live stock (millions)", // Add a caption
" ",
].join("\n"),
);

Created 2025-09-03
//โ ใใใใใใใใใใใใใใใใใใใใใใ็
//โ ็็ใใใใใใใใ็็ใใ็ใใใใใ็็ใใ็
//โ ใใ็็ใใใใใใใ็ใใใใใใใใใใใใ็
//โ ใใใใ็็็ใใ็็ใ็ใใใใ็็็็ใใใ็็ใใ็็ใ็็ฑ็็็
//โ ใใใ็็ฑใใใใ็็็ใ็็ใใใ็็็็็ใใใใ็็ใใ็็็ฑ็็
//โ ็ฑ็ใใใ็ใใใใ็็็็็ใใ็ฑ็ฑ็ฑ็็
//โ ใใใ็ฑ็ฑ็ฑ็็็็็็็ฑ็ฑ็ฑ็็็ใ็ฑ็็ฑใใใใใใ็็ฑ็ฑ
//โ ็็็็็็็ใใใใใ็็็็ฑ็ฑ็
//โ ็ฑ็ฑใใ็ฑ็็็็็็็ฑ็ฑ็็ฑ็ฑ็ฑใใใใ็็ฑ็ฑ็็็็็ฑ็ฑ็ฑ็็็็็ฑ
//โ ็็็็็็ฑ็ฑ็็ฑ็็็็็ใใ็็็็็ฑ็ฑ็็็็็็ฑ็ใ็็ใ็ฑ็ฑ็
//โ ็็็ใ็็็็็็็็็็็็ฑ็ฑ็ฑ็ใ็็ใ็ฑ็ฑ็็็ฑ็็็ฑ็ฑ็ฑ็ฑ็็
//โ ็ฑ็ฑ็ฑ็ฑ็ฑ็ฑ็ฑ็ฑ็็็็็ฑ็ฑ็ฑ็ฑ็ฑ็ซ็ฑ็็็ฑใ็ฑ็็ฑ็็ฑ็็็็็็็ฑ็ฑ
//โ ็ฑ็็็็็ฑ็็็็็ซ็ซ็ฑ็ฑ็ซ็ซ็ซ็ซ็ซ็ซ็ฑ็็็ใ็็็ใใ็ฑ็็็็็ซ
//โ ็็็ฑ็ฑ็็็็ฑ็็ซ็ฑ็็็ฑ็ฑ็็ฑ็็็ฑ็็ซ็ฑ็็ฑ็ฑ็ฑ็็ซ็ฑ็็็ฑ็ฑ็ฑ็
//โ ็ซ็็ฑ็ฑ็ซ็็็ฑ็็ฑ็ซ็็็็็็็็็ฑ็็ฑ็ซ็ฑ็ซ็็ฑ็ฑ็็็ฑ็ฑ็ฑ็ฑ็็ซ
{
frame;
for (let y = 0; y < rows - 1; y++) {
for (let x = 0; x < cols; x++) {
const decay = d3.randomInt(0, 3)();
const spread = d3.randomInt(-1, 1)();
const i = Math.min(Math.max(0, x - spread), cols - 1);
const target = fire[index(i, y + 1)];
fire[index(x, y)] = Math.max(0, target - decay);
}
}
const noise = new Noise();
const linear = d3.scaleLinear([0, 1], [0, rows]);
const source = (i) => linear(noise.get(i, 0, 0));
for (let x = 0; x < cols; x++) {
fire[index(x, rows - 1)] = ~~source(x);
}
const quantile = d3.scaleQuantile(d3.extent(fire), chs);
const ch = (d) => (d ? quantile(d) : "ใ");
let output = "";
for (let i = 0; i < rows; ++i) {
for (let j = 0; j < cols; ++j) output += ch(fire[index(j, i)]);
output += i === rows - 1 ? "" : "\n";
}
output = output.split("\n").map((d) => " " + d).join("\n");
echo(output);
}
const d3 = recho.require("d3");
/**
* I like this example also because I found one importable noise library:
* `perlin-noise-3d`:
*
* - https://www.npmjs.com/package/perlin-noise-3d
*/
const Noise = recho.require("perlin-noise-3d");
/**
* Perlin noise is so useful in creative coding, so it's great that we can use
* it in Recho before making it a built-in function.
*
* As you can see, the implementation replies on noise function to generate
* natural source of fire, which is the key to create such effect!
*/

Created 2025-08-22
//โ w $ R | @ 2 !
//โ j M & C m ,
//โ _# D 9 z L ? Z=
//โ !d ` [ * g g 3
//โ t| [ - BS E k e u< J : o6 j
//โ I* ! j R$ p7 p I&D k, j ?1 M
//โ k j# W O D ( , > a s# XY 9' . XK y )
//โ a $\ *n [B W W F 6[ w> R AOL p j _B U Z
//โ X /1 NX9 K ~ 7 Z /u b_ o H ,:u=$ 6
//โ Btb* rE a yD i n ^ ks , S! |? & ?$ l a
//โ $ 3 @ y ro 9 b Oh ! I- ^7 x =t X
//โ 8 n < F 8ZG - + 8i 2 .j j bx ,tf G
//โ _ 8 t 3 GH& T u S K y M ! Ln 45Y K L
//โ } d ca @ h 4 T 7 6 T ( !y_hx7 P r
//โ u i / FH0 = K v P ` e <[ XjD <6 u
//โ M% V { vg | V 4 q 7 [ R ; [@ =; \
//โ R y [: } ) `v S [ % / g- u= 3
//โ g $ UH g & rD @ b H = o 4+ R
//โ 9 g 96 W c ~ t * mq
//โ ; vI ) p \ M
//โ ) wc A ~
//โ d _ = D
//โ ` ~ q #
//โ | m e
//โ e W ^
{
frame;
// Create a new buffer.
const buffer = d3.range(width * height).map(() => " ");
// Update all columns.
for (let i = columns.length - 1; i >= 0; --i) {
const column = columns[i];
const {lifespan, length, chars} = column;
const n = chars.length;
if (lifespan < 0) columns[i] = createColumn(height);
else if (lifespan <= n) chars[n - lifespan] = " ";
else {
for (let j = length - 1; j < n; ++j) chars[j] = randomChar();
chars.push(randomChar());
}
column.lifespan -= 1;
}
// Update the buffer.
for (let i = 0; i < columns.length; ++i) {
const column = columns[i];
const {y, chars} = column;
for (let j = 0; j < chars.length; ++j) buffer[(y + j) * width + i] = chars[j];
}
// Render the buffer.
let output = "";
for (let i = 0; i < height; ++i) {
for (let j = 0; j < width; ++j) output += buffer[i * width + j];
output += i === height - 1 ? "" : "\n";
}
output = output
.split("\n")
.map((d) => " " + d)
.join("\n");
echo(output);
}
function createColumn(height) {
const lifespan = d3.randomInt(height)();
const length = d3.randomInt(lifespan)();
const chars = d3.range(length).map(randomChar);
const y = d3.randomInt(0, 10)();
return {lifespan, chars, y};
}
function randomChar() {
return String.fromCharCode(d3.randomInt(32, 127)());
}
const frame = recho.interval(1000 / 15);
const d3 = recho.require("d3");

Created 2025-08-22
//โ โ
//โ โ โ โ โ โโ
//โ โ โ โ โ โ โ โ โโ
//โ โ โ โโ โ โ โโ โ โ โโโ โ โ โโ
//โ โโ โโโโโโ โ โโ โโ โ โโโโ โโ โโโโ โ โโ โโ
//โ โโโโโโโโโโ โโโโโโโโโโโโ โโโโ โโ โโโโโโโโโโ โโโ
//โ โโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโ
//โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
{
let output = "";
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
const bin = bins[j];
const h = bin ? (bin * height) / d3.max(bins) : 0;
output += h >= height - i ? "โ" : " ";
}
output += i === height - 1 ? "" : "\n";
}
echo(output);
}

Created 2025-08-21
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*OoO@@@@@@@@@@@@@@@@@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@ยทO@**ยทยทยทยท*@@@@@@@@@@@@@@@@@@@@@@@@Oยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*@@@@@@@@ยทยท@@@@@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*@@@@@@@@@@*@@@@@@@@@@@@@@@@@@@@@@@@@oยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*o*@@@@@@@@@@@o@@@@@@@@@@@@@@@@@@@@@@@@oยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*o*@@@@@@@@@@@o@@@@@@@@@@@@@@@@@@@@@@@@oยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*@@@@@@@@@@*@@@@@@@@@@@@@@@@@@@@@@@@@oยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*@@@@@@@@ยทยท@@@@@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@ยทO@**ยทยทยทยท*@@@@@@@@@@@@@@@@@@@@@@@@Oยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*OoO@@@@@@@@@@@@@@@@@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@ยท*@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@ยทยทยทยทยทยทยท@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทO@@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทo@@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทO@ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
{
let output = "";
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const re = map(x, 0, cols, -2.5, 1);
const im = map(y, 0, rows, -1, 1);
let [a, b, i] = [0, 0, 0];
while (i < maxIter) {
[a, b] = [a * a - b * b + re, 2 * a * b + im];
if (a * a + b * b > 4) break;
i++;
}
const index = ~~((i / maxIter) * (colors.length - 1));
output += colors[index];
}
output += y === rows - 1 ? "" : "\n";
}
echo(output);
}
function map(x, d0, d1, r0, r1) {
return r0 + ((r1 - r0) * (x - d0)) / (d1 - d0);
}
/**
* Again, you don't need to completely understand the code above for now. If
* you find textual outputs can be interesting and creative by this example,
* that's the point!
*
* If you're really curious about Mandelbrot set, here are some examples that
* I made with Charming.js[2] you may find interesting:
*
* - Multibrot Set: https://observablehq.com/d/fc2cfd9ae9e7524c
* - Multibrot Set Table: https://observablehq.com/d/3028c0d5655345e3
* - Multibrot Set Transition: https://observablehq.com/d/c040d3db33c0033e
* - Zoomable Mandelbrot Set (Canvas): https://observablehq.com/d/2e5bdd2365236c2d
* - Zoomable Mandelbrot Set (WebGL): https://observablehq.com/d/cfe263c1213334e3
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* References
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* [1] https://en.wikipedia.org/wiki/Mandelbrot_set
* [2] https://charmingjs.org/
*/