OverviewCode › R130
Code · blind round

R130 · Radix sort ordering by one digit at a time

blind round 9/11 local models answered

Every answer below carries the name of the model that wrote it. Blind refers to the scoring: the review was done without knowing which model produced which answer — the published result names them.

2 of the 11 local models delivered nothing through our test harness in this round — for reasons that lie with our setup, not with the models. Those cards are marked DNF like any other, but they are not evidence about those models — the reason is noted on the card itself.

Show task
Create ONE self-contained HTML file (STRICT LIMIT: max 7000 characters in total — this is a budget on characters, not on lines, so removing line breaks does not help) that animates least-significant-digit radix sort on a <canvas>: around 80 vertical bars of random heights, processing one decimal digit place per pass, coloring each bar by its current digit bucket while bars visibly move to their bucket order, then starting the next digit place. Show the digit place currently being processed and the pass number. Smooth requestAnimationFrame animation, NO user input, starts immediately and keeps running (restart with a fresh random instance when finished). No external libraries, no comments needed. Output ONLY the HTML file content, nothing else.

The answers

Local models (Brain cluster) — identical prompt, shuffled order (the sequence is not a ranking)
qwen3:8bharness
No answer reached us (ERR) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget and the answer stayed empty; the gateway rejects budgets above 3000 tokens Counted as a did-not-finish, but it is not evidence about this model.
qwen3.5:9bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head><title>LSD Radix Sort Visualization</title></head>
<body style="margin:0;overflow:hidden"><canvas id="sortCanvas" width="800" height="600"></canvas><script>
    const canvas = document.getElementById('sortCanvas');
    const ctx = canvas.getContext('2d');

    let bars = [];
    const NUM_BARS = 80;
    const MAX_HEIGHT = 500;
    const BAR_WIDTH = (canvas.width - 1) / NUM_BARS;

    function initializeBars() {
        bars = Array(NUM_BARS).fill(0).map(() => Math.floor(Math.random() * MAX_HEIGHT) + 5);
    }

    let state = {
        currentPass: -1,
        digitPlace: 0, // Represents the current power of 10 (1, 10, 100...)
        isRunning: false,
        animationFrameId: null
    };

    function draw() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        const maxVal = Math.max(...bars);
        const scaleFactor = canvas.height / (Math.pow(10, state.digitPlace) * 2 + 1); // Simple scaling approach

        for (let i = 0; i < NUM_BARS; i++) {
            const height = bars[i] || 0;
            ctx.fillStyle = 'hsl(' + ((height % 10) * 36) + ', 70%, 50%)';
            ctx.fillRect(i * BAR_WIDTH, canvas.height - (height * scaleFactor), BAR_WIDTH - 1, height * scaleFactor);
        }

        // Display info
        ctx.fillStyle = '#333';
        ctx.font = '24px Arial';
        ctx.fillText(`Pass: ${state.currentPass + 1}`, 10, 30);
        ctx.font = '20px Arial';
        ctx.fillText(`Digit Place (k): ${state.digitPlace}`, 10, 60);

        // Display sorting status message if finished
        if (!state.isRunning && state.currentPass >= 9) {
             ctx.fillStyle = 'green';
             ctx.font = '32px Arial';
             ctx.fillText("SORT COMPLETE! Restarting...", canvas.width / 2 - 150, 40);
        }
    }

    // Swaps the positions and colors of two bars (i and j)
    function swap(arr, i, j) {
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }

    /**
     * Performs one pass of Counting Sort based on the current digit place.
     * This simulates the movement into buckets and then re-ordering them.
     */
    function countingSortPass() {
        const k = state.digitPlace; // Current power of 10 (1, 10, 100...)
        let newBars = Array(NUM_BARS).fill(0);
        let tempPositions = Array(NUM_BARS).fill(0);

        // Step 1: Determine the digit for sorting (d) and create buckets.
        const getDigit = (val) => Math.floor(Math.pow(10, state.digitPlace - 1) * val) % 10;
        let buckets = Array(10).fill(0); // 10 buckets for digits 0-9

        // Count occurrences of each digit
        for (let i = 0; i < NUM_BARS; i++) {
            const d = getDigit(bars[i]);
            buckets[d] += 1;
        }

        // Calculate starting positions for each bucket
        let starts = Array(10).fill(0);
        let currentSum = 0;
        for (let i = 0; i < 10; i++) {
            starts[i] = currentSum;
            currentSum += buckets[i];
        }

        // Step 2: Place elements into the new sorted array (stable sort)
        tempPositions.fill(0); // Tracking where each bucket starts filling from
        for (let i = 0; i < NUM_BARS; i++) {
            const d = getDigit(bars[i]);
            const targetIndex = starts[d] + tempPositions[d];
            newBars[targetIndex] = bars[i];
            tempPositions[d]++;
        }

        // Update the state for the next draw frame
        bars = [...newBars];
    }

    async function animatePass() {
        if (!state.isRunning) return;

        try {
            countingSortPass(); // Perform the sort pass (instantaneous calculation of new positions)

            state.currentPass++;

            // Check if all digits have been processed for a large enough number (e.g., up to 10^9 place)
            if (state.digitPlace > 9 && state.currentPass >= 10) {
                state.isRunning = false;
                draw();
                return; // Stop the loop
            }

        } catch (e) {
            console.error("Animation error:", e);
            state.isRunning = false;
        }

        draw();

        // Cycle through passes: 1, 10, 100, ...
        if (state.currentPass % 1 === 0 && state.digitPlace < 10) {
             state.digitPlace *= 10; // Next pass is for the next decimal place
        }

        // Delay to simulate processing time and allow visualization of movement
        await new Promise(resolve => setTimeout(resolve, 250));

        if (state.isRunning) {
            animatePass();
        }
    }

    function loop() {
        if (!state.animationFrameId || state.animationFrameId !== window.requestAnimationFrame(loop)) {
             // This structure is often needed for consistent rAF usage, but since we are using async/await on animatePass, 
             // simply calling it repeatedly will work if the pass takes time.
        }
    }

    function startAnimation() {
        if (state.animationFrameId) cancelAnimationFrame(state.animationFrameId);
        
        initializeBars();
        state = { currentPass: -1, digitPlace: 1, isRunning: true };
        draw();
        animatePass(); // Start the recursive async process
    }

    startAnimation();
</script></body></html>
```
gemma4:26bharness
No answer reached us (ERR) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget (3 min 28 s, zero characters of answer); larger budgets are refused by the gateway Counted as a did-not-finish, but it is not evidence about this model.
llama3.1:8bHANGFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Least Significant Digit Radix Sort Animation</title>
<style>
  canvas {
    border: 1px solid black;
  }
  #info {
    font-family: monospace;
    font-size: 24px;
  }
</style>
</head>
<body>
<canvas id="canvas" width="800" height="600"></canvas>
<div id="info">Pass: <span id="pass">0</span> | Digit Place: <span id="digit-place">0</span></div>
<script>
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  const info = document.getElementById('info');

  function randomArray(length, max) {
    let array = new Array(length);
    for (let i = 0; i < length; i++) {
      array[i] = Math.floor(Math.random() * max);
    }
    return array;
  }

  function sort(array) {
    const maxHeight = Math.max(...array);
    const barWidth = canvas.width / array.length;
    let pass = 0;
    for (let digitPlace = 0; digitPlace < 10; digitPlace++) {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      info.textContent = `Pass: ${pass} | Digit Place: ${digitPlace}`;
      const buckets = new Array(10).fill(0);
      for (let i = 0; i < array.length - 1; i++) {
        let digit = Math.floor(array[i] / Math.pow(10, digitPlace) % 10);
        ctx.fillStyle = `hsl(${digit * 36}, 100%, 50%)`;
        ctx.fillRect(i * barWidth, canvas.height - (array[i] % Math.pow(10, digitPlace)) * 5, barWidth, array[i] % Math.pow(10, digitPlace) * 5);
        buckets[digit]++;
      }
      const remaining = new Array(array.length - 1).fill(0);
      for (let i = 0; i < 10; i++) {
        ctx.fillStyle = 'black';
        let offset = 0;
        while (buckets[i] > 0) {
          const height = array[offset + (array.length - 1 - remaining.length)] % Math.pow(10, digitPlace);
          ctx.fillRect((offset + (array.length - 1 - remaining.length)) * barWidth, canvas.height - height * 5, barWidth, height * 5);
          buckets[i]--;
          if (buckets[i] === 0) {
            break;
          }
        }
      }
      requestAnimationFrame(() => {
        pass++;
        sort(array);
      });
    }
  }

  const array = randomArray(80, 100);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  for (let i = 0; i < array.length - 1; i++) {
    let digit = Math.floor(array[i] / 10 % 10);
    ctx.fillStyle = `hsl(${digit * 36}, 100%, 50%)`;
    ctx.fillRect(i * canvas.width / array.length, canvas.height - (array[i] % 10) * 5, canvas.width / array.length, array[i] % 10 * 5);
  }
  info.textContent = 'Pass: 0 | Digit Place: 0';
  requestAnimationFrame(() => { sort(array); });
</script>
</body>
</html>
```
mistral-small:24bSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Radix Sort Visualization</title>
<style>
body { margin: 0; overflow: hidden;}
canvas { display: block; }
</style>
</head>
<body>
<canvas id="sortCanvas"></canvas>
<script>
const canvas = document.getElementById('sortCanvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

let bars = [];
let pass = 0;
let digitPlace = 1;
let sorting = true;

function createBars() {
    bars = Array.from({ length: 80 }, () => Math.floor(Math.random() * 300));
}

function drawBars() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    const barWidth = canvas.width / bars.length;
    bars.forEach((bar, index) => {
        const x = index * barWidth + barWidth / 2;
        const y = canvas.height - bar;
        const color = `hsl(${Math.floor(360 * (index / bars.length))}, 100%, 50%)`;
        ctx.fillStyle = color;
        ctx.fillRect(x - barWidth / 2, y, barWidth, bar);
    });
}

