/ Examples / Algo / Hanoi

Right-click to copy examples to your workspace


The Tower of Hanoi is a mathematical puzzle that can easily be solved by your computer using a recursive program. In the Hz context we can both hear and see the algorithm as it runs. Perhaps this or other puzzles can form the basis for a rhythm track for your next song?

This example was inspired by a ChucK example created by Ge Wang and the ChucK team.

Right-click to copy examples to your workspace

The Setup

Here's the boilerplate code to setup the audio environment.

// Ensure audio engine is ready. 
let scene = await Ascene.BeginFiber(this);

// setup our audio pipeline
let sp = scene.NewAnode("Hz.Samplo");
let gain = scene.NewAnode("Hz.Mix", {cfg:"stereo", preset:{Gain:3}});
let rev = scene.NewAnode("Hz.Reverb", {cfg:"stereo"});
let dac = scene.GetDAC();
scene.Chain(sp, gain, rev, dac);
await scene.Sync();

// preload sample files into sp
let files = [
    "snare-chili.wav",
    "kick.wav",
    "snare-hop.wav"
];
let cwd = path.dirname(this.GetFilePath());
let wspath = await ResolveWSFile(cwd); // validate file and convert to fullpath
await sp.LoadPreset(SampleMgr.AsPresetFromLocalVoice(wspath, "drumkit", files));

You can learn more about Hz's Audio API detailed here.

The Solution and its Sonificiation

const wait = scene.Seconds(.2); 
const asap = 0;
let STEPS = 0, noteid;
let pans = [.2, .5, .8];
async function hanoi(num, src, dst, other)
{
    // move all except the biggest
    if(num > 1) 
        await hanoi(num-1, src, other, dst);

    STEPS++;

    // Sonify the move: playing a sound for the target peg.
    // We could get fancier and play different sounds according
    // to disk radius using the current disk states maintained 
    // by visualizer.
    let velocity = .2 + .7 * Math.random();
    let pan = pans[dst];
    noteid = sp.Note(dst, velocity, wait, asap)[0];
    sp.NoteExpression("pan", pan, noteid);
    await scene.Wait(wait); // wait for now to complete

    updateVisualization(src, dst);

    // move onto the biggest
    if(num > 1) 
        await hanoi(num-1, other, dst, src);
}

// start it
await hanoi(numdisks, 0, 2, 1);

NB: this example doesn't yield control back to the system. This means that Hz's Fiber-cancel feature doesn't work well. We've left the topic of yielding and fibers for another example.

The Visualization

The JavaScript sandbox environment is integrated into the WebView/WebKit environment on your computer. This means that in addition to triggering Hz's audio engine, you can program graphics as well. This example shows a crude visualization of the Hanoi algorithm's current state. Here is a snapshot of the state somewhere in the middle of the solution.

Here's all the code required to achieve it. Note that Hz includes the javascript package, @svgjs, described here.

// Hz's JavaScript sandbox exposes  WWW DOM via the 
// standard document object.
let content = globalThis.document.querySelector(".Content");
content.innerHTML = "";

// We use svgjs (https://svgjs.dev) to create colored rectangles.
let [xsize, ysize] = [400, 150];
let svg = SVG().addTo('.Content').size(xsize, ysize);
svg.text().plain("Ge Wang's Tower of Annoy").fill("orange").move(10, 10);
// make y point up
let draw = svg.group().transform({scale:[1, -1], translate:[0, ysize]});
let height = 10;
let spacing = height+2;
let maxrad = 110;
let centerX = [maxrad/2, maxrad*1.5, maxrad*2.5];
let disklist = []; // contains our svg rects for each disk
for(let i=0;i<numdisks;i++)
{
    let yinit = (numdisks - i) * height;
    disklist[i] = draw.rect(10 + i*10, height)
                    .fill(randomColor());
}

// initial conditions
const pegstacks = [[], [], []]; // holds the current disk stack for each peg
for(let i=0;i<numdisks;i++) 
    pegstacks[0].unshift(i); // largest disk on bottom of first stack

function updateVisualization(src, dst)
{
    let d = pegstacks[src].pop(); // take the disk off src peg
    pegstacks[dst].push(d); // push it onth the dst peg.
    // rebuild state
    for(let i=0;i<pegstacks.length;i++) // for each peg
    {
        let ps = pegstacks[i];
        let pegx = centerX[i];
        for(let j=0;j<ps.length;j++)
        {
            let d = disklist[ps[j]]; // ps[j] is id/radius, d is svg rect
            d.cx(pegx).y(j*spacing); // move it into position.
        }
    }
}
home .. topics .. interface .. reference .. examples .. tipjar