ДУРАК · THE RUSSIAN
КОЗЫРЬ  
36 cards
Наставник · Coach
Ready
Press ИГРАТЬ to deal. Coach and auto-play are on — watch and learn!
ДУРАК!
IS THE FOOL
Иван
Настя
— СТОЛ ПУСТ —
Петька
— ВЫ · YOUR HAND —
Press ИГРАТЬ to begin!
function renderHand(pIdx,el){const p=G.players[pIdx];if(p.type!=='human'){el.innerHTML=backStack(p.isOut?0:p.hand.length);return;}if(p.isOut){el.innerHTML=`
SAFE ✓
`;return;}const adv=COACH?getAdvice():null;const ranks=ranksOnTable();const def=G.players[G.defIdx];let html='';p.hand.forEach((c,i)=>{let valid=false,sel=G.selCard===i;if(G.phase==='attacking'&&G.atkIdx===0){valid=!G.table.length||ranks.has(c.r);if(G.table.length>=def.hand.length)valid=false;}else if(G.phase==='defending'&&G.defIdx===0&&G.selAtk!==null){valid=canBeat(G.table[G.selAtk].a,c,G.trumpSuit);}const coachPick=COACH&&adv&&(adv.action==='attack'||adv.action==='defend')&&adv.cardIdx===i&&!sel;html+=cardHTML(c,{sel,valid,coachPick,click:`onCardClick(${i})`});});el.innerHTML=html;} function renderFelt(){const felt=document.getElementById('felt');const em=document.getElementById('empty-msg');felt.querySelectorAll('.pair').forEach(e=>e.remove());if(!G.table.length){em.style.display='block';return;}em.style.display='none';const adv=COACH?getAdvice():null;G.table.forEach((p,i)=>{const div=document.createElement('div');div.className='pair';let atkClick='',coachAtk=false;if(G.phase==='defending'&&G.defIdx===0&&!p.d&&G.selCard!==null){atkClick=`onclick="selectAtk(${i})"`;coachAtk=COACH&&adv&&adv.action==='defend'&&adv.atkIdx===i;}const sel=G.selAtk===i;div.innerHTML=cardHTML(p.a,{sel,coachAtk,click:atkClick})+(p.d?cardHTML(p.d,{}):'
');felt.appendChild(div);});} function renderDeck(){const n=G.deck.length,l=Math.min(n,3);let h='';for(let i=l-1;i>=0;i--)h+=`
`;document.getElementById('deck-layers').innerHTML=h;document.getElementById('deck-info2').textContent=n?`${n} left`:'empty';document.getElementById('trump-face').innerHTML=G.trumpCard?cardHTML(G.trumpCard,{}):'';} function renderNames(){[{el:'n0',pi:1},{el:'n1',pi:2},{el:'n2',pi:3}].forEach(({el,pi})=>{const e=document.getElementById(el);if(!e)return;const p=G.players[pi];let lb=p.name;if(pi===G.atkIdx&&!p.isOut)lb+=' ⚔';if(pi===G.defIdx&&!p.isOut)lb+=' ⚑';e.textContent=lb;e.className='pname';if(p.isOut)e.classList.add('out');else if(pi===G.atkIdx)e.classList.add('atk');else if(pi===G.defIdx)e.classList.add('def');});} function updateTrumpDisplay(){const ts=document.getElementById('trump-sym');if(G.trumpSuit){ts.textContent=G.trumpSuit;ts.style.color=isRed(G.trumpSuit)?'#E04040':'#D4A520';document.getElementById('trump-name').textContent=SUIT_RU[G.trumpSuit];}document.getElementById('deck-info').textContent=G.deck.length+' in deck';} function renderAll(){renderHand(1,document.getElementById('h0'));renderHand(2,document.getElementById('h1'));renderHand(3,document.getElementById('h2'));renderHand(0,document.getElementById('h3'));renderFelt();renderDeck();renderNames();updateTrumpDisplay();} function msg(txt){if(txt!==undefined){document.getElementById('status').innerHTML=txt;return;}const dk=G.deck.length?` · ${G.deck.length} in deck`:' · DECK EMPTY';if(G.phase==='attacking'){if(G.atkIdx===0)msg(`Your turn — attack ${G.players[G.defIdx].name}!${dk}`);else msg(`${G.players[G.atkIdx].name} attacks ${G.defIdx===0?'YOU':G.players[G.defIdx].name}${dk}`);}else if(G.phase==='defending'){if(G.defIdx===0)msg(`Defend yourself — beat or TAKE!${dk}`);else msg(`${G.players[G.defIdx].name} is defending${dk}`);}} function showBtns(){const bd=document.getElementById('btn-done');const bt=document.getElementById('btn-take');bd.style.display='none';bt.style.display='none';bd.classList.remove('coach-btn-target');bt.classList.remove('coach-btn-target');if(G.phase==='attacking'&&G.atkIdx===0){bd.style.display='inline-block';bd.textContent=G.table.length?'ГОТОВО · DONE':'PASS';bd.onclick=playerDone;if(COACH&&getAdvice().action==='done')bd.classList.add('coach-btn-target');}if(G.phase==='defending'&&G.defIdx===0){bt.style.display='inline-block';if(!unbeaten().length&&G.table.length){bd.style.display='inline-block';bd.textContent='ЗАЩИТА · DEFENSE DONE';bd.onclick=()=>{cancelAuto();roundSuccess();};if(COACH)bd.classList.add('coach-btn-target');}if(COACH&&getAdvice().action==='take')bt.classList.add('coach-btn-target');}} function postHumanTurn(){renderCoach();showBtns();setTimeout(()=>updatePointer(),30);scheduleAutoPlay();} let autoTimer=null; function autoDelay(){return SPEEDS[speedIdx].ms;} function scheduleAutoPlay(){if(!AUTO||!COACH)return;cancelAuto();const human=(G.phase==='attacking'&&G.atkIdx===0)||(G.phase==='defending'&&G.defIdx===0);if(!human)return;const adv=getAdvice();if(!adv||adv.action==='watch')return;startBar(autoDelay(),()=>{if(AUTO&&COACH)executeAutoMove();});} function cancelAuto(){clearTimeout(autoTimer);autoTimer=null;const bar=document.getElementById('auto-progress');if(bar){bar.style.transition='none';bar.style.width='0';}} function startBar(dur,cb){const bar=document.getElementById('auto-progress');if(!bar)return;bar.style.transition='none';bar.style.width='100%';requestAnimationFrame(()=>requestAnimationFrame(()=>{bar.style.transition=`width ${dur}ms linear`;bar.style.width='0%';autoTimer=setTimeout(cb,dur);}));} function executeAutoMove(){if(!AUTO||!COACH)return;const adv=getAdvice();if(!adv)return;const hand=G.players[0].hand;if(adv.action==='attack'){if(adv.cardIdx<0||adv.cardIdx>=hand.length){playerDone();return;}const c=hand.splice(adv.cardIdx,1)[0];G.table.push({a:c,d:null});SoundEngine.cardPlace();renderAll();postHumanTurn();}else if(adv.action==='done'){cancelAuto();if(G.phase==='attacking'&&G.atkIdx===0)playerDone();else if(G.phase==='defending'&&G.defIdx===0)roundSuccess();}else if(adv.action==='take'){cancelAuto();playerTake();}else if(adv.action==='defend'){if(adv.cardIdx<0||adv.atkIdx<0)return;const defCard=hand[adv.cardIdx];const pair=G.table[adv.atkIdx];if(!pair||pair.d)return;pair.d=defCard;hand.splice(adv.cardIdx,1);SoundEngine.defendSuccess();G.selCard=G.selAtk=null;renderAll();postHumanTurn();}} function onCardClick(ci){cancelAuto();SoundEngine.cardPlace();if(G.phase==='attacking'&&G.atkIdx===0){const c=G.players[0].hand[ci];const ranks=ranksOnTable(),def=G.players[G.defIdx];if(G.table.length>0&&!ranks.has(c.r)){msg('Must match rank on table!');postHumanTurn();return;}if(G.table.length>=def.hand.length){msg('Too many attacks!');postHumanTurn();return;}G.players[0].hand.splice(ci,1);G.table.push({a:c,d:null});G.selCard=null;renderAll();postHumanTurn();}else if(G.phase==='defending'&&G.defIdx===0){if(G.selCard===null){G.selCard=ci;renderAll();renderCoach();msg('Click attack card on table to defend.');setTimeout(()=>updatePointer(),30);}else if(G.selCard===ci){G.selCard=G.selAtk=null;renderAll();postHumanTurn();}else if(G.selAtk!==null){tryDefend(ci);}else{G.selCard=ci;renderAll();renderCoach();setTimeout(()=>updatePointer(),30);}}} function selectAtk(i){if(G.phase!=='defending'||G.defIdx!==0)return;cancelAuto();if(G.table[i].d){msg('Already defended!');return;}if(G.selCard!==null){tryDefend(G.selCard,i);return;}G.selAtk=i;renderAll();renderCoach();msg(`Click hand card to beat ${G.table[i].a.r}${G.table[i].a.s}`);setTimeout(()=>updatePointer(),30);} function tryDefend(ci,atkI){const ai=atkI!==undefined?atkI:G.selAtk;if(ai===null)return;const defCard=G.players[0].hand[ci],atk=G.table[ai].a;if(canBeat(atk,defCard,G.trumpSuit)){G.table[ai].d=defCard;G.players[0].hand.splice(ci,1);SoundEngine.defendSuccess();G.selCard=G.selAtk=null;renderAll();postHumanTurn();}else{msg(`${defCard.r}${defCard.s} cannot beat it!`);postHumanTurn();}} function playerDone(){if(G.phase==='attacking'&&G.atkIdx===0){if(!G.table.length){msg('Play at least one card!');postHumanTurn();return;}cancelAuto();startPiling();}} function playerTake(){if(G.phase!=='defending'||G.defIdx!==0)return;SoundEngine.takePile();for(const p of G.table){if(p.a)G.players[0].hand.push(p.a);if(p.d)G.players[0].hand.push(p.d);}G.table=[];G.selCard=G.selAtk=null;cancelAuto();msg('You took the cards!');roundFailed();} function startPiling(){const ranks=ranksOnTable(),def=G.players[G.defIdx];const pilo=[0,1,2,3].filter(i=>i!==G.atkIdx&&i!==G.defIdx&&!G.players[i].isOut&&G.players[i].type==='ai'&&G.players[i].hand.some(c=>ranks.has(c.r))&&G.table.length{aiPile(pi);next();},700);})();}else startDefend();} function aiPile(pi){const ai=G.players[pi],ranks=ranksOnTable(),def=G.players[G.defIdx];const m=ai.hand.filter(c=>ranks.has(c.r));if(m.length&&G.table.lengthrv(a)-rv(b));const c=m[0];ai.hand.splice(ai.hand.indexOf(c),1);G.table.push({a:c,d:null});renderAll();}} function startDefend(){G.phase='defending';G.selCard=G.selAtk=null;renderAll();postHumanTurn();if(G.players[G.defIdx].type==='ai')setTimeout(aiDefend,1000);} function roundSuccess(){cancelAuto();SoundEngine.defendSuccess();for(const p of G.table){if(p.a)G.discard.push(p.a);if(p.d)G.discard.push(p.d);}G.table=[];const oldDef=G.defIdx;drawCards(true,()=>{G.atkIdx=oldDef;G.defIdx=nextActive(G.atkIdx);nextTurn();});} function roundFailed(){cancelAuto();drawCards(false,()=>{const skip=G.defIdx;G.atkIdx=nextActive(skip);G.defIdx=nextActive(G.atkIdx);nextTurn();});} function drawCards(success,cb){G.phase='drawing';let order=success?[G.atkIdx,G.defIdx]:[G.atkIdx];[0,1,2,3].forEach(i=>{if(!order.includes(i)&&!G.players[i].isOut)order.push(i);});const seen=new Set();for(const pi of order){if(seen.has(pi))continue;seen.add(pi);const p=G.players[pi];while(p.hand.length<6&&G.deck.length>0)p.hand.push(G.deck.pop());if(p.hand.length<6&&G.trumpCard){p.hand.push(G.trumpCard);G.trumpCard=null;}}for(let i=0;i<4;i++){if(!G.players[i].isOut&&!G.players[i].hand.length&&!G.deck.length&&!G.trumpCard)G.players[i].isOut=true;}renderAll();checkEnd(cb);} function checkEnd(cb){const act=actives();if(act.length<=1){G.phase='gameover';G.durak=act.length===1?act[0]:-1;renderAll();showGameOver();return;}if(cb)cb();} function nextTurn(){while(G.players[G.atkIdx].isOut)G.atkIdx=nextActive(G.atkIdx);let t=0;while((G.players[G.defIdx].isOut||G.defIdx===G.atkIdx)&&t++<4)G.defIdx=nextActive(G.atkIdx);G.table=[];G.selCard=G.selAtk=null;G.phase='attacking';renderAll();postHumanTurn();if(G.players[G.atkIdx].type==='ai')setTimeout(aiAttack,1200);} function aiAttack(){if(G.phase!=='attacking'||G.players[G.atkIdx].type!=='ai')return;const ai=G.players[G.atkIdx],def=G.players[G.defIdx],ranks=ranksOnTable(),style=ai.style;let c=null;if(!G.table.length){let cd=ai.hand.filter(x=>x.s!==G.trumpSuit);if(!cd.length)cd=[...ai.hand];cd.sort((a,b)=>rv(a)-rv(b));c=cd[0];}else{const m=ai.hand.filter(x=>ranks.has(x.r));if(m.length&&G.table.lengthrv(a)-rv(b));if(style==='aggressive'||(style==='balanced'&&rv(m[0])<=5))c=m[0];}}if(c){ai.hand.splice(ai.hand.indexOf(c),1);G.table.push({a:c,d:null});renderAll();setTimeout(()=>{if(G.phase!=='attacking')return;const ranks2=ranksOnTable(),def2=G.players[G.defIdx];const m2=ai.hand.filter(x=>ranks2.has(x.r));if(m2.length&&G.table.lengthcanBeat(p.a,c,G.trumpSuit));valid.sort((a,b)=>{const at=a.s===G.trumpSuit?1:0,bt=b.s===G.trumpSuit?1:0;if(at!==bt)return at-bt;return rv(a)-rv(b);});if(!valid.length){ok=false;break;}plan.push({pair:p,card:valid[0]});tmp.splice(tmp.indexOf(valid[0]),1);}if(!ok){msg(`${ai.name} takes!`);for(const p of G.table){if(p.a)ai.hand.push(p.a);if(p.d)ai.hand.push(p.d);}G.table=[];renderAll();setTimeout(roundFailed,600);return;}let dl=0;for(const{pair,card}of plan){dl+=750;setTimeout(()=>{pair.d=card;const hi=ai.hand.indexOf(card);if(hi>-1)ai.hand.splice(hi,1);renderAll();},dl);}setTimeout(()=>{roundSuccess();},dl+600);} function showGameOver(){cancelAuto();SoundEngine.pauseBackground();document.getElementById('coach-ptr').classList.remove('show');document.getElementById('gameover').classList.add('show');if(G.durak===-1){document.getElementById('go-title').textContent='НИЧЬЯ!';document.getElementById('go-name').textContent='No fool this round!';document.getElementById('go-sub').textContent='';}else{document.getElementById('go-title').textContent='ДУРАК!';document.getElementById('go-name').textContent=G.durak===0?'YOU ARE THE FOOL!':G.players[G.durak].name+' is the Fool!';document.getElementById('go-sub').textContent=G.durak===0?'Shuffle the deck with your teeth!':'Lucky escape...';if(G.durak===0)SoundEngine.foolSting();else SoundEngine.victory();}const safe=actives().filter(i=>i!==G.durak).map(i=>i===0?'YOU':G.players[i].name);document.getElementById('surv').textContent=safe.length?'Safe: '+safe.join(', '):'';} function toggleCoach(){SoundEngine.btnClick();COACH=!COACH;cancelAuto();document.getElementById('coach-bar').classList.toggle('off',!COACH);const btn=document.getElementById('btn-coach');btn.textContent=COACH?'COACH ON':'COACH OFF';btn.className=COACH?'ctrl-btn on-green':'ctrl-btn';if(!COACH){AUTO=false;updateAutoBtn();document.getElementById('coach-ptr').classList.remove('show');}else{renderAll();renderCoach();showBtns();setTimeout(()=>updatePointer(),30);}} function toggleAuto(){SoundEngine.btnClick();AUTO=!AUTO;cancelAuto();updateAutoBtn();if(AUTO&&COACH)scheduleAutoPlay();} function cycleSpeed(){SoundEngine.btnClick();speedIdx=(speedIdx+1)%3;document.getElementById('btn-speed').textContent=SPEEDS[speedIdx].label;if(AUTO&&COACH){cancelAuto();scheduleAutoPlay();}} function updateAutoBtn(){const b=document.getElementById('btn-auto');b.textContent=AUTO?'AUTO ON':'AUTO OFF';b.className=AUTO?'ctrl-btn on-gold':'ctrl-btn';} function toggleMute(){const muted=SoundEngine.toggleMute();document.getElementById('btn-mute').textContent=muted?'SOUND OFF':'SOUND ON';} initG(); // Unlock audio + video on first interaction const _bgv=document.getElementById('bgVideo'); if(_bgv)_bgv.play().catch(()=>{}); document.addEventListener('click',()=>{ if(_bgv&&_bgv.paused)_bgv.play().catch(()=>{}); if(SoundEngine.audioCtx&&SoundEngine.audioCtx.state==='suspended')SoundEngine.audioCtx.resume(); },{passive:true});