function getDigit(num, place) {
    return Math.floor(Math.abs(num) / Math.pow(10, place)) % 10;
}

function bucketSort() {
    const buckets = Array.from({ length: 10 }, () => []);
    for (let i = 0; i < bars.length; i++) {
        let digit = getDigit(bars[i], digitPlace);
        buckets[digit].push(bars[i]);
    }
    return buckets.flat();
}

function update() {
    if (!sorting) return;
    const newBars = bucketSort();
    for (let i = 0; i < bars.length; i++) {
        bars[i] += (newBars[i] - bars[i]) * 0.1;
    }
    drawBars();
    ctx.fillStyle = 'black';
    ctx.font = '24px Arial';
    ctx.fillText(`Pass: ${pass + 1}`, 10, 30);
    ctx.fillText(`Digit Place: ${digitPlace}`, 10, 60);

    if (Math.abs(bars[0] - newBars[0]) < 1) {
        pass++;
        digitPlace *= 10;
        if (digitPlace >= Math.max(...bars)) {
            sorting = false;
        }
    }

    requestAnimationFrame(update);
}

createBars();
update();

function restart() {
    bars = Array.from({ length: 80 }, () => Math.floor(Math.random() * 300));
    pass = 0;
    digitPlace = 1;
    sorting = true;
    update();
}

setTimeout(restart, 5000);
</script>
</body>
</html>
```
qwen3:14bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head><title>Radix Sort Visualization</title></head>
<body><canvas id="c"></canvas><script>const canvas = document.getElementById('c');const ctx = canvas.getContext('2d');let width, height;const NUM_BARS = 80;let bars = [];let maxVal = 1000;let numDigits = 3;let passCount = 0;let currentDigitPass = 1;let animationFrameId = null;let isSorting = false;let startTime = 0;const BUCKET_COUNT = 10;

function initializeBars() {
    width = canvas.width = width || window.innerWidth * 0.95;
    height = canvas.height = Math.max(400, window.innerHeight * 0.6);
    ctx.fillStyle = '#222';
    ctx.fillRect(0, 0, width, height);

    bars = [];
    for (let i = 0; i < NUM_BARS; i++) {
        // Generate random heights (numbers) up to maxVal
        const value = Math.floor(Math.random() * (maxVal - 1)) + 1;
        bars.push({
            value: value, // The number represented by the bar height
            height: value,
            originalIndex: i,
            x: (i / NUM_BARS) * width,
            y: height - value,
            color: '#ccc'
        });
    }
    passCount = 0;
    currentDigitPass = 1;
    isSorting = false;
}

function draw() {
    ctx.clearRect(0, 0, width, height);
    bars.forEach((bar, i) => {
        // Draw the bar using its current position and calculated height
        const barWidth = Math.min(width / NUM_BARS * 0.8, 25);
        ctx.fillStyle = bar.color;
        ctx.fillRect(bar.x, bar.y, barWidth, height - bar.y);
    });

    // Display status text
    const statusText = `Pass: ${passCount} | Digit Place (10^${currentDigitPass-1}): ${Math.pow(10, currentDigitPass-1)}`;
    ctx.fillStyle = '#fff';
    ctx.font = '20px monospace';
    ctx.fillText("LSD Radix Sort", 10, 30);
    ctx.fillText(statusText, 10, 60);

    if (isSorting && startTime) {
        const elapsed = Date.now() - startTime;
        // Simple progress indicator based on animation state if needed
    }
}

/**
 * Performs one pass of Counting Sort for the given digit place.
 * Returns an array detailing the desired next position and color for each bar.
 */
function getNextPositions(digitPlace) {
    const buckets = Array(BUCKET_COUNT).fill(0).map(() => []);
    const nextState = [];

    // 1. Counting/Bucket Phase (Stable Sort logic based on the digit)
    for (let i = 0; i < NUM_BARS; i++) {
        const bar = bars[i];
        let digit;
        if (bar.value === 0) {
            digit = 0;
        } else {
            // Get the specific digit: e.g., for 123 and pass 2 (tens place), get 2.
            digit = Math.floor(Math.pow(10, digitPlace) / bar.value) * bar.value / Math.pow(10, digitPlace);
        }

        // The actual digit value (0-9)
        const index = Math.floor((bar.value / Math.pow(10, digitPlace)) % 10);
        buckets[index].push(i); // Store original index
    }

    // 2. Collection Phase (Determining the final stable order and colors)
    // The 'sorted' array stores the new order of indices
    const sortedIndices = [];
    for (let i = 0; i < BUCKET_COUNT; i++) {
        buckets[i].forEach(originalIndex => {
            sortedIndices.push(originalIndex);
        });
    }

    // 3. Map results for animation/state update
    for (let i = 0; i < NUM_BARS; i++) {
        const originalBar = bars[i];
        const newIndex = sortedIndices[i];

        nextState.push({
            originalIndex: i, // The bar's current identity
            newPositionIndex: i,
            targetX: (i / NUM_BARS) * width + (Math.sin(i/10)*5+1)*Math.cos(i/10)*2, // Slight jitter effect
            targetY: height - originalBar.value,
            newColor: `hsl(${i * 36}, 70%, ${i % 10 * 10}%)` // Example color based on new bucket index
        });
    }

    return { sortedIndices, nextState };
}


function animateSort(nextStateData) {
    isSorting = true;
    startTime = Date.now();
    let startTimeProgress = 0;
    const durationMs = 1500; // Time for one pass to complete

    function animationLoop() {
        if (!isSorting) return;

        const elapsed = Math.min(durationMs, Date.now() - startTime);
        const progress = elapsed / durationMs;

        // Interpolate positions
        for (let i = 0; i < NUM_BARS; i++) {
            const bar = bars[i];
            const target = nextStateData[i];

            // Update position based on progress
            bar.x = bar.x + (target.targetX - bar.x) * progress * 0.1;
            bar.y = bar.y + (target.targetY - bar.y) * progress * 0.1;
            bar.color = target.newColor;
        }

        draw();

        if (progress < 1) {
            animationFrameId = requestAnimationFrame(animationLoop);
        } else {
            // Movement complete: Lock bars into final positions and update core data
            for (let i = 0; i < NUM_BARS; i++) {
                const bar = bars[i];
                const target = nextStateData[i];
                bar.x = target.targetX;
                bar.y = target.targetY;
                // We don't need to update the 'value' itself, as it remains constant, only the *order* changes
            }

            isSorting = false;
            passCount++;

            if (currentDigitPass < numDigits) {
                currentDigitPass++;
                setTimeout(() => startPass(), 500); // Wait half a second before starting next pass
            } else {
                // Sort complete!
                alert("Radix Sort Complete!");
                // Reset visualization for next run
                initializeBars();
                animateSort(getNextPositions(1)); // Start the next simulation cycle
            }
        }
    }

    animationFrameId = requestAnimationFrame(animationLoop);
}


function startPass() {
    if (isSorting) return;
    const passData = getNextPositions(currentDigitPass);
    animateSort(passData.nextState);
}


// --- Initialization and Event Handlers ---

window.addEventListener('resize', () => {
    width = canvas.width = Math.max(100, window.innerWidth * 0.95);
    height = canvas.height = Math.max(400, window.innerHeight * 0.6);
    initializeBars();
});

// Start the animation immediately
window.onload = () => {
    initializeBars();
    startPass();
};

</script></body></html>
```
qwen3-coder:30bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html><head><title>LSD Radix Sort</title></head><body style="background-color:#1a1a2e; display:flex; justify-content:center; align-items:center; height:100vh; margin:0;"><canvas id="c" width="800" height="500" style="border:1px solid #3f4269;"></canvas><div id="info" style="color:#e8e8ff; font-family:monospace; position:absolute; top:20px; left:50%; transform:translateX(-50%);">Pass: 0 | Digit: N/A</div><script>
const c = document.getElementById('c');
const ctx = c.getContext('2d');
let bars = [];
let maxVal = 0;
let passesDone = 0;
let currentDigitPlace = -1;

