Bank it · controlled dice game — Keep a turn subtotal separate from banked points, discard only the subtotal on a one and award victory only after banking.

HTML for this example

Select the code and use your copy shortcut. Changes in this preview are temporary.

<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Bank it · controlled dice game</title><style>
*{box-sizing:border-box}body{margin:0;background:#fbece5;color:#382244;font:16px/1.5 system-ui,sans-serif}main{max-width:1000px;margin:auto;padding:28px}h1{font-size:clamp(34px,6vw,58px);letter-spacing:-.05em;line-height:1.1;margin:5px 0 10px}p{margin:0 0 12px}.label{font-weight:750;font-size:12px;letter-spacing:.12em;text-transform:uppercase}.sub{color:#73576d}.table{border-radius:25px;background:#663a7c;padding:24px;color:white;margin:24px 0}.scoreboard{display:grid;grid-template-columns:1fr 1fr;gap:18px}.player{background:#ffffff16;border:2px solid #ffffff30;border-radius:15px;padding:18px}.player.active{background:#fff5cc;color:#382244;border-color:#f8cd63}.bank{font-size:62px;line-height:1.1;font-weight:800}.meter{height:7px;background:#ffffff32;border-radius:10px;margin-top:15px;overflow:hidden}.meter i{display:block;height:100%;background:#ed9962;width:0}.play{display:grid;grid-template-columns:130px 1fr;gap:25px;align-items:center;padding:25px 0 5px}.die{height:120px;width:120px;background:#fff;border-radius:23px;box-shadow:0 7px 0 #38224450;color:#382244;display:grid;place-items:center;font-size:85px;line-height:1}.turn{font-size:38px;font-weight:750;line-height:1.2}.controls{display:flex;gap:12px;flex-wrap:wrap;margin-top:22px}button{min-height:48px;border:0;border-radius:11px;background:#f7c85d;color:#382244;padding:11px 24px;font:750 16px system-ui;cursor:pointer}button.secondary{background:#fff;color:#663a7c}button:disabled{opacity:.5;cursor:default}:focus-visible{outline:3px solid #e96d3b;outline-offset:4px}.status{font-size:19px;margin:15px 0 0;font-weight:650}.queue{font-size:14px;margin-top:17px;color:#eddcf5}.ledger{background:white;border-radius:18px;padding:24px}.ledger h2{font-size:22px;margin:0 0 15px}.ledger ol{padding:0 0 0 23px;margin:0}.ledger li{padding:8px 0;border-bottom:1px solid #eee4ec;font-size:14px}.lower{display:grid;grid-template-columns:1fr 1fr;gap:25px;margin-top:25px}.lower h2{font-size:20px;margin:0 0 10px}.small{font-size:14px;color:#73576d}details{background:#ffffff90;padding:16px;border-radius:12px}summary{cursor:pointer;font-weight:650;min-height:35px}label{font-size:14px;font-weight:650}input{font:16px system-ui;display:block;padding:10px;width:100%;margin:8px 0 12px;border:1px solid #b49bab;border-radius:7px}.error{color:#9a2632;min-height:22px;font-size:14px}footer{font-size:13px;color:#73576d;margin:24px 0}@media(max-width:650px){main{padding:20px 15px}.table{padding:18px}.lower{grid-template-columns:1fr}.play{grid-template-columns:90px 1fr;gap:18px}.die{width:85px;height:85px;font-size:64px;border-radius:17px}.bank{font-size:46px}.turn{font-size:29px}.player{padding:13px}.controls button{flex:1;padding:10px 14px}}
</style></head><body><main><p class="label">Two players · target 20 · controlled fixture</p><h1>Bank it.</h1><p class="sub">Roll to build your turn. Hold to bank it. A one loses only the unbanked points.</p><section class="table" aria-label="Dice game"><div class="scoreboard"><div class="player active" id="player-a"><p class="label">Player A · banked</p><div class="bank" id="bank-a">0</div><div class="meter"><i id="progress-a"></i></div></div><div class="player" id="player-b"><p class="label">Player B · banked</p><div class="bank" id="bank-b">0</div><div class="meter"><i id="progress-b"></i></div></div></div><div class="play"><div class="die" id="die" role="img" aria-label="No face rolled">—</div><div><p id="actor" class="label">Player A's turn</p><div id="subtotal" class="turn">0 unbanked</div><p style="margin:5px 0 0">Banked points are safe. Turn points are at risk.</p></div></div><div class="controls"><button type="button" id="roll">Roll</button><button type="button" id="hold" class="secondary">Hold</button><button type="button" id="reset" class="secondary">Reset</button></div><p id="result" class="status" role="status">Player A starts. No winner yet.</p><p id="queue-status" class="queue">Test queue: 4, 5, 1, 6, 6, 6, 2 · next entry 1 of 7</p></section><section class="ledger"><h2>Action ledger</h2><p id="empty-history" class="small">Roll or Hold to begin. Every confirmed action keeps its before/after state.</p><ol id="history"></ol></section><div class="lower"><section><h2>How a turn ends</h2><p class="small">Roll 2–6: add to this turn. Roll 1: lose this turn's subtotal and switch. Hold: add the subtotal to your bank, then switch unless your bank reaches 20. Holding zero is allowed.</p><p class="small">The supplied queue is deterministic test data. After its final entry, Hold still works; Roll never invents another face.</p></section><details><summary>Try a different controlled queue</summary><label for="queue-input">Comma-separated faces · each next entry must be 1–6<input id="queue-input" value="4,5,1,6,6,6,2"></label><button type="button" id="load-queue" class="secondary">Start with this queue</button><p class="small" style="margin-top:10px">Starts a new local game. An invalid next entry stops without being consumed; correct the queue and start again. Reset always restores the original queue.</p><p id="queue-error" class="error" role="alert"></p></details></div><footer>Original short Pig-style teaching variant. No random or fairness claim, money, rewards, saved scores, or network play.</footer></main><script>
'use strict';const $=id=>document.getElementById(id);const original=['4','5','1','6','6','6','2'];
function fresh(queue=original){return{banks:[0,0],turn:0,actor:0,winner:null,cursor:0,face:null,queue:[...queue],history:[],message:'Player A starts. No winner yet.'};}
let game=fresh();
function transact(s,action){if(s.winner!==null)return s;const next={...s,banks:[...s.banks],history:[...s.history]};const actor=s.actor,label=actor===0?'A':'B',before='banks '+s.banks.join('/')+', turn '+s.turn;let verb='Hold';if(action==='roll'){if(s.cursor>=s.queue.length)return{...s,message:'Queue exhausted. Supply more fixture data or Hold the current subtotal.'};const raw=String(s.queue[s.cursor]).trim();if(!/^[1-6]$/.test(raw))return{...s,message:'Invalid face at entry '+(s.cursor+1)+'. Expected an integer from 1 to 6; entry not consumed.'};const face=Number(raw);next.cursor++;next.face=face;verb='Roll '+face;if(face===1){next.turn=0;next.actor=1-actor;next.message='Player '+label+' busts. Banked scores stay '+s.banks.join(' / ')+'.';}else{next.turn+=face;next.message='Player '+label+' has '+next.turn+' unbanked. Hold to commit; no winner yet.';}}else if(action==='hold'){next.banks[actor]+=s.turn;next.turn=0;if(next.banks[actor]>=20){next.winner=actor;next.message='Player '+label+' wins with '+next.banks[actor]+' banked points!';}else{next.actor=1-actor;next.message='Player '+label+' banks '+s.turn+'. Player '+(next.actor===0?'A':'B')+' is next.';}}else return s;next.history.push('Player '+label+' · '+verb+' · before '+before+' → banks '+next.banks.join('/')+', turn '+next.turn+(next.winner!==null?' · WIN':' · '+(next.actor===0?'A':'B')+' active'));return next;}
function render(){for(const i of [0,1]){const tag=i===0?'a':'b';$('bank-'+tag).textContent=game.banks[i];$('player-'+tag).className='player'+(game.actor===i?' active':'');$('progress-'+tag).style.width=Math.min(game.banks[i]/20*100,100)+'%';}$('actor').textContent=game.winner===null?'Player '+(game.actor===0?'A':'B')+"'s turn":'Game complete';$('subtotal').textContent=game.turn+' unbanked';$('result').textContent=game.message;$('die').textContent=game.face===null?'—':['','⚀','⚁','⚂','⚃','⚄','⚅'][game.face];$('die').setAttribute('aria-label',game.face===null?'No face rolled':'Accepted face '+game.face);$('roll').disabled=$('hold').disabled=game.winner!==null;$('queue-status').textContent='Test queue: '+game.queue.join(', ')+' · '+(game.cursor>=game.queue.length?'exhausted':('next entry '+(game.cursor+1)+' of '+game.queue.length));$('empty-history').hidden=game.history.length>0;$('history').replaceChildren();game.history.forEach(text=>{let li=document.createElement('li');li.textContent=text;$('history').appendChild(li);});}
for(const action of ['roll','hold'])$(action).addEventListener('click',()=>{game=transact(game,action);render();});$('reset').addEventListener('click',()=>{game=fresh();$('queue-input').value=original.join(',');$('queue-error').textContent='';render();});$('load-queue').addEventListener('click',()=>{const text=$('queue-input').value.trim();if(!text){$('queue-error').textContent='Enter at least one fixture entry. The current game is unchanged.';return;}$('queue-error').textContent='';game=fresh(text.split(',').map(v=>v.trim()));render();});for(const id of ['roll','hold','reset','load-queue'])$(id).addEventListener('keydown',e=>{if(e.repeat&&(e.key==='Enter'||e.key===' '))e.preventDefault();});render();
</script></body></html>