Here is a Klondike tableau. The columns each end in a face-up card; everything behind it is face-down — you cannot see it until you uncover it. Click a face-up card to "play" it; the hidden card beneath flips over.
<p class="hint">A Klondike tableau: seven columns, each ending in one <b>face-up</b> card. Everything behind it is <b>face-down</b> — you can’t see it until you uncover it. Click a face-up card to play it and flip the hidden card beneath. You’re choosing moves blind — that’s the whole difficulty.</p>
<div id="board" class="board"></div>
<div class="status" id="status">Click a face-up card to play it.</div>
<div class="btns">
<button id="reveal" type="button">Reveal everything</button>
<button id="deal" type="button" class="ghost">New deal</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.board { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; margin: .4rem 0;
background: #0d6b3f; padding: 10px; border-radius: 10px; }
.col { display: flex; flex-direction: column; gap: 3px; min-height: 120px; }
.card { height: 30px; border-radius: 5px; display: flex; align-items: center;
justify-content: center; font: 700 14px ui-monospace, monospace; user-select: none; }
.down { background: #1d4ed8; background-image: repeating-linear-gradient(45deg,#1d4ed8,#1d4ed8 4px,#2563eb 4px,#2563eb 8px);
border: 1px solid #1e40af; }
.up { background: #fff; border: 1px solid #cbd5e1; }
.up.red { color: #dc2626; }
.up.black { color: #111; }
.up.playable { cursor: pointer; box-shadow: 0 0 0 2px #fde047; }
.up.playable:hover { box-shadow: 0 0 0 2px #facc15; }
.empty { height: 30px; border: 1px dashed rgba(255,255,255,.4); border-radius: 5px; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #0d6b3f;
background: #0d6b3f; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #0d6b3f; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Klondike "hidden information": each column shows only its top (face-up) card.
// The cards behind it are face-down — you choose moves WITHOUT seeing them.
// Deciding the exact fraction of winnable deals under perfect play is a famous OPEN problem (the "solitaire problem"); estimates sit near 80-90%.
const SUITS = [{s:'♠',c:'black'},{s:'♥',c:'red'},{s:'♦',c:'red'},{s:'♣',c:'black'}];
const RANKS = ['A','2','3','4','5','6','7','8','9','10','J','Q','K'];
const COLS = 7;
let cols, revealed;
function freshDeck(){
const d=[];
for(const su of SUITS) for(let r=0;r<RANKS.length;r++) d.push({r, rank:RANKS[r], suit:su.s, color:su.c});
for(let i=d.length-1;i>0;i--){ const j=Math.floor(Math.random()*(i+1)); [d[i],d[j]]=[d[j],d[i]]; }
return d;
}
// Classic Klondike deal: column i gets i+1 cards, only the last is face-up.
function deal(){
const deck=freshDeck(); cols=[]; let k=0;
for(let i=0;i<COLS;i++){
const pile=[];
for(let j=0;j<=i;j++){ const card=deck[k++]; card.up = (j===i); pile.push(card); }
cols.push(pile);
}
revealed=false;
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+(cls||''); }
function render(){
const b=document.getElementById('board'); b.innerHTML='';
for(let i=0;i<COLS;i++){
const colEl=document.createElement('div'); colEl.className='col';
const pile=cols[i];
if(pile.length===0){ const e=document.createElement('div'); e.className='empty'; colEl.appendChild(e); }
pile.forEach((card,idx)=>{
const el=document.createElement('div');
const top = idx===pile.length-1;
if(card.up || revealed){
el.className='card up '+card.color+((card.up&&top)?' playable':'');
el.textContent=card.rank+card.suit;
if(card.up && top){
el.addEventListener('click',()=>play(i));
}
} else {
el.className='card down'; el.textContent='';
}
colEl.appendChild(el);
});
b.appendChild(colEl);
}
}
// "Play" the top face-up card: remove it; flip the card now exposed beneath.
function play(i){
if(revealed) return;
const pile=cols[i]; if(pile.length===0 || !pile[pile.length-1].up) return;
pile.pop();
if(pile.length>0) pile[pile.length-1].up=true;
render();
const flipped = pile.length>0;
setStatus(flipped ? 'A hidden card flipped over — you couldn’t know it in advance.'
: 'Column emptied. Every move reshapes what you’ll uncover next.', '');
}
function reveal(){
revealed=true; render();
const hidden = cols.reduce((n,p)=>n+p.filter(c=>!c.up).length,0);
setStatus(fmt('All %hidden% face-down cards revealed. A perfect player had to plan without seeing any of them.', { hidden: hidden }),'ok');
}
document.getElementById('reveal').addEventListener('click',reveal);
document.getElementById('deal').addEventListener('click',()=>{ deal(); render(); setStatus('New deal. Click a face-up card to play it.',''); });
deal(); render();
<p class="hint">A Klondike tableau: seven columns, each ending in one <b>face-up</b> card. Everything behind it is <b>face-down</b> — you can’t see it until you uncover it. Click a face-up card to play it and flip the hidden card beneath. You’re choosing moves blind — that’s the whole difficulty.</p>
<div id="board" class="board"></div>
<div class="status" id="status">Click a face-up card to play it.</div>
<div class="btns">
<button id="reveal" type="button">Reveal everything</button>
<button id="deal" type="button" class="ghost">New deal</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.board { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; margin: .4rem 0;
background: #0d6b3f; padding: 10px; border-radius: 10px; }
.col { display: flex; flex-direction: column; gap: 3px; min-height: 120px; }
.card { height: 30px; border-radius: 5px; display: flex; align-items: center;
justify-content: center; font: 700 14px ui-monospace, monospace; user-select: none; }
.down { background: #1d4ed8; background-image: repeating-linear-gradient(45deg,#1d4ed8,#1d4ed8 4px,#2563eb 4px,#2563eb 8px);
border: 1px solid #1e40af; }
.up { background: #fff; border: 1px solid #cbd5e1; }
.up.red { color: #dc2626; }
.up.black { color: #111; }
.up.playable { cursor: pointer; box-shadow: 0 0 0 2px #fde047; }
.up.playable:hover { box-shadow: 0 0 0 2px #facc15; }
.empty { height: 30px; border: 1px dashed rgba(255,255,255,.4); border-radius: 5px; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #0d6b3f;
background: #0d6b3f; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #0d6b3f; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Klondike "hidden information": each column shows only its top (face-up) card.
// The cards behind it are face-down — you choose moves WITHOUT seeing them.
// Deciding the exact fraction of winnable deals under perfect play is a famous OPEN problem (the "solitaire problem"); estimates sit near 80-90%.
const SUITS = [{s:'♠',c:'black'},{s:'♥',c:'red'},{s:'♦',c:'red'},{s:'♣',c:'black'}];
const RANKS = ['A','2','3','4','5','6','7','8','9','10','J','Q','K'];
const COLS = 7;
let cols, revealed;
function freshDeck(){
const d=[];
for(const su of SUITS) for(let r=0;r<RANKS.length;r++) d.push({r, rank:RANKS[r], suit:su.s, color:su.c});
for(let i=d.length-1;i>0;i--){ const j=Math.floor(Math.random()*(i+1)); [d[i],d[j]]=[d[j],d[i]]; }
return d;
}
// Classic Klondike deal: column i gets i+1 cards, only the last is face-up.
function deal(){
const deck=freshDeck(); cols=[]; let k=0;
for(let i=0;i<COLS;i++){
const pile=[];
for(let j=0;j<=i;j++){ const card=deck[k++]; card.up = (j===i); pile.push(card); }
cols.push(pile);
}
revealed=false;
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+(cls||''); }
function render(){
const b=document.getElementById('board'); b.innerHTML='';
for(let i=0;i<COLS;i++){
const colEl=document.createElement('div'); colEl.className='col';
const pile=cols[i];
if(pile.length===0){ const e=document.createElement('div'); e.className='empty'; colEl.appendChild(e); }
pile.forEach((card,idx)=>{
const el=document.createElement('div');
const top = idx===pile.length-1;
if(card.up || revealed){
el.className='card up '+card.color+((card.up&&top)?' playable':'');
el.textContent=card.rank+card.suit;
if(card.up && top){
el.addEventListener('click',()=>play(i));
}
} else {
el.className='card down'; el.textContent='';
}
colEl.appendChild(el);
});
b.appendChild(colEl);
}
}
// "Play" the top face-up card: remove it; flip the card now exposed beneath.
function play(i){
if(revealed) return;
const pile=cols[i]; if(pile.length===0 || !pile[pile.length-1].up) return;
pile.pop();
if(pile.length>0) pile[pile.length-1].up=true;
render();
const flipped = pile.length>0;
setStatus(flipped ? 'A hidden card flipped over — you couldn’t know it in advance.'
: 'Column emptied. Every move reshapes what you’ll uncover next.', '');
}
function reveal(){
revealed=true; render();
const hidden = cols.reduce((n,p)=>n+p.filter(c=>!c.up).length,0);
setStatus(fmt('All %hidden% face-down cards revealed. A perfect player had to plan without seeing any of them.', { hidden: hidden }),'ok');
}
document.getElementById('reveal').addEventListener('click',reveal);
document.getElementById('deal').addEventListener('click',()=>{ deal(); render(); setStatus('New deal. Click a face-up card to play it.',''); });
deal(); render();
Notice the trap. Every choice you make changes which hidden cards you'll reach — and you are deciding without knowing what they are. Press Reveal everything to see how a single buried card can have made the deal impossible from the very first move. A perfect player would need to reason about every arrangement consistent with what's hidden, and there are astronomically many.
Comments
Loading comments...