function generateRandomData() {
    bars = Array(80).fill(0).map(() => Math.floor(Math.random() * 90) + 10);
    maxVal = Math.max(...bars);
}

function drawBars() {
    ctx.clearRect(0, 0, c.width, c.height);
    const barWidth = c.width / bars.length;
    for (let i = 0; i < bars.length; i++) {
        // The stored value is the actual height/value at this stage of sorting
        ctx.fillStyle = `hsl(${(bars[i] * 10) % 360}, 70%, ${Math.min(50, Math.max(40, (passesDone % 5 + 1) * 8))}% )`;
        ctx.fillRect(i * barWidth, c.height - bars[i], barWidth - 1, bars[i]);
    }
}

function updateInfo() {
    const infoDiv = document.getElementById('info');
    if (currentDigitPlace > -1) {
        infoDiv.innerHTML = `Pass: ${passesDone + 1} | Digit: ${currentDigitPlace}`;
    } else if (passesDone === 0) {
         infoDiv.innerHTML = `Ready to Sort`;
    } else {
         infoDiv.innerHTML = `Sorting...`;
    }
}

function sortPass(k, arrayCopy, passIndex) {
    const n = bars.length;
    let output = Array(10).fill(0).map(() => []);
    for (let i = 0; i < n; i++) {
        const val = Math.floor(bars[i] / Math.pow(10, k)) % 10;
        output[val].push(i);
    }

    let newBars = Array(n).fill(0);
    let currentPos = 0;
    for (let i = 0; i < 10; i++) {
        if (output[i].length > 0) {
            for (const originalIndex of output[i]) {
                newBars[currentPos++] = bars[originalIndex]; // Keep the actual value for simplicity in visualization
            }
        }
    }

    bars = newBars;
}


function animate() {
    drawBars();
    updateInfo();

    if (passesDone < 3) { // Assuming max 3 digits (0-999 range is sufficient for visualization)
        currentDigitPlace++;
        sortPass(currentDigitPlace, bars, passesDone);
        passesDone++;
        requestAnimationFrame(animate);
    } else {
        // Finished sorting cycle. Reset after a delay.
        setTimeout(() => {
            generateRandomData();
            passesDone = 0;
            currentDigitPlace = -1;
            requestAnimationFrame(animate);
        }, 1500);
    }
}

generateRandomData();
animate();
</script></body></html>
```
deepseek-r1:14bSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html><body style="margin:0;display:flex;justify-content:center;align-items:center;height:100vh;background:#f0f0f0;">
<canvas id=c width=700 height=400></canvas>
<script>
const canvas = document.getElementById('c'), ctx = canvas.getContext('2d');
const numbers = Array(80).fill().map(() => Math.floor(Math.random() * 10000)), 
digits = [[],[],[],[]], maxDigit = 9, color = ['#00f', '#f00','#ff0','#0f0','#00f','#f0f','#ff0','#0ff','#000','#fff'];
let pass = 0, currentDigit = 0, digitPosition = [1,10,100,1000], pos = Array(80).fill().map(() => (Math.random() * (canvas.width - 80) + 4));
  
function draw() {
 ctx.clearRect(0,0,canvas.width,canvas.height);
 for(let i=0;i<numbers.length;i++) {
  const x = pos[i], barWidth = 6;
  ctx.fillStyle = 'white'; 
  ctx.fillRect(x-1,0,2,canvas.height); // left border
  ctx.fillRect(x + barWidth + 3,0,2,canvas.height); // right border
 }
}

function update() {
 const d = digitPosition[currentDigit], offset = 8;
 for(let i=0;i<numbers.length;i++) {
  const num = numbers[i];
  digits[currentDigit][i] = Math.floor(num / d) % 10;
 }
 
 let buckets = Array(10).fill().map(() => []), bpos = [];
 for(let i=0;i<digits[currentDigit].length;i++) buckets[digits[currentDigit][i]].push(i);
 
 bpos = Array(numbers.length).fill();
 let xoffset = 0;
 for(let b=0;b<10;b++) {
  const count = buckets[b].length;
  if(count > 0) {
   for(let idx of buckets[b]) {
    bpos[idx] = xoffset + (count - 1) * 3.5 + Math.random() * 3;
   }
   xoffset += count * 4;
  }
 }
 
 let changed = false;
 pos.forEach((p, i) => { 
  const target = bpos[i];
  if(p != target) {
   p += (target - p) * 0.15;
   changed = true;
  }
 });
 
 if(!changed) {
  pass++;
  if(pass >= 4) { // Reset after all passes complete
   numbers.forEach((num, i) => pos[i] = Math.random() * (canvas.width - 80) + 4);
   pass = 0;
   currentDigit = 0;
   return requestAnimationFrame(update);
  }
  currentDigit++;
  if(currentDigit >= 4) currentDigit = 0;
 }
 
 requestAnimationFrame(update);
}

function render() {
 ctx.fillStyle = '#fff';
 ctx.fillRect(0,0,canvas.width,canvas.height);
 ctx.font = '12px Arial';
 ctx.fillStyle = '#333';
 
 for(let i=0;i<numbers.length;i++) {
  const x = pos[i], y = canvas.height;
  const barWidth = 6, gap = 15;
  ctx.fillStyle = color[digits[currentDigit][i]];
  ctx.fillRect(x, canvas.height - numbers[i] * 3.5 / 4, barWidth, numbers[i] * 3.5 / 4);
 }
 
 // Draw digit indicator
 ctx.fillStyle = '#f00';
 ctx.fillRect(10, 10, 20, 10);
 ctx.fillStyle = 'white';
 ctx.fillRect(10, 10, 18, 8);
 ctx.font = '14px Arial';
 ctx.fillStyle = '#333';
 ctx.fillText(currentDigit + 1, 15, 16);
 
 requestAnimationFrame(render);
}

update();
render();
</script>
</body></html>
```
deepseek-r1:32bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html><html><body style="display:flex;flex-direction:column;align-items:center;background:#f0f0f0;font-family:sans-serif">
<h2>Radix Sort</h2>
<canvas id="c"></canvas>
<script>
const canvas=document.getElementById("c"), ctx=canvas.getContext("2d");
let bars=[], currentDigit=1, passNumber=0, sorting=true,delay=300;
const radix=10;

function initializeBars(){
    const max=Math.random()*400+50;
    bars=new Array(80).fill().map((_,i)=>({height:Math.random()*(max),x:i*13,y:bars?bars[i].y||0:0, currentDigit:0}));
    bars.sort((a,b)=>a.height-b.height);
}

function draw(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    canvas.width=window.innerWidth*.9;
    canvas.height=window.innerHeight*.75;
    const spacing=13;
    bars.forEach((bar,i)=>{
        const h=Math.min(canvas.height/2, bar.height*canvas.height/400), y=canvas.height/2 - h + (passNumber>bars.length?0:bar.y);
        ctx.fillStyle=`hsl(${(bar.currentDigit%radix)*360/radix},50%,50%)`;
        ctx.fillRect(i*spacing, y, spacing-3,h);
    });
}

function animate(timestamp){
    if(!sorting) return;
    draw();
    const nextDigit=Math.pow(radix, currentDigit);
    if(passNumber<4){
        if(passNumber===bars.length/currentDigit) processPass();
        else moveBars();
        requestAnimationFrame(animate);
    }else{
        sorting=false; setTimeout(()=>{
            passNumber=0;currentDigit=1;
            initializeBars();sorting=true; animate();
        },delay);
    }
}

