Escape Room 3.15 — Level 3 Random Values in JavaScript
Drag-and-drop spinner algorithm, and demos using Math.random(), randint(), choice, and shuffle in JS
🔒 Level 3 — Random Values in JavaScript
Welcome to the final chamber!
In this level, we’ll explore how random values work in JavaScript.
🎲 Math.random()
Math.random()
generates a decimal in [0, 1).- Multiply by a range, then floor it to get integers.
Example: roll a die
Math.floor(Math.random() * 6) + 1; // → 1 to 6 inclusive
Example 1: Random decimal in [0, 1)
%%javascript
// Roll a die: random integer between 1 and 6
let roll = Math.floor(Math.random() * 6) + 1;
console.log("You rolled:", roll);
<IPython.core.display.Javascript object>
Example 2: Random Choice from a List
%%javascript
// Pick a random fruit from a list
let fruits = ["apple", "banana", "cherry", "grape"];
let index = Math.floor(Math.random() * fruits.length);
console.log("Random fruit:", fruits[index]);
<IPython.core.display.Javascript object>
Example 3: Shuffle a List
%%javascript
// Shuffle numbers using a simple swap method
let numbers = [1, 2, 3, 4, 5];
// Fisher-Yates shuffle
for (let i = numbers.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = temp;
}
console.log("Shuffled:", numbers);
<IPython.core.display.Javascript object>
🧱 Final Lock — Build the Spinner
Goal: Assemble the blocks to build a correct JavaScript algorithm for a weighted spinner with 8 equal slices:
- Green: 3/8
- Blue: 2/8
- Red: 1/8
- Orange: 1/8
- Purple: 1/8
How it should work
- Generate an integer from 1 to 8 (inclusive) with JavaScript: ```js let spin = Math.floor(Math.random() * 8) + 1;
%%html
<div id="l3-sim" style="margin:18px 0; padding:16px; border:1px solid rgba(255,255,255,.12); border-radius:14px; background:linear-gradient(180deg,#101635,#0b1026);">
<h3 style="margin:0 0 12px;">🔒 Final Lock — Build the Spinner Algorithm (JS)</h3>
<p style="margin:6px 0 12px; color:#cfe7ff">
Drag the blocks into the <strong>Assembly Area</strong> (top → bottom). Then press <em>Check Structure</em>. If correct, you can run the simulation.
</p>
<div style="display:grid; gap:12px; grid-template-columns:1fr 1fr;">
<!-- Palette -->
<div>
<h4 style="margin:0 0 8px;">🧰 Block Palette</h4>
<div id="palette" style="min-height:240px; padding:10px; border-radius:10px; background:rgba(255,255,255,.04); display:grid; gap:8px;">
<!-- Blocks injected by JS -->
</div>
<button id="reset" style="margin-top:10px;">↺ Reset Blocks</button>
</div>
<!-- Assembly -->
<div>
<h4 style="margin:0 0 8px;">🧱 Assembly Area (top executes first)</h4>
<div id="assembly" style="min-height:240px; padding:10px; border-radius:10px; background:rgba(255,255,255,.06); outline:2px dashed rgba(255,255,255,.18); display:grid; gap:8px;"></div>
<div style="display:flex; gap:8px; margin-top:10px;">
<button id="check">✅ Check Structure</button>
<button id="run" disabled>▶ Run 10,000 Spins</button>
</div>
<div id="feedback" style="margin-top:8px; font-weight:600;"></div>
<div id="results" style="margin-top:10px;"></div>
</div>
</div>
<style>
/* General text inside the activity */
#l3-sim,
#l3-sim h3,
#l3-sim h4,
#l3-sim p,
#l3-sim code,
#l3-sim div,
#l3-sim span {
color: #e9eefc; /* bright off-white */
}
/* Buttons */
#l3-sim button {
padding:6px 10px;
border-radius:6px;
border:1px solid rgba(255,255,255,.25);
background:rgba(255,255,255,.06);
color:#e9eefc;
cursor:pointer;
}
#l3-sim button:hover {
background: rgba(255,255,255,.12);
}
#l3-sim button[disabled] {
opacity:.5;
cursor:not-allowed;
}
/* Blocks */
#l3-sim .block {
user-select:none; cursor:grab; padding:8px 10px; border-radius:8px;
border:1px solid rgba(255,255,255,.18);
background:rgba(255,255,255,.08);
color:#e9eefc;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
display:flex; align-items:center; gap:8px;
}
#l3-sim .block:active { cursor:grabbing; }
/* Block tag badges */
#l3-sim .tag {
font-size:.75rem;
padding:2px 6px;
border-radius:999px;
background:rgba(255,255,255,.15);
color:#cfe7ff;
}
/* Feedback */
#l3-sim .ok { color:#7CFC00; }
#l3-sim .bad { color:#FF6B6B; }
/* Results pills */
#l3-sim .pill {
display:inline-block;
padding:2px 8px;
border-radius:999px;
background:rgba(255,255,255,.12);
color:#e9eefc;
margin-right:6px;
}
/* Indentation for nested-style blocks */
#l3-sim .indent-1 { margin-left:16px; }
#l3-sim .indent-2 { margin-left:32px; }
</style>
<script>
(function(){
// ---- Target algorithm order (IDs must match):
// 1) assign, 2) ifG, 3) elifB, 4) elifR, 5) elifO, 6) elseP
const EXPECT = ["assign","ifG","elifB","elifR","elifO","elseP"];
const BLOCKS = [
// Core blocks (now shown in JavaScript syntax)
{ id:"assign", text:"let spin = Math.floor(Math.random() * 8) + 1;", tag:"SET", indent:0 },
{ id:"ifG", text:"if (spin <= 3) { display('Green'); }", tag:"IF", indent:0 },
{ id:"elifB", text:"else if (spin === 4 || spin === 5) { display('Blue'); }", tag:"ELSE IF", indent:0 },
{ id:"elifR", text:"else if (spin === 6) { display('Red'); }", tag:"ELSE IF", indent:0 },
{ id:"elifO", text:"else if (spin === 7) { display('Orange'); }", tag:"ELSE IF", indent:0 },
{ id:"elseP", text:"else { display('Purple'); }", tag:"ELSE", indent:0 },
// Decoys (JS syntax but subtly wrong to prompt thinking)
{ id:"decoy1", text:"if (spin < 3) { display('Green'); }", tag:"IF", indent:0 }, // uses < instead of <=
{ id:"decoy2", text:"else if (spin >= 6) { display('Purple'); }", tag:"ELSE IF", indent:0 } // collapses too many cases
];
const palette = document.getElementById("palette");
const assembly = document.getElementById("assembly");
const feedback = document.getElementById("feedback");
const results = document.getElementById("results");
const checkBtn = document.getElementById("check");
const runBtn = document.getElementById("run");
const resetBtn = document.getElementById("reset");
// Create a draggable block element
function makeBlock(b){
const el = document.createElement("div");
el.className = "block " + (b.indent?("indent-"+b.indent):"");
el.draggable = true;
el.dataset.blockId = b.id;
el.innerHTML = `<span class="tag">${b.tag}</span><span>${b.text}</span>`;
el.addEventListener("dragstart", function(ev){
ev.dataTransfer.setData("text/plain", b.id);
ev.dataTransfer.setData("from", el.parentElement.id);
});
return el;
}
function renderPalette(){
palette.innerHTML = "";
BLOCKS.forEach(function(b){ palette.appendChild(makeBlock(b)); });
}
function renderAssemblyPlaceholders(){
assembly.innerHTML = "";
assembly.addEventListener("dragover", function(ev){ ev.preventDefault(); });
assembly.addEventListener("drop", onDropAssembly);
}
function onDropAssembly(ev){
ev.preventDefault();
const id = ev.dataTransfer.getData("text/plain");
const from = ev.dataTransfer.getData("from");
if(!id) return;
// Prevent duplicates of core blocks when dragged from palette
if(from==="palette" && Array.from(assembly.querySelectorAll(".block")).some(function(el){ return el.dataset.blockId===id; })){
return;
}
const def = BLOCKS.find(function(b){ return b.id===id; });
if(!def) return;
if(from==="assembly"){
const node = Array.from(assembly.children).find(function(el){ return el.dataset.blockId===id; });
if(node){ assembly.removeChild(node); assembly.appendChild(node); }
} else {
assembly.appendChild(makeBlock(def));
}
feedback.textContent = "";
results.innerHTML = "";
runBtn.disabled = true;
}
// Allow dropping back into palette to remove from assembly
palette.addEventListener("dragover", function(ev){ ev.preventDefault(); });
palette.addEventListener("drop", function(ev){
ev.preventDefault();
const id = ev.dataTransfer.getData("text/plain");
const from = ev.dataTransfer.getData("from");
if(from==="assembly"){
const node = Array.from(assembly.children).find(function(el){ return el.dataset.blockId===id; });
if(node) assembly.removeChild(node);
}
feedback.textContent = "";
results.innerHTML = "";
runBtn.disabled = true;
});
// Structure checker
checkBtn.onclick = function(){
const ids = Array.from(assembly.querySelectorAll(".block")).map(function(el){ return el.dataset.blockId; });
const exact = JSON.stringify(ids) === JSON.stringify(EXPECT);
if(ids.length === 0){
feedback.innerHTML = `<span class="bad">Add blocks to the Assembly Area first.</span>`;
runBtn.disabled = true;
return;
}
if(exact){
feedback.innerHTML = `<span class="ok">Perfect structure! You can now run the simulation.</span>`;
runBtn.disabled = false;
} else {
// Targeted hints (JS wording)
var hint = "";
if(!ids.includes("assign")) hint = "Start with <code>let spin = Math.floor(Math.random() * 8) + 1;</code>";
else if(ids[0] !== "assign") hint = "The <code>let spin = ...</code> assignment block must be first.";
else if(!ids.includes("ifG")) hint = "Add the <code>if (spin <= 3)</code> block for Green right after the assignment.";
else if(ids.indexOf("elifB") < ids.indexOf("ifG")) hint = "The Blue check must come after the Green check.";
else hint = "Order should be: assign → if(Green) → else if(Blue) → else if(Red) → else if(Orange) → else(Purple).";
feedback.innerHTML = `<span class="bad">Not quite.</span> ${hint}`;
runBtn.disabled = true;
}
results.innerHTML = "";
};
// Simulation: 10k spins, show empirical percentages
runBtn.onclick = function(){
const trials = 10000;
const counts = {Green:0, Blue:0, Red:0, Orange:0, Purple:0};
for(var i=0;i<trials;i++){
var spin = Math.floor(Math.random()*8)+1; // 1..8
var color = "";
if(spin <= 3) color = "Green";
else if (spin===4 || spin===5) color = "Blue";
else if (spin===6) color = "Red";
else if (spin===7) color = "Orange";
else color = "Purple";
counts[color]++;
}
function pct(c){ return (c/trials*100).toFixed(1) + "%"; }
results.innerHTML = `
<div><span class="pill">Green</span> ${pct(counts.Green)} (target 37.5%)</div>
<div><span class="pill">Blue</span> ${pct(counts.Blue)} (target 25.0%)</div>
<div><span class="pill">Red</span> ${pct(counts.Red)} (target 12.5%)</div>
<div><span class="pill">Orange</span> ${pct(counts.Orange)} (target 12.5%)</div>
<div><span class="pill">Purple</span> ${pct(counts.Purple)} (target 12.5%)</div>
<div style="margin-top:8px; opacity:.85;">(Targets are slices/8: 3/8, 2/8, 1/8, 1/8, 1/8)</div>
`;
};
resetBtn.onclick = function(){
renderPalette();
renderAssemblyPlaceholders();
feedback.textContent = "";
results.innerHTML = "";
runBtn.disabled = true;
};
// Initial render
renderPalette();
renderAssemblyPlaceholders();
})();
</script>
</div>
🔒 Final Lock — Build the Spinner Algorithm (JS)
Drag the blocks into the Assembly Area (top → bottom). Then press Check Structure. If correct, you can run the simulation.