Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>Binomial Free-Throw Challenge (Discrete Distributions)</title> | |
| <style> | |
| :root{ | |
| --blue:#0b6ef6; --bg:#0f172a; --card:#111827; --muted:#94a3b8; --ok:#10b981; --bad:#ef4444; --accent:#f59e0b; | |
| } | |
| *{box-sizing:border-box} | |
| body{ | |
| margin:0; font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial; | |
| background:linear-gradient(180deg,#0b132b,#1b2433 55%,#0f172a); | |
| color:#e5e7eb; line-height:1.45; | |
| padding:24px; | |
| } | |
| header{ | |
| display:flex; flex-wrap:wrap; gap:12px; align-items:center; justify-content:space-between; | |
| } | |
| h1{font-size:1.4rem; margin:0} | |
| .tagline{color:#cbd5e1; font-size:.95rem} | |
| .card{ | |
| background:rgba(17,24,39,.7); border:1px solid #1f2937; border-radius:16px; padding:16px; box-shadow:0 10px 30px rgba(0,0,0,.25); | |
| } | |
| .grid{ | |
| display:grid; grid-template-columns:repeat(2,minmax(260px,1fr)); gap:16px; margin-top:16px; | |
| } | |
| label{display:block; font-size:.9rem; color:#cbd5e1; margin:6px 0 4px} | |
| input[type="number"], input[type="range"]{ | |
| width:100%; | |
| } | |
| .controls{display:grid; grid-template-columns:1fr 1fr; gap:12px} | |
| .row{display:flex; gap:12px; align-items:center} | |
| .hint{font-size:.85rem; color:#94a3b8} | |
| .buttons{display:flex; gap:8px; flex-wrap:wrap; margin-top:10px} | |
| button{ | |
| background:var(--blue); color:#fff; border:none; border-radius:999px; padding:10px 14px; font-weight:600; cursor:pointer; | |
| transition:transform .07s ease, filter .15s ease; | |
| } | |
| button:hover{filter:brightness(1.08)} | |
| button:active{transform:scale(.98)} | |
| .secondary{background:#334155} | |
| .danger{background:#b91c1c} | |
| .success{background:var(--ok)} | |
| .pill{padding:.15rem .6rem; border:1px solid #334155; border-radius:999px; color:#cbd5e1; font-size:.8rem} | |
| #shots{ | |
| display:grid; grid-template-columns:repeat(auto-fill,minmax(24px,1fr)); gap:6px; | |
| min-height:120px; align-content:start; | |
| } | |
| .shot{ | |
| width:100%; aspect-ratio:1/1; border-radius:6px; border:1px solid #1f2937; | |
| display:grid; place-items:center; font-size:.7rem; font-weight:700; | |
| } | |
| .hit{background:rgba(16,185,129,.15); color:#34d399; border-color:#065f46} | |
| .miss{background:rgba(239,68,68,.12); color:#f87171; border-color:#7f1d1d} | |
| .panel{display:grid; gap:8px} | |
| .stat{display:flex; gap:12px; align-items:center; justify-content:space-between; font-variant-numeric:tabular-nums} | |
| .big{font-size:1.35rem; font-weight:800} | |
| #hist{height:220px; display:flex; align-items:flex-end; gap:6px; border-left:2px solid #334155; border-bottom:2px solid #334155; padding:8px} | |
| .bar{flex:1; display:flex; flex-direction:column; justify-content:flex-end; align-items:center} | |
| .bar .block{width:100%; background:linear-gradient(180deg,#60a5fa,#2563eb); border-radius:6px 6px 0 0} | |
| .bar .theory{width:100%; opacity:.55; background:linear-gradient(180deg,#fbbf24,#f59e0b)} | |
| .bar label{font-size:.75rem; color:#94a3b8} | |
| .footer{margin-top:14px; color:#9ca3af; font-size:.85rem} | |
| .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace} | |
| .highlight{color:#fde68a} | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div> | |
| <h1>Binomial Free-Throw Challenge</h1> | |
| <div class="tagline">Choose <span class="pill">n</span> shots and success chance <span class="pill">p</span>, guess the makes, then simulate. (A binomial model of <em>n</em> Bernoulli trials.)</div> | |
| </div> | |
| <div class="pill mono">Discrete Distribution: Binomial(n, p)</div> | |
| </header> | |
| <section class="grid"> | |
| <!-- Left: Controls + Results --> | |
| <div class="card"> | |
| <div class="controls"> | |
| <div> | |
| <label for="n">Number of shots (n)</label> | |
| <input id="n" type="number" min="1" max="100" value="20" /> | |
| </div> | |
| <div> | |
| <label for="p">Make probability p = <span id="pVal" class="highlight">0.60</span></label> | |
| <input id="p" type="range" min="0" max="1" step="0.01" value="0.60" oninput="pVal.textContent=this.value" /> | |
| </div> | |
| </div> | |
| <div class="row" style="margin-top:8px"> | |
| <div style="flex:1"> | |
| <label for="guess">Your guess: makes this round</label> | |
| <input id="guess" type="number" min="0" value="12" /> | |
| <div class="hint">Score = <span class="mono">100 − 10×|guess − actual|</span> (min 0)</div> | |
| </div> | |
| <div class="panel" style="min-width:220px"> | |
| <div class="stat"><span>Expected makes E[X]=np</span><span id="exp" class="mono"></span></div> | |
| <div class="stat"><span>Variance Var[X]=np(1−p)</span><span id="var" class="mono"></span></div> | |
| <div class="stat"><span>St.dev</span><span id="sd" class="mono"></span></div> | |
| </div> | |
| </div> | |
| <div class="buttons"> | |
| <button id="play">Shoot this round</button> | |
| <button class="secondary" id="multi">Run 200 rounds → histogram</button> | |
| <button class="danger" id="reset">Reset all</button> | |
| </div> | |
| <div class="panel" style="margin-top:12px"> | |
| <div class="stat"> | |
| <span class="big">Round Result</span> | |
| <span id="roundSummary" class="mono"></span> | |
| </div> | |
| <div id="shots"></div> | |
| <div class="stat"> | |
| <span>Total Score</span> | |
| <span id="score" class="big mono">0</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right: Histogram --> | |
| <div class="card"> | |
| <div class="stat"> | |
| <span class="big">Empirical Distribution (Simulated) vs Theoretical PMF</span> | |
| <span id="simCount" class="mono">0 sims</span> | |
| </div> | |
| <div id="hist"></div> | |
| <div class="footer"> | |
| Bars show relative frequency of total makes <span class="mono">X~Binomial(n,p)</span>. Blue = simulation; amber = exact binomial <span class="mono">P(X=k)=C(n,k)p^k(1−p)^(n−k)</span>. | |
| </div> | |
| </div> | |
| </section> | |
| <section class="card" style="margin-top:16px"> | |
| <strong>What you’re modeling:</strong> When outcomes are <em>countable</em>, the binomial distribution counts successes across a fixed number of independent trials with constant success chance <span class="mono">p</span>. Try changing <span class="mono">n</span> and <span class="mono">p</span> and watch the histogram shift: bigger <span class="mono">n</span> narrows the spread (relative), while <span class="mono">p</span> moves the center. | |
| </section> | |
| <script> | |
| // ===== Utilities ===== | |
| const byId = id => document.getElementById(id); | |
| const nEl = byId('n'), pEl = byId('p'), guessEl = byId('guess'); | |
| const expEl = byId('exp'), varEl = byId('var'), sdEl = byId('sd'); | |
| const shotsDiv = byId('shots'), roundSummary = byId('roundSummary'), scoreEl = byId('score'); | |
| const histDiv = byId('hist'), simCountEl = byId('simCount'); | |
| let totalScore = 0, sims = [], n=Number(nEl.value), p=Number(pEl.value); | |
| function updateStats(){ | |
| n = Number(nEl.value); p = Number(pEl.value); | |
| guessEl.max = n; | |
| const EX = n*p; | |
| const Var = n*p*(1-p); | |
| const SD = Math.sqrt(Var); | |
| expEl.textContent = EX.toFixed(2); | |
| varEl.textContent = Var.toFixed(2); | |
| sdEl.textContent = SD.toFixed(2); | |
| renderHistogram(); // refresh theoretical overlay | |
| } | |
| updateStats(); | |
| pEl.addEventListener('input', updateStats); | |
| nEl.addEventListener('change', ()=>{ if(guessEl.value>nEl.value) guessEl.value=nEl.value; updateStats(); }); | |
| // Single Bernoulli(p) | |
| function bernoulli(prob){ return Math.random() < prob ? 1 : 0; } | |
| // Run one Binomial round of n trials | |
| function runRound(){ | |
| shotsDiv.innerHTML=''; | |
| const results = []; | |
| for(let i=0;i<n;i++){ | |
| const r = bernoulli(p); | |
| results.push(r); | |
| const cell = document.createElement('div'); | |
| cell.className = 'shot ' + (r? 'hit':'miss'); | |
| cell.textContent = r? '✓':'×'; | |
| shotsDiv.appendChild(cell); | |
| } | |
| const makes = results.reduce((a,b)=>a+b,0); | |
| const guess = Math.max(0, Math.min(n, Number(guessEl.value)||0)); | |
| const roundScore = Math.max(0, 100 - 10*Math.abs(guess - makes)); | |
| totalScore += roundScore; | |
| roundSummary.textContent = `Made ${makes} / ${n} (you guessed ${guess}) → +${roundScore}`; | |
| scoreEl.textContent = totalScore; | |
| // record for histogram | |
| sims.push(makes); | |
| renderHistogram(); | |
| } | |
| // Run many simulations quickly | |
| function runMany(count=200){ | |
| for(let i=0;i<count;i++){ | |
| const makes = Array.from({length:n}, ()=>bernoulli(p)).reduce((a,b)=>a+b,0); | |
| sims.push(makes); | |
| } | |
| renderHistogram(); | |
| } | |
| // Binomial coefficient and PMF | |
| function choose(N,K){ | |
| if(K<0||K>N) return 0; | |
| if(K===0||K===N) return 1; | |
| // multiplicative for stability | |
| let c=1; | |
| for(let i=1;i<=K;i++){ | |
| c = c * (N - (K - i)) / i; | |
| } | |
| return c; | |
| } | |
| function binomPMF(k, N, P){ | |
| return choose(N,k) * Math.pow(P,k) * Math.pow(1-P, N-k); | |
| } | |
| // Histogram render | |
| function renderHistogram(){ | |
| const counts = Array.from({length:n+1}, ()=>0); | |
| sims.forEach(v=>{ if(v>=0 && v<=n) counts[v]++; }); | |
| const total = sims.length || 1; | |
| const freqs = counts.map(c=>c/total); // 0..1 | |
| const theory = Array.from({length:n+1}, (_,k)=>binomPMF(k,n,p)); | |
| const maxY = Math.max(0.05, ...freqs, ...theory); // avoid zero | |
| histDiv.innerHTML=''; | |
| freqs.forEach((f,k)=>{ | |
| const bar = document.createElement('div'); bar.className='bar'; | |
| const block = document.createElement('div'); block.className='block'; | |
| block.style.height = `${(f/maxY)*200}px`; | |
| const t = document.createElement('div'); t.className='block theory'; | |
| t.style.height = `${(theory[k]/maxY)*200}px`; | |
| // stack: theoretical at back, simulated in front | |
| const stack = document.createElement('div'); stack.style.width='100%'; stack.style.display='grid'; | |
| stack.style.alignItems='end'; | |
| stack.appendChild(t); stack.appendChild(block); | |
| bar.appendChild(stack); | |
| const lab = document.createElement('label'); lab.textContent = k; | |
| bar.appendChild(lab); | |
| histDiv.appendChild(bar); | |
| }); | |
| simCountEl.textContent = `${sims.length} sims`; | |
| } | |
| // Buttons | |
| byId('play').addEventListener('click', runRound); | |
| byId('multi').addEventListener('click', ()=>runMany(200)); | |
| byId('reset').addEventListener('click', ()=>{ | |
| sims = []; totalScore = 0; shotsDiv.innerHTML=''; roundSummary.textContent=''; | |
| scoreEl.textContent='0'; renderHistogram(); | |
| }); | |
| // First paint | |
| renderHistogram(); | |
| </script> | |
| </body> | |
| </html> | |