HTML5 Checkers: Finish the Same Piece’s Capture
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>HTML5 Checkers: Finish the Same Piece’s Capture</title><style>:root{color-scheme:light;--ink:#203947;--paper:#f5f4ee;--accent:#265f70;--tint:#e3edf0}*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font:17px/1.6 system-ui,sans-serif}main{max-width:1060px;margin:auto;padding:clamp(20px,4vw,42px)}header{border-bottom:2px solid var(--ink);margin-bottom:24px;padding-bottom:16px}h1{font:700 clamp(28px,4vw,42px)/1.17 Georgia,serif;margin:10px 0 16px}h2{font:700 24px/1.25 Georgia,serif;margin:14px 0}h3{font-size:inherit}p{margin:12px 0 20px}.eyebrow{font-size:16px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}mark{background:#dceda6;color:#183526;padding:0 3px}nav{display:flex;gap:10px;flex-wrap:wrap;margin:18px 0 24px}button{font:600 16px/1.35 system-ui;border:1.5px solid var(--accent);background:#fff;color:var(--ink);border-radius:8px;padding:12px 16px;min-height:46px;cursor:pointer;max-width:100%;overflow-wrap:anywhere}button:hover{background:var(--tint)}button:disabled{opacity:.45;cursor:default}button:focus-visible,input:focus-visible,textarea:focus-visible,summary:focus-visible{outline:3px solid #a94c25;outline-offset:3px}input,textarea{font:inherit;display:block;color:var(--ink);background:#fff;border:1px solid #7c969d;border-radius:7px;max-width:100%;width:100%;padding:12px;margin:6px 0 16px}textarea{min-height:100px}label{font-weight:600;display:block;font-size:16px}section{min-width:0;padding:16px 0;border-bottom:1px solid #c8d4d4}.twocol{display:grid;grid-template-columns:1fr 1fr;gap:28px}.prompt{border-left:5px solid var(--accent);padding:16px 20px;background:var(--tint);font-size:19px}.result{padding:18px 22px;background:var(--ink);color:#fff;border-radius:10px;font-weight:600;white-space:pre-line}.result:empty{display:none}.ledger{margin:20px 0;overflow-wrap:anywhere}table{width:100%;table-layout:fixed;border-collapse:collapse;font-size:16px}th,td{text-align:left;vertical-align:top;padding:12px 8px;border-bottom:1px solid #ccd6d7;overflow-wrap:anywhere}th{background:var(--tint)}pre{font:16px/1.6 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;background:#e8edeb;padding:18px;max-width:100%;border-radius:8px}svg{display:block;max-width:100%;width:100%;height:auto;max-height:440px}.board{display:grid;gap:5px;width:100%;max-width:520px;margin:24px auto;padding:10px;background:#c4d5d9;border-radius:14px}.cell{display:flex;align-items:center;justify-content:center;aspect-ratio:1;min-width:0;min-height:0;padding:0;font:700 clamp(16px,3vw,24px)/1 system-ui;background:#fdfcf7;border:0;border-radius:6px}.four{grid-template-columns:repeat(4,1fr)}.five{grid-template-columns:repeat(5,1fr)}.seven,.connect{grid-template-columns:repeat(7,1fr)}.eight{grid-template-columns:repeat(8,1fr);gap:2px;padding:4px}.eight .cell{font-size:16px}.light{background:#f4ead5}.dark{background:#5c7468;color:#fff}.chosen{outline:3px solid #a54823;outline-offset:-3px}.p1,.alive{background:#8b3e29;color:white}.p2{background:#205e75;color:white}.wall{background:#35525d;color:white}.v2,.v4{background:#e7d5ad}.v8,.v16,.v32{background:#d1994b}.v64,.v128,.v256,.v512,.v1024,.v2048{background:#285d68;color:white}.tokens,.hands{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}.hands button{margin:5px 4px 5px 0}.notice{padding:24px;background:#fff;border:1px solid #adbec2}.notice h3{font-size:1.2em}.treemap{display:flex;min-height:220px;background:#d6e0e1}.tile{flex-basis:0;min-width:0;background:#2b6274;color:white;border:2px solid var(--paper);padding:12px;overflow-wrap:anywhere}.tile:nth-child(even){background:#774a32}.paths{padding:20px;background:var(--tint);overflow-wrap:anywhere}details{margin:22px 0}summary{cursor:pointer;font-weight:700}footer{border-top:1px solid #b6c8c9;margin-top:28px;padding-top:18px;font-size:16px}.classroom{--paper:#faf5e9;--ink:#263d2d;--accent:#426b3c;--tint:#e7ecd8}.game{--paper:#eef3f5;--ink:#173b4c;--accent:#386f85;--tint:#dcebf0}.code{--paper:#f5f3ee;--ink:#34374c;--accent:#575b86;--tint:#e5e4f0}@media(max-width:650px){.twocol,.hands{grid-template-columns:1fr}.tokens{grid-template-columns:1fr}main{padding:20px}.board{gap:3px;padding:6px}.eight{gap:1px;padding:2px}.connect .cell,.seven .cell{font-size:16px}th,td{padding:10px 5px}button{padding:12px}.prompt{padding:14px}.result{padding:16px}h1{font-size:30px}}</style></head>
<body class="game"><main><header><div class="eyebrow">Original interactive state lab</div><h1>HTML5 Checkers: Finish the Same Piece’s Capture</h1><p>Red jumps once, then another piece tries a quiet move while a second jump remains. <mark>The capture chain belongs to the moving piece</mark>; selecting a different piece cannot end it.</p></header>
<nav><button type="button" id="chain">Chain preset</button><button type="button" id="crown">Crown preset</button><button type="button" id="resign">Resign current player</button><button type="button" id="offer">Previous mover offers draw</button><button type="button" id="accept">Accept draw as current player</button><button type="button" id="decline">Decline draw</button><button type="button" id="reset">Reset full board</button></nav><div id="board" class="board eight"></div><p id="result" class="result" role="status"></p><div id="ledger" class="ledger"></div><p>Zero-based row,column coordinates. R/W identify color; K identifies a king. Captures are compulsory; crown-stop ends the turn.</p>
<footer>Reloading restores the full opening board with Red to move.</footer></main><script>
(()=>{'use strict';const q=id=>document.querySelector('#'+id);const set=(id,value)=>{q(id).textContent=String(value);};const on=(id,callback)=>q(id).addEventListener('click',callback);const escapeHtml=value=>String(value).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));const table=(id,heads,rows)=>{q(id).innerHTML='<table><thead><tr>'+heads.map(h=>'<th scope="col">'+escapeHtml(h)+'</th>').join('')+'</tr></thead><tbody>'+rows.map(row=>'<tr>'+row.map(value=>'<td>'+escapeHtml(value)+'</td>').join('')+'</tr>').join('')+'</tbody></table>';};const field=(id,label,value='')=>'<label for="'+id+'">'+escapeHtml(label)+'</label><input id="'+id+'" value="'+escapeHtml(value)+'">';const btn=(id,label)=>'<button type="button" id="'+id+'">'+escapeHtml(label)+'</button>';const fmt=value=>Number(value).toFixed(6);
(function(){let pieces=[],turn='R',selected=null,chain=null,pending=new Set(),terminal='',offer=null,lastMover=null;function at(r,c){return pieces.find(p=>p.r===r&&p.c===c);}function dirs(p){return p.king?[-1,1]:[p.owner==='R'?1:-1];}function captures(p){const out=[];for(const dr of dirs(p))for(const dc of[-1,1]){const enemy=at(p.r+dr,p.c+dc),r=p.r+2*dr,c=p.c+2*dc;if(enemy&&enemy.owner!==p.owner&&!pending.has(enemy.id)&&r>=0&&r<8&&c>=0&&c<8&&!at(r,c))out.push({piece:p.id,r,c,take:enemy.id});}return out;}function actions(){const own=pieces.filter(p=>p.owner===turn&&!pending.has(p.id)),caps=own.flatMap(captures);if(chain)return caps.filter(a=>a.piece===chain);if(caps.length)return caps;const out=[];for(const p of own)for(const dr of dirs(p))for(const dc of[-1,1]){const r=p.r+dr,c=p.c+dc;if(r>=0&&r<8&&c>=0&&c<8&&!at(r,c))out.push({piece:p.id,r,c});}return out;}function render(message='',focusId=''){const legal=terminal?[]:actions();q('board').innerHTML=Array.from({length:64},(_,i)=>{const r=Math.floor(i/8),c=i%8,p=at(r,c),label=p?(p.owner+(p.king?'K':'')+(pending.has(p.id)?'×':'')):'·';return `<button type="button" id="sq${r}${c}" class="cell ${(r+c)%2?'dark':'light'} ${p?.id===selected?'chosen':''}" aria-label="Row ${r} column ${c}: ${p?p.id+(p.king?' king':' man'):'empty'}">${label}</button>`;}).join('');for(let r=0;r<8;r++)for(let c=0;c<8;c++)on('sq'+r+c,()=>choose(r,c));set('result',terminal||`${turn==='R'?'Red':'White'} to play. ${chain?'Capture chain locked to '+chain+'. ':''}${message}`);set('ledger',`Pieces ${pieces.length}; pending captured ${[...pending].join(', ')||'none'}. Legal destinations: ${legal.map(a=>a.piece+'→('+a.r+','+a.c+')').join('; ')||'none'}. ${offer?'Draw offered by '+offer+'.':''}`);if(focusId)q(focusId).focus();}function finish(){pieces=pieces.filter(p=>!pending.has(p.id));pending.clear();chain=null;selected=null;lastMover=turn;turn=turn==='R'?'W':'R';offer=null;if(!pieces.some(p=>p.owner===turn)||actions().length===0)terminal=(turn==='R'?'White':'Red')+' wins: opponent has no legal move.';}function choose(r,c){if(terminal)return;const focusId='sq'+r+c;const p=at(r,c),legal=actions();if(p&&p.owner===turn&&!pending.has(p.id)){if(chain&&p.id!==chain)return render('Finish the same piece’s capture.',focusId);selected=p.id;return render('Selected '+p.id+'.',focusId);}const action=legal.find(a=>a.piece===selected&&a.r===r&&a.c===c);if(!action)return render('Illegal destination; position and turn unchanged.',focusId);offer=null;const moving=pieces.find(p=>p.id===selected);moving.r=r;moving.c=c;if(action.take)pending.add(action.take);const crowns=!moving.king&&r===(turn==='R'?7:0);if(crowns)moving.king=true;if(action.take&&!crowns&&captures(moving).length){chain=moving.id;}else finish();render('',focusId);}function preset(kind){turn='R';selected=chain=null;pending=new Set();terminal='';offer=lastMover=null;if(kind==='chain')pieces=[['R1','R',2,1],['R2','R',2,5],['W1','W',3,2],['W2','W',5,4]].map(([id,owner,r,c])=>({id,owner,r,c,king:false}));else if(kind==='crown')pieces=[['R3','R',5,0],['W3','W',6,1],['W4','W',6,3]].map(([id,owner,r,c])=>({id,owner,r,c,king:false}));else{pieces=[];for(let r=0;r<8;r++)for(let c=0;c<8;c++)if((r+c)%2&&(r<3||r>4))pieces.push({id:(r<3?'R':'W')+pieces.length,owner:r<3?'R':'W',r,c,king:false});}render();}on('chain',()=>preset('chain'));on('crown',()=>preset('crown'));on('resign',()=>{if(!terminal){terminal=(turn==='R'?'White':'Red')+' wins by resignation.';render();}});on('offer',()=>{if(!terminal&&!chain&&lastMover){offer=lastMover;render();}});on('accept',()=>{if(offer&&!chain&&!terminal){terminal='Draw by mutual agreement.';render();}});on('decline',()=>{offer=null;render();});on('reset',()=>preset('full'));preset('full');})();})();
</script></body></html>