function processPass(){
    const divisor=Math.pow(radix, currentDigit), mod=radix;
    bars.forEach((bar,i)=>{
        bar.currentDigit=Math.floor(bar.height/divisor)%mod;
    });
    let buckets=new Array(radix).fill().map(()=>[]);
    bars.forEach(bar=>buckets[bar.currentDigit].push(bar));
    const startPosition=bars.length/radix;
    bars.forEach((bar,i)=>{bar.targetX=i%startPosition + i*spacing});
    passNumber++;
    currentDigit++;
}

function moveBars(){
    let done=true;
    bars.forEach(bar=>{
        if(bar.y!=bar.targetY){
            bar.y+= (bar.targetY - bar.y)/10;
            done=false;
        }
    });
    if(!done) requestAnimationFrame(animate);
}

initializeBars();
animate();

window.addEventListener('resize', draw);
</script>
</body></html>
```
command-r:35bSTAT
Show code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Radix Sort Animation</title>
    <style>
        body {
            margin: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: #f4f4f4;
        }
        canvas {
            border: 2px solid #333;
            background-color: white;
        }
    </style>
</head>
<body>
    <canvas id="animationCanvas" width="600" height="400"></canvas>

    <script>
        const canvas = document.getElementById('animationCanvas');
        const ctx = canvas.getContext('2d');

        let bars = [];
        let pass = 1;
        let digitPlace = 0;

        function randomInt(min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        }

        function initBars() {
            for (let i = 0; i < 80; i++) {
                const barHeight = randomInt(50, canvas.height);
                bars.push({ height: barHeight });
            }

            bars.sort((a, b) => a.height - b.height);
        }

        function drawBars() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            for (let i = 0; i < bars.length; i++) {
                const barWidth = Math.floor(canvas.width / bars.length);
                const barStartX = i * barWidth;

                ctx.fillStyle = `rgb(${i % 256}, ${Math.floor(i / 3) % 256}, ${i * 19}%)`; 
                ctx.fillRect(barStartX, canvas.height - bars[i].height, barWidth, bars[i].height);
            }
        }

        function radixSort() {
            const maxDigit = String(Math.max(...bars.map(b => b.height))).length;

            for (let place = 0; place < maxDigit; place++) {
                digitPlace = place + 1;

                bars.sort((a, b) => getDigit(a.height, place) - getDigit(b.height, place));
                
                drawBars();
            }
        }

        function getDigit(num, digitPlace) {
            const strNum = num.toString().padStart(digitPlace + 1, '0');
            return parseInt(strNum[strNum.length - (digitPlace + 1)]);
        }

        function animate() {
            if (!bars.length) {
                initBars();
            }

            radixSort();

            requestAnimationFrame(animate);
        }

        animate();
    </script>
</body>
</html>
qwq:32bBLANKLIM
Show code
<think>

Okay, so I need to create an HTML file that animates the least-significant-digit radix sort using a canvas. The user wants it to be self-contained, no libraries, and under 7000 characters. Let me break this down.

First, the structure. I'll start with the basic HTML setup: doctype, html tags, head with a title, and a body. The canvas element will be in the body. Since it's self-contained, all the JavaScript has to be inside a script tag.

The animation needs to show bars moving as each digit place is processed. The bars are vertical, so I'll represent each with a rectangle. The number of bars is around 80, so maybe 80 elements. They should start with random heights.

Radix sort works by processing each digit from least to most significant. For each digit place (like units, tens, hundreds), we distribute the numbers into buckets based on that digit, then collect them back in order. Since it's LSD, we start with the rightmost digit.

The animation should show the current digit place and the pass number. Each pass corresponds to a digit place. So, for each digit position (starting from 0 for units), we'll need to distribute the bars into buckets based on that digit, then reorder them. The bars should move to their new positions while being colored according to their current bucket.

Wait, how do I handle the animation? Using requestAnimationFrame for smooth animation. The steps would be:

1. Initialize bars with random heights.
2. Start the first pass (digit 0, units place).
3. For each pass, determine the digit place, separate the numbers into buckets, then animate moving the bars into their new positions based on the buckets.
4. After the current digit place is processed (all passes for that digit are done), move to the next digit place (tens, hundreds, etc.).
5. Repeat until all digits have been processed.
6. At the end, restart with new random bars.

Hmm, but how to represent the digit places? Since numbers can be up to the maximum possible height, which is probably up to the canvas height. Wait, in the code, the bars' heights are random, so their maximum possible value would determine how many digits we need. But for simplicity, maybe just process up to 3 or 4 digits, which should cover typical random numbers.

Wait, but to be precise, we need to find the maximum number of digits in the initial array. Let me think: when starting, the heights are random, say between 0 and 500 (the canvas height maybe 500px). So the maximum number of digits would be 3 (since 500 has 3 digits). So the digit places would be 0 (units), 1 (tens), 2 (hundreds). So three passes total.

Wait, but how to handle the digit extraction. For each number, to get the digit at position d (starting from 0 for units), it would be (number / 10^d) % 10.

The animation steps per pass:

- For each bar, calculate its current digit (based on current digit place).
- Assign each bar to a bucket (0-9) based on that digit.
- The order of the bars in each pass should be the order of the buckets. So all the 0 bucket bars come first, then 1, etc.
- The bars need to move to their new positions in the order determined by the bucket sorting.

But how to animate this? Each bar should smoothly move from its current position to its new position in the next step.

Wait, but the animation needs to run in a way that each frame moves the bars closer to their target positions. Each pass (digit place) will have multiple frames where the bars move step by step, rather than jumping.

Alternatively, perhaps during the processing of each digit place, the bars are reordered in their positions based on their digit, and then the bars animate moving into their new order. So the process is:

- For the current digit place, compute the sorted order based on the current digit.
- Then, create an animation where each bar moves from their current position to their new position in the sorted order of the digit.
- After all bars have moved, proceed to the next digit place.

This way, each digit step is animated as a separate phase, with the bars moving into their new order based on that digit.

So the steps in code:

Initialize variables:

- An array of bars (each with a current height and target position).
- Keep track of current digit (starting at 0), pass count (each digit is a pass), and the current animation phase (whether moving to the next position).

The animation loop (requestAnimationFrame) will handle moving the bars towards their target positions. When they reach their targets, proceed to the next phase (next digit).

But how to structure this:

Maybe, the main loop (animation) checks if there is a current target setup. If not, then compute the next target positions for the current digit place.

Wait, here's a possible approach:

- The main animation loop runs continuously (using requestAnimationFrame).
- When in a "processing digit" phase, the bars are moving to their new positions based on the current digit.
- Once all bars have reached their target positions for the current digit, switch to the next digit.

So steps in code:

Initialize bars with random heights and starting positions. Positions are evenly spaced along the x-axis, so each bar has an initial x-coordinate based on its index.

The process for each digit:

1. Determine the current digit (starting at 0, then incrementing).
2. For each bar, compute the digit at the current digit position.
3. Group the bars by their digit (0-9).
4. The new positions of the bars are determined by the order of the groups and their order within the groups.
5. Assign each bar's target x-coordinate (based on their new position in the sorted order).
6. Then, in the animation loop, each frame moves the bars towards their target x-coordinate by a certain amount.
7. Once all bars have moved, move to the next digit.

Wait, but the target x-coordinate needs to be calculated such that the bars are ordered according to their digit's bucket. For instance, first all the 0 digit bars, then 1, etc., and within each digit group, their order is the same as their previous order before the current digit's processing.

Wait, the order in each bucket should be the same as they were when they came from the previous pass. So, for example, after processing the units digit, when moving to the tens digit, the current order is based on units. Now, in the next pass (tens), the buckets are based on tens digit, so each bucket (0-9) will hold the bars with that tens digit, ordered as they were before (i.e., their previous order from the units pass). This way, when you collect the buckets in order, the numbers are sorted by both tens and units digits.

Therefore, the target positions for the bars in the current digit's pass are arranged by:

- For each digit (d) from 0 to 9:
  - Collect all bars where the current digit is d, and their current order (from the previous passes)
- Concatenate all these lists to get the new order.

The new x-positions are based on their index in this new order.

So for each bar in the new order list, the target x is based on their new index (position in the list).

Therefore, each bar will have a target x that corresponds to their new position in the sorted buckets.

Now, the animation needs to move each bar's x-coordinate from its current position to this target x over several frames.

To track this, each bar could have a current x and a target x, and during animation frames, we move toward the target by some delta (like 10% per frame or something).

This way, each bar is smoothly moving to its new position.

Now, the steps in code:

First, setup:

- Create an array of bars, each with:

  - x: current x position
  - target_x: target x position (initialized to x)
  - height: the current height (starts as random)
  - digit: current digit value at the current digit place (but maybe not needed)
  - color: color based on the current digit's bucket (but only when in a pass, else default color?)

Wait, colors: during the current digit's processing, each bar's color should reflect which bucket (digit) they're in. So, when the animation is moving them to their new positions, their color should be based on the current digit's value. So, for each bar, during the current digit's pass, the digit is calculated, and that's used to determine the color.

Hmm, perhaps the color is computed as the current digit's value (from the current digit place). So, during each digit's processing phase, each bar's digit for that place is (bar's height / (10^digit)) % 10. The color can be a function of this digit, maybe a rainbow or sequential colors for digits 0-9.

The HTML and setup:

The canvas should have a reasonable size, say 800x500.

In the JavaScript:

Initialize:

var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var bars = [];
var digit = 0;
var pass = 0;
var animating = false;
var step = 0;
var done = false;

Wait, but variables need to be declared properly. Maybe using let and const.

Wait, perhaps better to use an object to hold state variables.

Wait, but to keep it concise, variables should be kept minimal.

First, in the setup:

var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var width = canvas.width = 800;
var height = canvas.height = 500;
var numBars = 80;
var bars = Array.from({length: numBars}, (_, i) => ({
  x: i * (width / numBars),
  targetX: i * (width / numBars),
  height: Math.random() * (height - 50) + 20, // between 20 and 450
  color: 'white' // default color
}));
var currentDigit = 0;
var currentPass = 0;
var maxDigit = 0;
var isAnimating = false;
var complete = false;

Wait, but how to determine maxDigit? The maximum digit needed can be computed based on the maximum height. So initially, find the max height, then compute how many digits it has. For example, if max is 500, then 3 digits, so maxDigit is 2 (since starting from 0).

Alternatively, compute maxDigit as Math.floor(Math.log10(maxHeight)) or similar. But since we can't know the max in advance, maybe compute it once at the start.

So:

var max = Math.max(...bars.map(b => b.height));
var maxDigit = max === 0 ? 0 : Math.floor(Math.log10(max));
var totalDigits = maxDigit + 1;

That way, totalDigits is the number of digits we need to process.

Wait, but since heights are numbers, the maximum digits needed is the number of digits in the largest number. For example if the maximum height is 999, that's 3 digits, so we need to process digit 0 (units), 1 (tens), 2 (hundreds).

So, totalDigits is that value.

Now, the animation steps:

The main loop is controlled by requestAnimationFrame. Each frame:

If the animation is currently moving towards targets (isAnimating), then we move each bar's x towards targetX by some fraction. Maybe 0.1 per frame? If they reach target, then we can mark that the animation is done.

Once all bars are at their target positions, we can proceed to the next digit.

Wait, but how to know when all bars have arrived. Maybe track a counter of bars not yet arrived.

Alternatively, after a certain number of steps, but that might not be accurate.

Alternatively, in each frame, check if any bar hasn't reached its target yet. If all have, then trigger the next phase.

Hmm, but that could be computationally intensive.

Alternatively, after moving, the targetX can be set to null once reached, and check if all have done so.

Alternatively, for simplicity, just move towards the target by a fixed amount per frame. Once the target is within a small delta, consider it there.

Alternatively, just use an easing function. But to keep it simple, perhaps:

bars.forEach(bar => {
  var dx = bar.targetX - bar.x;
  bar.x += dx * 0.2; // 20% of the distance per frame
  if (Math.abs(dx) < 0.1) bar.x = bar.targetX; // snap when close enough
});

This way, bars move towards their target, and when close enough, snap to it.

So, in the animation loop, after moving, check if any bars are still moving. If none, then proceed to the next phase.

The animation loop function:

function animate() {
  if (complete) return;

  ctx.clearRect(0, 0, width, height);

  // Draw bars and text
  bars.forEach(bar => {
    ctx.fillStyle = bar.color;
    ctx.fillRect(bar.x, height - bar.height, width/numBars, bar.height);
  });

  // Update positions
  if (isAnimating) {
    // Move towards target
    let allStopped = true;
    bars.forEach(bar => {
      let dx = bar.targetX - bar.x;
      bar.x += dx * 0.2;
      if (Math.abs(dx) > 0.1) allStopped = false;
      if (Math.abs(dx) < 0.1) bar.x = bar.targetX;
    });
    if (!allStopped) {
      requestAnimationFrame(animate);
      return;
    } else {
      // Animation done, proceed to next digit
      isAnimating = false;
      processNextDigit();
      requestAnimationFrame(animate);
      return;
    }
  } else {
    // Not animating, so check if need to do next phase
    if (currentDigit < totalDigits) {
      // Compute next digit's targets
      computeTargetPositions();
      isAnimating = true;
      requestAnimationFrame(animate);
      return;
    } else {
      // All done, reset
      complete = true;
      setTimeout(() => {
        reset();
        complete = false;
        animate();
      }, 1000); // Wait a sec before restarting
      return;
    }
  }

  requestAnimationFrame(animate);
}

Wait, but the logic might need to be adjusted. Let me think again.

The animate function is called every frame.

First, clear the canvas and draw the bars.

Then, if isAnimating, move the bars towards targetX. If all have reached their targets (allStopped is true), then after that frame, we set isAnimating to false and call processNextDigit, which would compute the next step.

Wait, but in the next frame, since isAnimating is false, it would proceed.

Alternatively, after setting allStopped, once it's done, then the processNextDigit is called.

Hmm, perhaps better to structure it as:

Inside the animate function:

if (isAnimating) {
  // Move bars
  bars.forEach(...);
  check if all stopped
  if not, continue
  else:
    isAnimating = false
    processNextDigit()
} else {
  // not animating
  if currentDigit is not done:
    compute targets for next digit, set isAnimating to true
    then, next frame will start moving
}

Wait, maybe I should restructure the flow.

The phases:

1. Initial state: bars are in original positions, no animation.

When currentDigit < totalDigits:

- Need to process the next digit.
- First, compute the target positions based on current digit.
- Set each bar's targetX to their new position in the sorted buckets.
- Then, start the animation (set isAnimating = true)
- Then, in the next frames, move towards targetX.

Once the animation is done (all stopped), processNextDigit() increments the currentDigit and starts again.

Wait, processNextDigit would:

currentDigit++;
if (currentDigit <= totalDigits) {
  computeTargetPositions();
  isAnimating = true;
} else {
  // done, reset
  reset();
}

Wait, but let's think step by step.

First, after initialization:

currentDigit is 0, isAnimating is false.

In the first call to animate:

since isAnimating is false and currentDigit < totalDigits, it calls computeTargetPositions (for digit 0), then sets isAnimating = true.

Then, in the next frame, isAnimating is true, so bars start moving.

Once all bars have reached their targets (after moving for some frames), in the next frame after all are stopped:

- isAnimating becomes false, then processNextDigit is called.
- processNextDigit increments currentDigit to 1, then calls computeTargetPositions again, and sets isAnimating = true again.
- So the next digit's animation starts.

Wait, but processNextDigit should be called after the animation is done. So, the 'processNextDigit' function would handle that.

Wait, let me structure the functions:

The computeTargetPositions() function would:

Given the current digit, re-calculate the targetX positions for all bars based on their digit in that place.

So:

function computeTargetPositions() {
  const d = currentDigit;
  const divisor = Math.pow(10, d);
  const digitBuckets = Array.from({length: 10}, () => []);

  // Assign each bar to their bucket based on current digit
  bars.forEach(bar => {
    const digit = Math.floor(bar.height / divisor) % 10;
    bar.digit = digit;
    digitBuckets[digit].push(bar);
  });

  // Flatten the buckets in order to get new order
  let newOrder = [];
  digitBuckets.forEach(bucket => newOrder = newOrder.concat(bucket));

  // Assign targetX based on their new position in newOrder
  const barWidth = width / numBars;
  newOrder.forEach((bar, index) => {
    bar.targetX = index * barWidth;
  });
}

Then, after this, the bars have targetX set. Then, the animation starts.

Once all bars have reached their targetX, next digit is processed.

So in the animate function:

if (isAnimating) {
  // move, check if stopped
  if (allStopped) {
    isAnimating = false;
    processNextDigit();
  }
} else {
  // not animating, so need to check if we need to compute next targets
  if (currentDigit < totalDigits) {
    computeTargetPositions();
    isAnimating = true;
  } else {
    // done, reset
    // ... reset code ...
  }
}

Wait, but in the animate function, after checking isAnimating, if it's false, but currentDigit < totalDigits, then computeTargetPositions, and set isAnimating to true, then on next frame, the animation starts.

So the processNextDigit() function is actually just the increment of currentDigit and possibly the computeTargetPositions?

Wait, perhaps processNextDigit() would be:

function processNextDigit() {
  currentDigit++;
  if (currentDigit <= totalDigits) {
    // compute next digit's targets
    computeTargetPositions();
    isAnimating = true;
  } else {
    // done, reset
    setTimeout(reset, 1000);
    complete = true;
  }
}

Hmm, but the structure in the animate function would need to call processNextDigit after the animation stops.

Alternatively, perhaps the animate function's flow is:

Inside the animate function:

if (isAnimating) {
  move bars, check if all stopped.
  if stopped, set isAnimating to false, and call processNextDigit()?
}

Wait, perhaps better to structure it as:

Inside the animate function, after moving and checking allStopped:

if (allStopped) {
  isAnimating = false;
  if (currentDigit < totalDigits) {
    currentDigit++;
    computeTargetPositions(); // no, wait, currentDigit is now next, so compute with that?
    isAnimating = true;
  } else {
    // done
    // reset
  }
}

Wait, this is getting a bit tangled. Maybe it's better to structure the processNextDigit as a separate function.

Alternatively, here's a possible structure:

After moving the bars, if all stopped:

- isAnimating = false
- increment currentDigit and check if it's within totalDigits. If yes, then need to start next digit's animation.

Wait, perhaps:

if (allStopped) {
  isAnimating = false;
  if (currentDigit < totalDigits) {
    // move to next digit
    currentDigit++;
    computeTargetPositions();
    isAnimating = true;
  } else {
    // done, reset
    complete = true;
    // after a delay, reset
    // ...
  }
}

But then, in this case, computeTargetPositions() would compute for the new currentDigit.

Wait, that's correct. Because after incrementing currentDigit, computeTargetPositions() will use that new digit.

Yes, that makes sense.

So in the animate function, when allStopped:

We set isAnimating to false, then:

if (currentDigit (now incremented) is still less than totalDigits, we compute the new currentDigit's target positions, and set isAnimating to true again.

Thus, the next frame will start moving towards those targets.

Okay, that seems better.

Now, the color of the bars can be based on their current digit. So during computeTargetPositions, we can set bar.digit, and then in the drawing phase, set color based on that.

In the drawing phase:

bars.forEach(bar => {
  ctx.fillStyle = bar.color;
  // ... draw
});

But the color depends on bar.digit. So perhaps:

bar.color = `hsl(${bar.digit * 36}, 100%, 50%)`; // or another function.

Wait, in the computeTargetPositions function, after computing the digit for each bar, set the color:

so in computeTargetPositions:

digitBuckets.forEach( (bucket, digit) => {
  bucket.forEach(bar => bar.color = `hsl(${digit * 36}, 100%, 50%)`);
});

Alternatively, in computeTargetPositions function, when calculating the digit for each bar:

bar.digit = digit;
bar.color = getColor(digit);

where getColor is a function that returns a color based on digit.

This would color each digit a different color, making it clear which digit is being sorted.

Alternatively, a simple gradient or rainbow.

Alternatively, during the computeTargetPositions, set the color to a digit-specific color.

Now, when the animation is done, and all digits are processed, the bars are sorted by their height? Wait no, because the process is grouping digits, but not sorting. Wait, this is actually a Radix sort visualization.

Radix sort is a non-comparative integer sorting algorithm that sorts data with integer keys by grouping keys by the individual digits which share the same significant position and value.

So the digit being processed is the current digit (units, tens, hundreds etc), and the algorithm groups the numbers into buckets based on that digit, then outputs them in order. After all digits are processed, the numbers are sorted.

Therefore, the way the target positions are computed above is correct for radix sort.

Therefore, once all digits have been processed, the bars will be ordered by their height in the correct sorted order.

Therefore, the reset should randomize the bars again, so that when restarted, it's a new set.

The reset function would re-initialize the bars array with new random heights, reset the currentDigit to 0, and set complete to false.

function reset() {
  bars = Array.from({length: numBars}, (_, i) => ({
    x: i * (width / numBars),
    targetX: i * (width / numBars),
    height: Math.random() * (height - 50) + 20,
    color: 'white',
    digit: 0
  }));
  max = Math.max(...bars.map(b => b.height));
  maxDigit = max === 0 ? 0 : Math.floor(Math.log10(max));
  totalDigits = maxDigit + 1;
  currentDigit = 0;
  currentPass = 0;
  isAnimating = false;
  complete = false;
}

Now, putting all together.

Also, in the animate function, need to display the current digit and pass.

So add text to the canvas.

In the drawing phase:

Draw the bars.

Then draw text like:

ctx.font = '20px Arial';
ctx.fillStyle = 'black';
ctx.fillText(`Digit: ${currentDigit}`, 10, 30);
ctx.fillText(`Pass: ${currentPass}`, 10, 60);

Wait, but the pass is currentPass, but in this case, each digit is a pass. Hmm, perhaps not necessary. Alternatively, just show the current digit.

Also, when resetting, we need to wait a second before restarting, so the animation can display the final state before resetting.

In the else part of the animate function (when done):

if (currentDigit < totalDigits) {
  // ... same as before ...
} else {
  complete = true;
  setTimeout(() => {
    reset();
    complete = false;
    animate();
  }, 1000);
}

Wait, in the else clause (after currentDigit exceeds totalDigits), we set complete = true, then after 1 sec, reset and restart.

Therefore, the animate function continues to be called, but once complete is true, it triggers the reset.

Now, putting all this together, the JavaScript code would be structured as follows:

Initialize variables.

Then define functions computeTargetPositions(), processNextDigit(), reset().

Then the animate function.

Finally, call animate() once to start.

Also, the canvas and context are initialized via querySelector.

Putting this all into code:

But I need to be careful with variable declarations and function order.

Let me try to write the full code step by step.

First, the HTML:

<canvas id="c"></canvas>

The JavaScript:

const canvas = document.querySelector('#c');
const ctx = canvas.getContext('2d');
const width = canvas.width = 800;
const height = canvas.height = 500;
const numBars = 80;
let bars = [];
let currentDigit = 0;
let totalDigits = 0;
let isAnimating = false;
let complete = false;

function reset() {
  bars = Array.from({length: numBars}, (_, i) => ({
    x: i * (width / numBars),
    targetX: i * (width / numBars),
    height: Math.random() * (height - 50) + 20,
    color: 'white',
    digit: 0
  }));
  const max = Math.max(...bars.map(b => b.height));
  totalDigits = max === 0 ? 0 : Math.floor(Math.log10(max)) + 1; // +1 because e.g log10(100)=2, but 3 digits
}

reset();

function computeTargetPositions() {
  const d = currentDigit;
  const divisor = Math.pow(10, d);
  const digitBuckets = Array.from({length: 10}, () => []);
  
  bars.forEach(bar => {
    const digit = Math.floor(bar.height / divisor) % 10;
    bar.digit = digit;
    digitBuckets[digit].push(bar);
  });
  
  // Assign colors based on digit
  digitBuckets.forEach((bucket, digit) => {
    bucket.forEach(bar => bar.color = `hsl(${digit * 36}, 100%, 50%)`);
  });
  
  // Flatten the buckets to get new order
  const newOrder = digitBuckets.reduce((acc, bucket) => acc.concat(bucket), []);
  
  // Assign target positions
  const barWidth = width / numBars;
  newOrder.forEach((bar, index) => {
    bar.targetX = index * barWidth;
  });
}

function animate() {
  requestAnimationFrame(animate);
  
  if (complete) return;
  
  ctx.clearRect(0, 0, width, height);
  
  // Draw bars
  bars.forEach(bar => {
    ctx.fillStyle = bar.color;
    ctx.fillRect(bar.x, height - bar.height, width/numBars, bar.height);
  });
  
  // Draw info
  ctx.fillStyle = 'black';
  ctx.font = '20px Arial';
  ctx.fillText(`Current Digit: ${currentDigit}`, 10, 30);
  
  // Update positions
  if (isAnimating) {
    let allStopped = true;
    bars.forEach(bar => {
      if (bar.x !== bar.targetX) {
        const dx = bar.targetX - bar.x;
        bar.x += dx * 0.1; // smooth movement
        allStopped = false;
      }
    });
    
    if (allStopped) {
      isAnimating = false;
      if (currentDigit < totalDigits) {
        currentDigit++;
        computeTargetPositions();
        isAnimating = true;
      } else {
        complete = true;
        setTimeout(() => {
          reset();
          currentDigit = 0;
          computeTargetPositions();
          isAnimating = true;
          complete = false;
        }, 1000);
      }
    }
  } else {
    if (currentDigit < totalDigits) {
      computeTargetPositions();
      isAnimating = true;
    }
  }
}

// Start the animation
animate();

Wait, but there might be some issues here.

First, in computeTargetPositions, the newOrder is the flattened array, but the targetX for each bar is set to index * barWidth. So the first bucket (digit 0) will be placed first, then digit 1, etc.

This correctly assigns targetX positions according to the bucket order.

In the animate function, during isAnimating, the bars move towards their targetX, using a smooth step (dx * 0.1). So they slowly move to their target.

When allStopped becomes true, meaning all bars have reached their targetX (within some tolerance, but since we're moving by a fraction each frame, it might take a few frames to stop), then we check if currentDigit is still less than totalDigits.

If yes, increment currentDigit, compute the next target positions, and start animating again.

If all digits are processed (currentDigit >= totalDigits), then we set complete to true. After a second, reset the bars and restart.

However, in the code above, after resetting, it calls computeTargetPositions again, which recomputes the first targets for currentDigit=0. Then sets isAnimating to true, so it can start the next cycle.

Wait, in the code:

Inside the else block (currentDigit >= totalDigits):

setTimeout(() => {
  reset();
  currentDigit = 0;
  computeTargetPositions();
  isAnimating = true;
  complete = false;
}, 1000);

This should restart the animation.

But perhaps need to call computeTargetPositions after reset, because during reset, the bars are initialized to their initial positions (x = i * barWidth, targetX the same, so no movement). So after reset, to start the process again, we need to compute the first targets when currentDigit is 0.

Yes, that seems correct.

Testing this code might reveal some issues, but this is the general approach.

Possible adjustments:

- The color assignment. The current code sets the color based on the digit, but since during the animation, the digit is fixed for each bar (they retain their digit for the current pass), the color remains the digit color throughout. That's okay.

- The movement speed. The current code uses a step of 0.1 of the remaining distance each frame. This could be adjusted for smoother or faster movement.

- The initial reset: when first starting, the bars are placed at their initial positions (x = index * barWidth), but after reset, they are again set to those positions. So the first time computeTargetPositions is called (when currentDigit is 0), it will compute their target positions based on the first digit, which may differ.

Overall, this should work as a visualization of the Radix sort algorithm.
Outside the Brain cluster — same task, same character budget, collected through their own tooling (response times not comparable)
Claude Opus 5frontier referenceOK
Show code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>LSD Radix Sort — Canvas</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#080c18}
canvas{display:block}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv=document.getElementById('c'),g=cv.getContext('2d');
const N=80,PL=[1,10,100],NM=['ones (1s)','tens (10s)','hundreds (100s)'];
let W,H,bars,pi,ph,t;
function fit(){const d=Math.min(2,devicePixelRatio||1);W=innerWidth;H=innerHeight;cv.width=W*d;cv.height=H*d;g.setTransform(d,0,0,d,0,0)}
addEventListener('resize',fit);fit();
const dg=(v,p)=>Math.floor(v/p)%10;
function prep(){const bk=[];for(let d=0;d<10;d++)bk.push([]);
 for(const b of bars){b.fx=b.x;bk[dg(b.v,PL[pi])].push(b)}
 let i=0;for(const q of bk)for(const b of q)b.tx=i++}
function init(){bars=[];for(let i=0;i<N;i++)bars.push({v:Math.floor(Math.random()*1000),x:i,fx:i,tx:i});pi=0;ph=0;t=0;prep()}
const es=x=>x<.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;
function step(dt){t+=dt;
 if(ph==0){if(t>.6){ph=1;t=0}}
 else if(ph==1){const k=Math.min(1,t/1.25),e=es(k);
  for(const b of bars)b.x=b.fx+(b.tx-b.fx)*e;
  if(k>=1){bars.sort((a,b)=>a.tx-b.tx);bars.forEach((b,i)=>{b.x=i;b.fx=i});ph=2;t=0}}
 else if(ph==2){if(t>.4){pi++;if(pi>2){ph=3;t=0}else{prep();ph=0;t=0}}}
 else if(t>1.7){init()}}
function draw(){
 const gr=g.createLinearGradient(0,0,0,H);gr.addColorStop(0,'#0d1226');gr.addColorStop(1,'#04060f');
 g.fillStyle=gr;g.fillRect(0,0,W,H);
 const w=W/N,top=96,base=H-46,mh=Math.max(40,base-top);
 const p=PL[Math.min(pi,2)],done=ph==3;
 g.textAlign='center';g.font=(Math.min(12,w*.8)|0)+'px monospace';
 for(const b of bars){const d=dg(b.v,p),h=Math.max(3,(b.v+1)/1000*mh),x=b.x*w;
  g.fillStyle=done?'hsl(150,70%,55%)':'hsl('+d*35+',85%,'+(ph==0?62:54)+'%)';
  g.fillRect(x+.6,base-h,Math.max(1,w-1.2),h);
  if(!done&&w>9){g.fillStyle='rgba(255,255,255,.55)';g.fillText(d,x+w/2,base+16)}}
 g.fillStyle='#111a33';g.fillRect(0,base+1,W,1);
 g.textAlign='left';g.fillStyle='#eef2ff';g.font='600 22px system-ui,sans-serif';
 g.fillText('LSD Radix Sort',24,38);
 g.font='14px monospace';g.fillStyle='#8ea0c8';
 g.fillText(done?'sorted — reshuffling…':'pass '+(pi+1)+' / 3    digit place: '+NM[pi]+'    ('+(ph==1?'moving to buckets':'bucketing by digit')+')',24,66);
 for(let d=0;d<10;d++){const x=W-24-(10-d)*26;
  g.fillStyle='hsl('+d*35+',85%,58%)';g.fillRect(x,24,20,10);
  g.fillStyle='#8ea0c8';g.textAlign='center';g.fillText(d,x+10,50)}
 const pw=W-48,pr=Math.min(1,(pi+(ph>0?1:0))/3);
 g.fillStyle='#18213c';g.fillRect(24,base+24,pw,4);
 g.fillStyle='#5eead4';g.fillRect(24,base+24,pw*pr,4)}
let last=0;
function loop(ts){const dt=Math.min(.05,(ts-last)/1000||0);last=ts;step(dt);draw();requestAnimationFrame(loop)}
init();requestAnimationFrame(loop);
</script>
</body>
</html>
Claude Sonnet 5frontier referenceOK
Show code
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Radix Sort Visualization</title>
<style>html,body{margin:0;background:#111;overflow:hidden}canvas{display:block}#info{position:fixed;top:10px;left:10px;color:#eee;font:16px monospace;text-shadow:0 0 4px #000}</style>
</head><body>
<div id="info"></div>
<canvas id="c"></canvas>
<script>
const c=document.getElementById('c'),ctx=c.getContext('2d'),info=document.getElementById('info');
function resize(){c.width=innerWidth;c.height=innerHeight;}
resize();addEventListener('resize',resize);
const N=80,MAXV=999,PLACES=3,DUR=50,HOLD=25;
let vals,cur,tgt,start,place,pass,animT,phase,holdT;
function digitAt(v,p){return Math.floor(v/Math.pow(10,p))%10;}
function computeTargets(){
  let order=new Array(N);
  for(let i=0;i<N;i++)order[Math.round(cur[i])]=i;
  let b=Array.from({length:10},()=>[]);
  for(let s=0;s<N;s++){let idx=order[s];b[digitAt(vals[idx],place)].push(idx);}
  let no=[].concat(...b);
  for(let s=0;s<N;s++)tgt[no[s]]=s;
}
function newRound(){
  vals=Array.from({length:N},()=>Math.floor(Math.random()*(MAXV+1)));
  cur=vals.map((_,i)=>i);tgt=cur.slice();start=cur.slice();
  place=0;pass=1;animT=0;phase='animate';holdT=0;
  computeTargets();start=cur.slice();
}
newRound();
function ease(t){return t<.5?2*t*t:1-Math.pow(-2*t+2,2)/2;}
function step(){
  if(phase==='animate'){
    animT++;
    let t=ease(Math.min(animT/DUR,1));
    for(let i=0;i<N;i++)cur[i]=start[i]+(tgt[i]-start[i])*t;
    if(animT>=DUR){cur=tgt.slice();phase='hold';holdT=0;}
  } else {
    holdT++;
    if(holdT>=HOLD){
      place++;
      if(place>=PLACES){newRound();}
      else{pass++;computeTargets();start=cur.slice();animT=0;phase='animate';}
    }
  }
}
function draw(){
  ctx.fillStyle='#111';ctx.fillRect(0,0,c.width,c.height);
  const bw=c.width/N;
  const maxH=c.height-60;
  for(let i=0;i<N;i++){
    let d=digitAt(vals[i],place);
    let h=(vals[i]/MAXV)*maxH;
    let x=cur[i]*bw;
    ctx.fillStyle=`hsl(${d*36},75%,55%)`;
    ctx.fillRect(x+1,c.height-h,bw-2,h);
  }
  const places=['ones','tens','hundreds'];
  info.textContent=`Radix Sort (LSD) — Pass ${pass}/${PLACES} — Digit place: ${places[place]||place}`;
}
function loop(){step();draw();requestAnimationFrame(loop);}
loop();
</script>
</body></html>
Claude Fable 5frontier referenceOK
Show code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>LSD Radix Sort</title>
<style>
html,body{margin:0;height:100%;background:#0b0e14;overflow:hidden}
canvas{display:block}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv=document.getElementById('c'),ctx=cv.getContext('2d');
let W,H;
function rs(){W=cv.width=innerWidth;H=cv.height=innerHeight}
rs();
addEventListener('resize',rs);
const N=80,PASSES=3,MAXV=999,NAMES=['1s (ones)','10s (tens)','100s (hundreds)'];
let bars,arr,pass,phase,timer;
function digit(v,p){return Math.floor(v/Math.pow(10,p))%10}
function init(){
 bars=[];
 for(let i=0;i<N;i++)bars.push({v:1+Math.floor(Math.random()*MAXV),x:i,tx:i});
 arr=bars.slice();
 pass=-1;phase='wait';timer=40;
}
function startPass(){
 pass++;
 if(pass>=PASSES){phase='done';timer=140;return}
 const buckets=[];
 for(let d=0;d<10;d++)buckets.push([]);
 for(const b of arr)buckets[digit(b.v,pass)].push(b);
 arr=[].concat(...buckets);
 arr.forEach((b,i)=>b.tx=i);
 phase='move';
}
function barColor(b){
 if(pass<0)return '#5a6478';
 if(phase==='done')return 'hsl(145 60% 52%)';
 return 'hsl('+digit(b.v,pass)*36+' 75% 58%)';
}
function draw(){
 ctx.fillStyle='#0b0e14';
 ctx.fillRect(0,0,W,H);
 const mL=40,mR=40,top=110,bot=46;
 const span=W-mL-mR,bw=span/N,plotH=Math.max(60,H-top-bot);
 for(const b of bars){
  const x=mL+b.x*bw,h=(b.v/MAXV)*plotH;
  ctx.fillStyle=barColor(b);
  ctx.fillRect(x+bw*0.1,H-bot-h,Math.max(1,bw*0.8),h);
 }
 ctx.strokeStyle='#2a3242';
 ctx.beginPath();
 ctx.moveTo(mL,H-bot+0.5);
 ctx.lineTo(W-mR,H-bot+0.5);
 ctx.stroke();
 ctx.fillStyle='#e8ecf4';
 ctx.font='700 22px system-ui,sans-serif';
 ctx.textAlign='left';
 ctx.fillText('LSD Radix Sort — '+N+' bars',mL,34);
 ctx.font='15px system-ui,sans-serif';
 ctx.fillStyle='#9aa7bd';
 let status;
 if(pass<0)status='Shuffling a fresh random array…';
 else if(phase==='done')status='Pass 3 / 3 complete — fully sorted. Restarting…';
 else status='Pass '+(pass+1)+' / '+PASSES+'  ·  digit place: '+NAMES[pass]+(phase==='move'?'  ·  moving bars to bucket order':'  ·  buckets settled');
 ctx.fillText(status,mL,58);
 if(pass>=0&&phase!=='done'){
  ctx.font='12px system-ui,sans-serif';
  ctx.textAlign='center';
  for(let d=0;d<10;d++){
   const lx=mL+d*58;
   ctx.fillStyle='hsl('+d*36+' 75% 58%)';
   ctx.fillRect(lx,72,16,16);
   ctx.fillStyle='#9aa7bd';
   ctx.fillText(String(d),lx+8,100);
  }
  ctx.textAlign='left';
 }
}
function tick(){
 if(phase==='wait'||phase==='pause'){
  if(--timer<=0)startPass();
 }else if(phase==='move'){
  let done=true;
  for(const b of bars){
   const d=b.tx-b.x;
   if(Math.abs(d)>0.02){b.x+=d*0.085;done=false}
   else b.x=b.tx;
  }
  if(done){phase='pause';timer=55}
 }else if(phase==='done'){
  if(--timer<=0)init();
 }
 draw();
 requestAnimationFrame(tick);
}
init();
requestAnimationFrame(tick);
</script>
</body>
</html>
Claude Haiku 4.5frontier referenceOK
Show code
<!DOCTYPE html><html><head><style>*{margin:0}body{background:#000;display:flex;justify-content:center;align-items:center;height:100vh;font-family:monospace;color:#fff;overflow:hidden}canvas{border:1px solid #fff}#info{position:absolute;top:15px;left:15px;font-size:16px}</style></head><body><div id="info"></div><canvas></canvas><script>
const canvas=document.querySelector('canvas'),ctx=canvas.getContext('2d'),info=document.querySelector('#info');
canvas.width=960;canvas.height=480;
const BARS=80,colors=['#f44','#f84','#fd4','#4f4','#44f','#b4f','#f4b','#4ff','#8cf','#ccc'];
let arr=[],pos=[],npos=[],digt=0,pass=0,prog=0;

function dig(n,d){return Math.floor(n/Math.pow(10,d))%10}

function sort(){
  if(digt>2){
    arr=Array.from({length:BARS},()=>Math.floor(Math.random()*999));
    pos=Array.from({length:BARS},(_,i)=>i);
    digt=0;pass=0;prog=0;
    sort();
    return;
  }
  const cnt=Array(10).fill(0);
  for(let v of arr)cnt[dig(v,digt)]++;
  const p=[0];
  for(let i=1;i<10;i++)p[i]=p[i-1]+cnt[i-1];
  npos=Array(BARS);
  const b=Array(10).fill().map(()=>[]);
  for(let i=0;i<BARS;i++)b[dig(arr[i],digt)].push({v:arr[i],i});
  let k=0;
  for(let i=0;i<10;i++)
    for(let x of b[i]){
      npos[x.i]=k;
      arr[k++]=x.v;
    }
  const tmp=Array(BARS);
  for(let j=0;j<BARS;j++)tmp[npos[j]]=j;
  pos=tmp;
  digt++;pass++;prog=0;
}

function anim(){
  ctx.fillStyle='#000';ctx.fillRect(0,0,960,480);
  const w=12,mx=Math.max(...arr);
  const t=Math.min(prog/40,1);
  for(let i=0;i<BARS;i++){
    const x=(pos[i]*(1-t)+i*t)*w;
    const h=arr[i]/mx*480;
    ctx.fillStyle=colors[dig(arr[i],digt-1)];
    ctx.fillRect(x,480-h,w-1,h);
  }
  info.textContent=`Digit:${digt-1} Pass:${pass}`;
  prog++;
  if(t<1)requestAnimationFrame(anim);
  else{
    sort();
    requestAnimationFrame(anim);
  }
}

arr=Array.from({length:BARS},()=>Math.floor(Math.random()*999));
pos=Array.from({length:BARS},(_,i)=>i);
sort();
anim();
</script></body></html>
Results at a glance— every model in this round with score, response time and status.

Who wrote what

ModelBlind scoreLatencyStatus
qwen3:8b harness
qwen3.5:9b 44.7 sOK
gemma4:26b harness
llama3.1:8b 71.4 sOK
mistral-small:24b 73.7 sOK
qwen3:14b 29.1 sOK
qwen3-coder:30b 15.5 sOK
deepseek-r1:14b 44.5 sOK
deepseek-r1:32b 67.6 sOK
command-r:35b 257.5 sOK
qwq:32b 234.8 sOK