하이로우 POT 0
로그
52
100
10
POT0
🧑
0
다이
따당
쿼터
하프
🃏 하이로우 포커
7포커 · 하이 / 로우 / 스윙
시작 방식
4장
1장 버림
3장
버림 없음
AI 플레이어
6
+
/ 7명
블라인드
500원
1천원
5천원
1만원
10만원
시작 금액
백만원
천만원
1억
10억
100억
블라인드 500원 · 시작 천만원
🃏 버릴 카드를 선택하세요!
1장 선택 후 확인
🎯 하이 / 로우 / 스윙 선택
하이높은 패
0
로우낮은 패
0
스윙양쪽 다 이겨야
0
★ 게임결과 ★
이름족보선언수익
// ══════════════════════════════════════ // 뷰포트 // ══════════════════════════════════════ (function(){ function setSize(){ const w=window.visualViewport?window.visualViewport.width:window.innerWidth; const h=window.visualViewport?window.visualViewport.height:window.innerHeight; document.documentElement.style.setProperty('--real-w',w+'px'); document.documentElement.style.setProperty('--real-h',h+'px'); const root=document.getElementById('root');if(!root)return; const isP=window.innerHeight>window.innerWidth; if(isP){root.style.width=h+'px';root.style.height=w+'px';root.style.left=w+'px'} else{root.style.width=w+'px';root.style.height=h+'px';root.style.left='0'} } window.addEventListener('load',()=>{setTimeout(()=>window.scrollTo(0,1),300);setSize()}); window.addEventListener('resize',setSize); window.addEventListener('orientationchange',()=>setTimeout(setSize,300)); if(window.visualViewport){window.visualViewport.addEventListener('resize',setSize);window.visualViewport.addEventListener('scroll',setSize)} document.addEventListener('DOMContentLoaded',setSize); })(); // ══════════════════════════════════════ // 레이아웃 — 상대 플레이어 슬롯 // ══════════════════════════════════════ // 시계방향 8자리: tl tm tr mr br bm bl ml const SEAT_POSITIONS=['tl','tm','tr','mr','bl','bm','br','ml']; // CSS 클래스 매핑 const SEAT_CLASS={tl:'opp-tl',tm:'opp-tm',tr:'opp-tr',mr:'opp-mr',bl:'opp-bl',bm:'opp-bm',br:'opp-br',ml:'opp-ml'}; // 동적 위치 CSS (JS로 주입) function injectSeatCSS(){ const s=document.createElement('style'); s.textContent=` .opp-tl{position:absolute;top:6px;left:6px} .opp-tm{position:absolute;top:6px;left:50%;transform:translateX(-50%)} .opp-tr{position:absolute;top:6px;right:6px} .opp-mr{position:absolute;top:50%;right:6px;transform:translateY(-50%)} .opp-br{position:absolute;bottom:58px;right:6px} .opp-bm{position:absolute;bottom:58px;left:50%;transform:translateX(-50%)} .opp-bl{position:absolute;bottom:58px;left:6px} .opp-ml{position:absolute;top:50%;left:6px;transform:translateY(-50%)} `; document.head.appendChild(s); } function buildLayout(){ injectSeatCSS(); const total=G.players.length; // AI 플레이어 최대 7명 → 7자리 const seats=['tl','tm','tr','mr','br','bl','ml']; const sm={}; const container=document.getElementById('opp-slots'); container.innerHTML=''; for(let i=1;i{ container.appendChild(makeOppZone(p,seat)); }); // 슬롯 초기화는 약간 뒤에 setTimeout(initCardSlots,0); } function makeOppZone(p,seat){ const div=document.createElement('div'); div.className=`opp-zone ${SEAT_CLASS[seat]||'opp-tl'}`; div.id=`opp-zone-${p.id}`; div.innerHTML=`
${p.emoji}
${p.name}
${fmt(p.chips)}
`; return div; } // ══════════════════════════════════════ // 카드 슬롯 초기화 // ══════════════════════════════════════ function initCardSlots(){ // 내 카드 const myEl=document.getElementById('my-cards'); myEl.innerHTML=''; for(let i=0;i<7;i++){ const ph=document.createElement('div'); ph.className='my-slot-ph';ph.id=`my-slot-${i}`; myEl.appendChild(ph); } document.getElementById('my-hand-label').textContent=''; // AI 슬롯 G.players.filter(p=>p.id!==0).forEach(p=>{ const el=document.getElementById(`pcards-${p.id}`); if(!el)return; el.innerHTML=''; for(let i=0;i<7;i++){ const ph=document.createElement('div'); ph.className='slot-ph';ph.id=`slot-${p.id}-${i}`; el.appendChild(ph); } }); } function resetCardSlots(){ const myEl=document.getElementById('my-cards'); myEl.innerHTML=''; for(let i=0;i<7;i++){ const ph=document.createElement('div');ph.className='my-slot-ph';ph.id=`my-slot-${i}`;myEl.appendChild(ph); } document.getElementById('my-hand-label').textContent=''; document.getElementById('info-panel').style.display='none'; G.players.filter(p=>p.id!==0).forEach(p=>{ const el=document.getElementById(`pcards-${p.id}`);if(!el)return; el.innerHTML=''; const inner=document.getElementById(`opp-inner-${p.id}`); if(inner){inner.classList.remove('boss-hi','folded-dim');} for(let i=0;i<7;i++){ const ph=document.createElement('div');ph.className='slot-ph';ph.id=`slot-${p.id}-${i}`;el.appendChild(ph); } const hel=document.getElementById(`phand-${p.id}`);if(hel)hel.textContent=''; const bel=document.getElementById(`pbet-${p.id}`);if(bel)bel.innerHTML=''; }); } // ══════════════════════════════════════ // 카드 렌더링 (슬롯 채우기) // ══════════════════════════════════════ const isRed=s=>s==='♥'||s==='♦'; function cardHTML(c,cls,back){ if(!c||back)return`
`; const col=isRed(c.suit)?'red':'blk'; if(cls==='tc'){ return`
${c.rank}
${c.suit}
`; } return`
${c.rank}
${c.suit}
`; } function renderPlayerCards(p){ if(p.id===0){ for(let i=0;i<7;i++){ const slotEl=document.getElementById(`my-slot-${i}`);if(!slotEl)continue; const c=p.cards[i]; if(!c){if(slotEl.classList.contains('my-slot-ph'))continue;const ph=document.createElement('div');ph.className='my-slot-ph';ph.id=`my-slot-${i}`;slotEl.replaceWith(ph);continue;} if(slotEl.classList.contains('card'))continue; const isHid=p.hidden.includes(c); const d=document.createElement('div');d.innerHTML=cardHTML(c,'myc',false); const card=d.firstChild;if(isHid)card.classList.add('hidden-mine'); card.classList.add('new-card');card.id=`my-slot-${i}`;slotEl.replaceWith(card); } if(p.highHand){ document.getElementById('my-hand-label').textContent=`하이:${hiName(p.highHand)} / 로우:${loName(p.lowHand)}`; } updateInfoPanel(p);return; } const el=document.getElementById(`pcards-${p.id}`);if(!el)return; const inner=document.getElementById(`opp-inner-${p.id}`); if(p.folded){if(inner)inner.classList.add('folded-dim');return;} if(inner)inner.classList.remove('folded-dim'); for(let i=0;i<7;i++){ const slotEl=document.getElementById(`slot-${p.id}-${i}`);if(!slotEl)continue; const c=p.cards[i]; if(!c)continue; if(slotEl.classList.contains('card')||slotEl.classList.contains('pmc'))continue; const isOpen=p.open.includes(c); let newEl; if(isOpen){const d=document.createElement('div');d.innerHTML=cardHTML(c,'tc',false);newEl=d.firstChild;} else{newEl=document.createElement('div');newEl.className='pmc';} newEl.classList.add('new-card');newEl.id=`slot-${p.id}-${i}`;slotEl.replaceWith(newEl); } const hel=document.getElementById(`phand-${p.id}`);if(hel&&p.highHand)hel.textContent=hiName(p.highHand); } // ══════════════════════════════════════ // HUD // ══════════════════════════════════════ function updateHUD(){ const p0=G.players[0]; document.getElementById('my-chips').textContent=fmt(p0.chips); document.getElementById('hd-pot').textContent=fmt(G.pot); document.getElementById('felt-pot').textContent=fmt(G.pot); document.getElementById('hd-stage').textContent={deal:'딜',discard:'버리기',bet4:'4번째',bet5:'5번째',bet6:'6번째',bet7:'최종베팅',declare:'선언',result:'결과'}[G.stage]||G.stage; if(G.pot>0)document.getElementById('chip-stack').style.display='flex'; G.players.forEach(p=>{ if(p.id===0)return; const cel=document.getElementById(`opp-chips-${p.id}`);if(cel)cel.textContent=fmt(p.chips); const bel=document.getElementById(`pbet-${p.id}`);if(!bel)return; bel.innerHTML=''; if(p.bet>0)bel.textContent=fmt(p.bet); if(p.chips===0&&!p.folded)bel.innerHTML+=' ALL-IN'; if(p.decl){ const badge={high:'hi',low:'lo',swing:'sw'}[p.decl]; const lbl={high:'▲하이',low:'▼로우',swing:'↕스윙'}[p.decl]; bel.innerHTML+=` ${lbl}`; } // 아바타 보스 강조 const ava=document.getElementById(`opp-ava-${p.id}`); if(ava)ava.classList.toggle('boss',p.id===G.bossId); const inner=document.getElementById(`opp-inner-${p.id}`); if(inner)inner.classList.toggle('boss-hi',p.id===G.bossId); }); // 내 아바타 document.getElementById('my-ava').classList.toggle('boss',G.bossId===0); updateBetAmounts(); } function updateInfoPanel(p){ if(!p.highHand){document.getElementById('info-panel').style.display='none';return} document.getElementById('info-panel').style.display=''; document.getElementById('ip-hi-name').textContent='하이: '+hiName(p.highHand); document.getElementById('ip-lo-name').textContent='로우: '+loName(p.lowHand); const hiPct=Math.min(100,Math.round((p.highHand.tier/9)*100+10)); const loPct=p.lowHand&&!p.lowHand.penalty?Math.round(Math.max(8,100-(p.lowHand.top-1)*12)):5; document.getElementById('ip-hi-pct').textContent=hiPct+'%'; document.getElementById('ip-lo-pct').textContent=loPct+'%'; document.getElementById('ip-hi-bar').style.width=hiPct+'%'; document.getElementById('ip-lo-bar').style.width=loPct+'%'; const tiers=[{n:'로티플',v:9},{n:'스트플',v:8},{n:'포카드',v:7},{n:'풀하우스',v:6},{n:'플러시',v:5},{n:'스트레이트',v:4},{n:'트리플',v:3},{n:'투페어',v:2},{n:'원페어',v:1},{n:'하이카드',v:0}]; const cur=p.highHand.tier; document.getElementById('ip-hand-rows').innerHTML=tiers.slice(0,6).map(t=>`
${t.n}
`).join(''); } // ══════════════════════════════════════ // 위치 계산 // ══════════════════════════════════════ function getSlotRect(playerId){ if(playerId===0){const el=document.getElementById('my-zone');return el?el.getBoundingClientRect():null} const el=document.getElementById(`opp-zone-${playerId}`); return el?el.getBoundingClientRect():null; } function getSlotCenter(playerId){const r=getSlotRect(playerId);if(!r)return null;return{x:r.left+r.width/2,y:r.top+r.height/2}} function getPotCenter(){const el=document.getElementById('felt-pot');if(!el)return{x:window.innerWidth/2,y:window.innerHeight/2};const r=el.getBoundingClientRect();return{x:r.left+r.width/2,y:r.top+r.height/2}} // ══════════════════════════════════════ // 베팅 금액 미리보기 // ══════════════════════════════════════ function updateBetAmounts(){ const p=G.players[0];if(!p)return; const pot=G.pot,call=G.currentBet,chips=p.chips; const amts={'amt-call':Math.min(call,chips),'amt-bbang':Math.min(call*2||G.blind,chips),'amt-ttadang':Math.min(call*3||G.blind*3,chips),'amt-qtr':Math.min(Math.floor(pot/4),chips),'amt-half':Math.min(Math.floor(pot/2),chips)}; Object.entries(amts).forEach(([id,v])=>{const el=document.getElementById(id);if(el)el.textContent=v>0?fmt(v):''}); } // ══════════════════════════════════════ // 보스 마커 // ══════════════════════════════════════ let _bossMarkerEl=null; function updateBossMarker(){ const bossId=G.bossId; if(!_bossMarkerEl){ _bossMarkerEl=document.createElement('div'); _bossMarkerEl.className='boss-marker'; document.body.appendChild(_bossMarkerEl); } _bossMarkerEl.style.display='flex'; let targetEl=null; if(bossId===0)targetEl=document.getElementById('my-ava'); else targetEl=document.getElementById(`opp-ava-${bossId}`); if(targetEl){const r=targetEl.getBoundingClientRect();_bossMarkerEl.style.left=(r.right-12)+'px';_bossMarkerEl.style.top=(r.top-4)+'px'} } // ══════════════════════════════════════ // 베팅 팝업 / 선언 팝업 / 칩 이동 // ══════════════════════════════════════ function showBetPopup(playerId,text,type){ const pos=getSlotCenter(playerId);if(!pos)return; const el=document.createElement('div');el.className='slot-popup bet'; el.innerHTML=`
${text}
`; el.style.left=pos.x+'px';el.style.top=(pos.y-4)+'px'; document.body.appendChild(el);setTimeout(()=>el.remove(),1800); } function showDeclPopup(playerId,decl){ const pos=getSlotCenter(playerId);if(!pos)return; const map={high:`
하이
`,low:`
로우
`,swing:`
스윙
`}; const el=document.createElement('div');el.className='slot-popup decl';el.innerHTML=map[decl]||''; el.style.left=pos.x+'px';el.style.top=(pos.y+2)+'px'; document.body.appendChild(el);setTimeout(()=>el.remove(),2500); } function flyChipToPot(playerId,amount){ const from=getSlotCenter(playerId);const to=getPotCenter();if(!from||!to)return; const count=Math.min(Math.max(1,Math.ceil(Math.log2(amount/cfg.blind+1))),5); for(let i=0;i{ const chip=document.createElement('div'); chip.className='flying-chip '+(amount>=cfg.blind*20?'red':amount>=cfg.blind*5?'blue':'gold'); const ox=(Math.random()-.5)*14,oy=(Math.random()-.5)*8; chip.style.left=(from.x+ox-7)+'px';chip.style.top=(from.y+oy-7)+'px'; document.body.appendChild(chip); requestAnimationFrame(()=>requestAnimationFrame(()=>{ const dur=420+Math.random()*160;const start=performance.now(); function frame(now){ const t=Math.min((now-start)/dur,1);const mt=t<.5?2*t*t:1-Math.pow(-2*t+2,2)/2; chip.style.left=(from.x+ox+(to.x-from.x-ox)*mt-7)+'px'; const yLine=from.y+oy+(to.y-from.y-oy)*mt-7; chip.style.top=(yLine-(1-Math.pow(mt*2-1,2))*44)+'px'; chip.style.opacity=t>0.75?(1-(t-0.75)*4).toString():'1'; chip.style.transform=`scale(${1-t*0.5})`; if(t<1)requestAnimationFrame(frame);else chip.remove(); } chip.style.transition='none';requestAnimationFrame(frame); })); },i*80); } } // ══════════════════════════════════════ // 덱 카드 딜 애니메이션 // ══════════════════════════════════════ function updateDeckCount(){const el=document.getElementById('deck-count');if(el)el.textContent=G.deck?G.deck.length:'52'} function dealCardAnim(toPlayerId,delay,onLand){ setTimeout(()=>{ const deckEl=document.getElementById('deck-pile'); const toRect=getSlotRect(toPlayerId); if(!deckEl||!toRect){onLand&&onLand();return} const fr=deckEl.getBoundingClientRect(); const fromX=fr.left+fr.width/2-10,fromY=fr.top+fr.height/2-14; const toX=toRect.left+toRect.width/2-10,toY=toRect.top+toRect.height/2-14; const card=document.createElement('div');card.className='dealing-card'; card.style.left=fromX+'px';card.style.top=fromY+'px'; document.body.appendChild(card); requestAnimationFrame(()=>requestAnimationFrame(()=>{ const dur=300+Math.random()*80;const start=performance.now(); function frame(now){ const t=Math.min((now-start)/dur,1);const mt=t<.5?2*t*t:1-Math.pow(-2*t+2,2)/2; card.style.left=(fromX+(toX-fromX)*mt)+'px'; card.style.top=(fromY+(toY-fromY)*mt-(1-Math.pow(mt*2-1,2))*28)+'px'; card.style.opacity=t>0.85?(1-(t-0.85)/0.15).toString():'1'; card.style.transform=`rotate(${mt*12}deg) scale(${1-mt*0.2})`; if(t<1)requestAnimationFrame(frame);else{card.remove();onLand&&onLand();} } requestAnimationFrame(frame); })); },delay); } function runDealAnimation(players,cardsPerPlayer,onDone){ const GAP=85;let t=0,done=0,total=players.length*cardsPerPlayer; for(let round=0;round{ dealCardAnim(p.id,t,()=>{done++;updateDeckCount();if(done>=total)setTimeout(onDone,150);}); t+=GAP; }); } } function runSingleDealAnim(players,onDone){ const GAP=110;let done=0; players.forEach((p,i)=>{dealCardAnim(p.id,i*GAP,()=>{done++;updateDeckCount();if(done>=players.length)setTimeout(onDone,150);});}); } // ══════════════════════════════════════ // 게임 시작 // ══════════════════════════════════════ function startGame(){ hide('ov-start'); G={deck:shuffle(createDeck()),pot:cfg.blind*(cfg.aiNum+1),stage:'deal',blind:cfg.blind,players:[],currentBet:cfg.blind,round:1,logs:[],seatMap:{}}; const emojis=['🧑','🦁','🐸','🦊','🎭','🤖','👱','🧔']; const names=['나','AI 고래','AI 개구리','AI 여우','AI 탈','AI 로봇','AI 금발','AI 수염']; for(let i=0;i{p.cards=[];p.open=[];p.hidden=[];p.folded=false;p.decl=null;p.highHand=null;p.lowHand=null;p.bet=cfg.blind}); G.players.forEach(p=>{for(let i=0;ievalHands(p)); G.bossId=calcBoss(); addLog(`보스: ${G.players.find(p=>p.id===G.bossId)?.name||'?'}`); document.getElementById('decl-count-row').style.display='none'; document.getElementById('call-box').style.display='none'; document.getElementById('bet-call-info').style.display='none'; document.getElementById('bet-pot-info').style.display='none'; updateDeckCount(); resetCardSlots(); const dealPlayers=getBossOrder(); runDealAnimation(dealPlayers,cfg.mode,()=>{ renderAll();updateHUD();updateBossMarker(); if(cfg.mode===4){G.players.filter(p=>p.isAI&&!p.folded).forEach(p=>aiDiscard(p));showDiscardUI();} else startBetRound('bet4'); }); } function checkLastPlayerWins(){ const alive=G.players.filter(p=>!p.folded); if(alive.length===1){ const winner=alive[0];winner.chips+=G.pot; addLog(`${winner.name}: 단독 생존 (${fmt(G.pot)})`); updateHUD();renderAll(); const gains=G.players.map(p=>({id:p.id,gain:p.id===winner.id?G.pot:0})); G.stage='result'; setTimeout(()=>showResultOverlay(gains,alive),400); return true; } return false; } function aiDiscard(p){const w=p.cards.reduce((a,c)=>rv(c)c!==w);p.hidden=[p.cards[0],p.cards[1]];p.open=[p.cards[2]];evalHands(p)} function showDiscardUI(){ const p=G.players[0]; document.getElementById('discard-cards').innerHTML=p.cards.map((c,i)=>`
${cardHTML(c,'dsc',false)}
`).join(''); show('ov-discard'); } function toggleDiscard(el,idx){document.querySelectorAll('#discard-cards .ov-wrap.sel').forEach(e=>e.classList.remove('sel'));el.classList.add('sel');G._discardIdx=idx} function hideDiscard(){hide('ov-discard')} function confirmDiscard(){ if(G._discardIdx===undefined){alert('버릴 카드를 선택하세요');return} const p=G.players[0];p.cards.splice(G._discardIdx,1);p.hidden=[p.cards[0],p.cards[1]];p.open=[p.cards[2]];evalHands(p);G._discardIdx=undefined; hideDiscard();renderAll();setTimeout(()=>startBetRound('bet4'),400); } // ══════════════════════════════════════ // 베팅 라운드 // ══════════════════════════════════════ const stageSeq=['bet4','bet5','bet6','bet7','declare']; function startBetRound(stage){ G.stage=stage; const proceed=()=>{ renderAll();updateHUD();updateBossMarker(); document.getElementById('round-badge').textContent=`${G.round}라운드 · 보스: ${G.players.find(p=>p.id===G.bossId)?.name||'?'}`; if(stage==='declare'){doDeclaration();return} const betOrder=getBossOrder().filter(p=>p.isAI); let delay=0; betOrder.forEach(p=>{ setTimeout(()=>{ if(p.folded)return; const score=p.highHand?p.highHand.tier:0,r=Math.random(); if(G.currentBet===0){ if(score>=5&&r<0.5){const pay=Math.min(Math.floor(G.pot*0.5),p.chips);p.chips-=pay;p.bet+=pay;G.pot+=pay;G.currentBet=pay;showBetPopup(p.id,`빵! ${fmt(pay)}`,'bbang');flyChipToPot(p.id,pay);addLog(`${p.name}: 빵`)} } else { if(score>=4||r<0.5){const pay=Math.min(G.currentBet,p.chips);p.chips-=pay;p.bet+=pay;G.pot+=pay;showBetPopup(p.id,'콜','call');flyChipToPot(p.id,pay);addLog(`${p.name}: 콜`)} else{p.folded=true;showBetPopup(p.id,'다이','die');addLog(`${p.name}: 다이`);G.bossId=calcBoss();updateBossMarker();if(checkLastPlayerWins())return;} } updateHUD();renderAll(); },delay); delay+=380; }); setTimeout(()=>{ updateHUD();renderAll();G.bossId=calcBoss();updateBossMarker(); if(G.currentBet>0){ document.getElementById('bet-call-info').style.display=''; document.getElementById('bet-pot-info').style.display=''; document.getElementById('call-amt').textContent=fmt(G.currentBet); document.getElementById('max-amt').textContent=fmt(G.pot); document.getElementById('call-box').style.display='flex'; document.getElementById('felt-call').textContent=fmt(G.currentBet); } updateBetAmounts();enableBtns(true);showTurn(true); },delay+200); }; if(stage!=='bet4'){ const dealOrder=getBossOrder(); dealOrder.forEach(p=>{const c=G.deck.pop();p.cards.push(c);if(stage==='bet7')p.hidden.push(c);else p.open.push(c);evalHands(p);}); G.bossId=calcBoss();updateDeckCount(); runSingleDealAnim(dealOrder,proceed); } else proceed(); } function playerAct(action){ enableBtns(false);showTurn(false); const p=G.players[0];let pay=0,type=action,label=''; if(action==='die'){ p.folded=true;label='다이';type='die';G.bossId=calcBoss();updateBossMarker(); addLog('나: 다이');showBetPopup(0,'다이','die');updateHUD();renderAll(); if(checkLastPlayerWins())return; const idx=stageSeq.indexOf(G.stage);setTimeout(()=>startBetRound(idx0)flyChipToPot(0,pay); updateHUD();renderAll(); const idx=stageSeq.indexOf(G.stage); setTimeout(()=>startBetRound(idxp.isAI&&!p.folded).forEach(p=>{ if(isAutoSwing(p.highHand))p.decl='swing'; else{const hs=p.highHand?p.highHand.tier:0,ls=p.lowHand&&!p.lowHand.penalty?(14-p.lowHand.top):0;p.decl=hs>=6&&ls>=6?'swing':hs>=ls?'high':'low'} if(p.decl==='high')cHi++;else if(p.decl==='low')cLo++;else cSw++; }); document.getElementById('cnt-hi').textContent=cHi; document.getElementById('cnt-lo').textContent=cLo; document.getElementById('cnt-sw').textContent=cSw; const p0=G.players[0]; if(p0.folded){startRevealSequence();return} if(isAutoSwing(p0.highHand)){p0.decl='swing';cSw++;document.getElementById('cnt-sw').textContent=cSw;setTimeout(startRevealSequence,600);return} document.getElementById('decl-hi-sub').textContent=hiName(p0.highHand); document.getElementById('decl-lo-sub').textContent=loName(p0.lowHand); document.getElementById('decl-hand-info').innerHTML=`하이: ${hiName(p0.highHand)}  |  로우: ${loName(p0.lowHand)}`; document.getElementById('decl-hi-cnt').textContent=cHi; document.getElementById('decl-lo-cnt').textContent=cLo; document.getElementById('decl-sw-cnt').textContent=cSw; document.getElementById('decl-count-row').style.display='none'; show('ov-declare'); } function playerDeclare(d){ hide('ov-declare');G.players[0].decl=d;addLog('나: '+d+' 선언'); document.getElementById('decl-count-row').style.display='flex'; const k={high:'cnt-hi',low:'cnt-lo',swing:'cnt-sw'}[d]; if(k){const el=document.getElementById(k);if(el)el.textContent=parseInt(el.textContent)+1} setTimeout(startRevealSequence,400); } // ══════════════════════════════════════ // 리벌 시퀀스 // ══════════════════════════════════════ function showDeclRevealPopup(playerId,decl){ const pos=getSlotCenter(playerId);if(!pos)return; const arrows={high:'▲',low:'▼',swing:'⬆⬇'};const labels={high:'하이',low:'로우',swing:'스윙'};const cls={high:'hi',low:'lo',swing:'sw'}; const el=document.createElement('div');el.className='decl-reveal-popup'; el.innerHTML=`
${arrows[decl]||'?'}
${labels[decl]||decl}
`; el.style.left=pos.x+'px';el.style.top=(pos.y+4)+'px'; document.body.appendChild(el);setTimeout(()=>el.remove(),4200); } function getHandPopupDur(tier){return[2.0,2.1,2.3,2.5,2.7,3.0,3.2,3.6,4.0,4.4][tier]||2.0} function showHandPopup(playerId,html,tier,isLo){ const pos=getSlotCenter(playerId);if(!pos)return; const dur=isLo?2.5+(tier||0)*0.2:getHandPopupDur(tier||0); const el=document.createElement('div');el.className='hand-popup';el.style.setProperty('--dur',dur+'s'); function getLoCls(s){return s<=1?'best':s<=2?'strong':s<=4?'mid':'weak'} el.innerHTML=`
${html}
`; el.style.left=pos.x+'px';el.style.top=(pos.y-8)+'px'; document.body.appendChild(el);setTimeout(()=>el.remove(),(dur+0.2)*1000); } function getLoPopupInfo(lowHand){ if(!lowHand)return{text:'—',score:5}; if(lowHand.penalty)return{text:`${lowHand.top}탑(패)`,score:5}; const top=lowHand.top===14?1:lowHand.top; const cards=[...lowHand.cards].sort((a,b)=>{const av=rv(a)===14?1:rv(a),bv=rv(b)===14?1:rv(b);return av-bv}); const cardStr=cards.map(c=>rv(c)===14?1:rv(c)).join(' '); let score=top<=6?0:top<=7?1:top<=9?2:top<=11?3:4; return{text:`${top===1?'A':top}탑 ${cardStr}`,score}; } function flipHiddenCards(player,onDone){ if(player.id===0){ const cardsEl=document.getElementById('my-cards'); const cardEls=cardsEl.querySelectorAll('.card.hidden-mine');let i=0; function flipNext(){if(i>=cardEls.length){setTimeout(onDone,300);return}const c=cardEls[i];c.classList.remove('hidden-mine');c.style.animation='none';void c.offsetHeight;c.classList.add('flip-reveal');i++;setTimeout(flipNext,700)} flipNext();return; } const el=document.getElementById(`pcards-${player.id}`); if(!el){onDone();return} const pmcs=el.querySelectorAll('.pmc');const hiddens=player.hidden;let i=0; function flipNext(){ if(i>=pmcs.length||i>=hiddens.length){setTimeout(onDone,300);return} const pmc=pmcs[i];const c=hiddens[i]; const d=document.createElement('div');d.innerHTML=cardHTML(c,'tc',false); const newCard=d.firstChild;newCard.style.animation='none';pmc.replaceWith(newCard); void newCard.offsetHeight;newCard.classList.add('flip-reveal');i++;setTimeout(flipNext,700); } flipNext(); } const SPECIAL_CONFIGS={9:{title:'🃏 로얄 스트레이트 플러시',sub:'AUTO SWING',color:'#ffdd0033',pcolor:'#ffdd00',text:'#ffe566',glow:'#ffdd00',subColor:'#ffcc55',dur:4500},8:{title:'🔥 스트레이트 플러시',sub:'AUTO SWING',color:'#ff008833',pcolor:'#ff66cc',text:'#ff88dd',glow:'#ff0088',subColor:'#ff88cc',dur:3800},7:{title:'💥 포 카드',sub:'AUTO SWING',color:'#ff440022',pcolor:'#ff8844',text:'#ff9955',glow:'#ff4400',subColor:'#ffaa66',dur:3400}}; function showSpecialSplash(tier,playerName,onDone){ const cfg2=SPECIAL_CONFIGS[tier];if(!cfg2){onDone();return} const splash=document.getElementById('special-splash'); document.getElementById('sp-title').textContent=cfg2.title;document.getElementById('sp-title').style.color=cfg2.text;document.getElementById('sp-title').style.textShadow=`0 0 22px ${cfg2.glow},0 0 44px ${cfg2.glow}`; document.getElementById('sp-sub').textContent=cfg2.sub;document.getElementById('sp-sub').style.color=cfg2.subColor; document.getElementById('sp-name').textContent=playerName; const pc=document.getElementById('sp-particles');pc.innerHTML=''; for(let i=0;i<28;i++){const p=document.createElement('div');p.className='sp-particle';const ang=Math.random()*Math.PI*2,dist=120+Math.random()*180;p.style.setProperty('--tx',Math.cos(ang)*dist+'px');p.style.setProperty('--ty',Math.sin(ang)*dist-60+'px');p.style.setProperty('--spd',(1.6+Math.random()*1.4)+'s');p.style.setProperty('--del',(Math.random()*0.6)+'s');p.style.setProperty('--sp-pcolor',cfg2.pcolor);p.style.left=(40+Math.random()*20)+'%';p.style.top=(40+Math.random()*20)+'%';pc.appendChild(p);} splash.style.setProperty('--sp-color',cfg2.color);splash.classList.add('show'); setTimeout(()=>{splash.classList.remove('show');onDone()},cfg2.dur); } function startRevealSequence(){ G.stage='result';document.getElementById('decl-count-row').style.display='flex'; const active=G.players.filter(p=>!p.folded); active.forEach(p=>{if(p.decl==='swing'){p.highHand=bestHi(p.cards);p.lowHand=swingLo(p.cards)}else if(p.decl==='high'){p.highHand=bestHi(p.cards);p.lowHand=null}else{p.lowHand=bestLo(p.cards);p.highHand=null}}); const revealOrder=getBossOrder().filter(p=>!p.folded); let t=0;const DECL_GAP=1600; revealOrder.forEach((p,i)=>{setTimeout(()=>{showDeclRevealPopup(p.id,p.decl);},t+i*DECL_GAP);}); t+=revealOrder.length*DECL_GAP+800; const CARD_GAP=1400; revealOrder.forEach((p,i)=>{setTimeout(()=>{flipHiddenCards(p,()=>{setTimeout(()=>showHandRevealPopup(p),400);});},t+i*(CARD_GAP*2));}); t+=revealOrder.length*(CARD_GAP*2)+1200; let specialQueue=[]; revealOrder.forEach(p=>{if(p.highHand&&p.highHand.tier>=7)specialQueue.push({tier:p.highHand.tier,name:p.emoji+' '+p.name});}); specialQueue.sort((a,b)=>b.tier-a.tier); if(specialQueue.length){const run=(idx)=>{if(idx>=specialQueue.length){setTimeout(()=>finishSettle(active),400);return}setTimeout(()=>showSpecialSplash(specialQueue[idx].tier,specialQueue[idx].name,()=>run(idx+1)),idx===0?t:200)};setTimeout(()=>run(0),t);} else setTimeout(()=>finishSettle(active),t); } function showHandRevealPopup(p){ if(p.decl==='high'||p.decl==='swing'){const h=p.highHand;if(h)showHandPopup(p.id,hiName(h),h.tier,false);} if(p.decl==='low'||p.decl==='swing'){const l=p.lowHand;if(l){const info=getLoPopupInfo(l);setTimeout(()=>showHandPopup(p.id,info.text,info.score,true),p.decl==='swing'?900:0);}} } function finishSettle(active){ const gains=settleGame(active,G.pot); gains.forEach(g=>{const p=G.players.find(x=>x.id===g.id);if(p)p.chips+=g.gain}); renderAll();updateHUD();setTimeout(()=>showResultOverlay(gains,active),500); } function showResultOverlay(gains,active){ const allHi=active.filter(p=>p.decl==='high'||p.decl==='swing'); const allLo=active.filter(p=>p.decl==='low'||p.decl==='swing'); let hiW=null,loW=null; if(allHi.length)hiW=allHi.reduce((b,p)=>p.highHand&&(!b.highHand||cmpHi(p.highHand,b.highHand)>0)?p:b); if(allLo.length)loW=allLo.reduce((b,p)=>p.lowHand&&(!b.lowHand||cmpLo(p.lowHand,b.lowHand)>0)?p:b); const banners=document.getElementById('win-banners');banners.innerHTML=''; if(hiW)banners.innerHTML+=`
▲ 하이 WIN ${hiW.emoji} ${hiW.name}
`; if(loW&&loW!==hiW)banners.innerHTML+=`
▼ 로우 WIN ${loW.emoji} ${loW.name}
`; const maxGain=gains.reduce((m,g)=>g.gain>m?g.gain:m,0); const bgEl=document.getElementById('big-gain'); if(maxGain>0){bgEl.style.display='';bgEl.textContent='+'+fmt(maxGain)}else bgEl.style.display='none'; document.getElementById('result-rows').innerHTML=active.map(p=>{ const g=gains.find(x=>x.id===p.id)||{gain:0},net=g.gain-p.bet; const dlbl={high:'▲하이',low:'▼로우',swing:'↕스윙'}[p.decl]||'—'; const dcls={high:'hi',low:'lo',swing:'sw'}[p.decl]||''; const hand=p.decl==='high'?hiName(p.highHand):p.decl==='low'?loName(p.lowHand):`${hiName(p.highHand)}/${loName(p.lowHand)}`; return`${p.emoji}
${p.name}
${hand}
${dlbl}${net>0?'+':''}${fmt(net)}`; }).join(''); show('ov-result'); } function nextRound(){ hide('ov-result');G.round++;G.deck=shuffle(createDeck()); G.pot=cfg.blind*G.players.length;G.currentBet=cfg.blind; G.players.forEach(p=>{p.bet=cfg.blind;p.chips-=cfg.blind}); if(_bossMarkerEl)_bossMarkerEl.style.display='none'; doDeal(); } // ══════════════════════════════════════ // 유틸 // ══════════════════════════════════════ function evalHands(p){if(p.cards.length<5)return;p.highHand=bestHi(p.cards);p.lowHand=bestLo(p.cards)} function renderAll(){G.players.forEach(p=>renderPlayerCards(p));updateHUD()} function enableBtns(on){document.querySelectorAll('.bbar-btn').forEach(b=>b.classList.toggle('active',on))} function showTurn(on){ document.getElementById('turn-badge').style.display=on?'':'none'; const tb=document.getElementById('tb'); if(on){tb.style.animation='none';void tb.offsetHeight;tb.style.animation='tdown 20s linear forwards'}else tb.style.width='0'; } function show(id){document.getElementById(id).classList.add('show')} function hide(id){document.getElementById(id).classList.remove('show')} function addLog(msg){G.logs=G.logs||[];G.logs.push(msg);if(G.logs.length>60)G.logs.shift()} function showLog(){ const el=document.getElementById('log-panel'); el.style.display=el.style.display==='none'?'block':'none'; if(el.style.display!=='none')el.innerHTML=(G.logs||[]).slice(-20).reverse().map(l=>`
${l}
`).join(''); }