diff --git a/ingame.html b/ingame.html index ba9c7eb..ad0927d 100644 --- a/ingame.html +++ b/ingame.html @@ -3,14 +3,13 @@ -Hit or Run — Final +Hit or Run
-
Your Total Card: 0
PLACE YOUR BET
@@ -291,7 +217,6 @@
-
DEALER
@@ -299,43 +224,21 @@
-
-
BALANCE: 5000
-
CURRENT BET: 0
-
+
+
-
- -
- - +
+ + +
-
- -
@@ -354,32 +257,13 @@ const endScreen = document.getElementById('endScreen'); const endMessage = document.getElementById('endMessage'); const playAgainBtn = document.getElementById('playAgainBtn'); -// UI Betting Utama -const balanceUI = document.getElementById('balanceUI'); -const currentBetUI = document.getElementById('currentBetUI'); - -// UI Betting Modal -const bettingScreen = document.getElementById('bettingScreen'); -const modalBalanceUI = document.getElementById('modalBalanceUI'); -const modalBetInput = document.getElementById('modalBetInput'); -const modalBetError = document.getElementById('modalBetError'); -const modalDealBtn = document.getElementById('modalDealBtn'); - - -let balance = 5000; -let currentBet = 0; -let gamePhase = 'BETTING'; - - let playerCards = []; let dealerCards = []; -let playerEls = []; -let dealerEls = []; - -/* store all hidden dealer cards and their back elements */ +let playerEls = []; +let dealerEls = []; let dealerHiddenCards = []; -let dealerHiddenEls = []; - +let dealerHiddenEls = []; +let gamePhase = 'PLAYING'; // langsung start /* ---------- helpers ---------- */ function randomCard(){ @@ -388,7 +272,6 @@ function randomCard(){ return {rank:r, suit:s, color:(s==='♥'||s==='♦')?'red':'black'}; } -// Mengembalikan elemen CARD (Wajah) function createCardEl(card){ const el = document.createElement('div'); el.className = 'card' + (card.color==='red' ? ' red' : ''); @@ -400,28 +283,21 @@ function createCardEl(card){ return el; } -// Mengembalikan elemen Punggung Kartu function createBackCardEl(){ const el = document.createElement('div'); - el.className = 'card back-card'; + el.className = 'card back-card'; el.innerText = 'HIT'; return el; } -// Fungsi pembungkus untuk menciptakan elemen kartu (front/back) di dalam wrapper (card-wrapper) function wrapCardInContainer(cardEl, isDealer = false){ - const wrapper = document.createElement('div'); - wrapper.className = 'card-wrapper'; - - if (!isDealer) { - wrapper.style.perspective = '1000px'; - } - - wrapper.appendChild(cardEl); - return wrapper; + const wrapper = document.createElement('div'); + wrapper.className = 'card-wrapper'; + if (!isDealer) wrapper.style.perspective = '1000px'; + wrapper.appendChild(cardEl); + return wrapper; } - function calc(cards){ let total=0, ace=0; for(const c of cards){ @@ -436,89 +312,56 @@ function calc(cards){ /* ---------- UI updates ---------- */ function updateTotals(){ document.getElementById('playerTotalUI').innerText = calc(playerCards); - const hiddenExists = dealerHiddenEls.length > 0; - if(hiddenExists){ - document.getElementById('dealerTotalUI').innerText = '??'; - } else { - document.getElementById('dealerTotalUI').innerText = calc(dealerCards); - } + if(hiddenExists) document.getElementById('dealerTotalUI').innerText = '??'; + else document.getElementById('dealerTotalUI').innerText = calc(dealerCards); } /* layout overlap */ function layoutOverlap(list, wrapper){ const isDealer = (wrapper === dealerWrapper); - - const cardW_base = 110; - const cardH_base = 154; - - const cardW = cardW_base; - const cardH = cardH_base; - - const overlap = 28; + const cardW = 110; + const cardH = 154; + const overlap = 28; const count = list.length; - - const totalWidth = cardW + Math.max(0, count-1)*overlap; - + const totalWidth = cardW + Math.max(0, count-1)*overlap; const startX = (wrapper.clientWidth - totalWidth)/2; - list.forEach((el, i)=>{ const x = startX + i*overlap; - let y = (wrapper.clientHeight - cardH) / 2; - - if (isDealer) { - y += 0; - } - - el.style.transform = `translate3d(${x}px, ${y}px, 0)`; + el.style.transform = `translate3d(${x}px, ${y}px, 0)`; el.style.zIndex = 100 + i; }); } -// FUNGSI ANIMASI BARU +/* animation from deck */ function animateFromDeck(cardWrapper, isInitial = true){ - const deckRect = deckEl.getBoundingClientRect(); - const stageRect = document.getElementById('stage').getBoundingClientRect(); - - // Perbaikan: Menggunakan offsetLeft dan offsetTop karena Deck sekarang absolute terhadap stage - const startX = deckEl.offsetLeft; - const startY = deckEl.offsetTop; - - // Ambil posisi target dari transform yang sudah dihitung layoutOverlap + const startX = deckEl.offsetLeft; + const startY = deckEl.offsetTop; const targetTransformMatch = cardWrapper.style.transform.match(/translate3d\((.*?)px,\s*(.*?)px/); - const targetX = parseFloat(targetTransformMatch[1]); - const targetY = parseFloat(targetTransformMatch[2]); - - // Set posisi awal (dari deck, dengan scale dan rotasi) - cardWrapper.style.transition = 'none'; // Matikan transisi untuk penempatan awal + const targetX = parseFloat(targetTransformMatch ? targetTransformMatch[1] : startX); + const targetY = parseFloat(targetTransformMatch ? targetTransformMatch[2] : startY); + cardWrapper.style.transition = 'none'; cardWrapper.style.transform = `translate3d(${startX}px, ${startY}px, 0) scale(0.4)`; - - // Tambahkan kelas is-moving untuk efek bayangan - cardWrapper.querySelector('.card').classList.add('is-moving'); - - // Jeda kecil, lalu aktifkan transisi - requestAnimationFrame(() => { - requestAnimationFrame(() => { - cardWrapper.style.transition = ''; // Aktifkan semua transisi - - // Target posisi akhir - cardWrapper.style.transform = `translate3d(${targetX}px, ${targetY}px, 0) scale(1)`; - - // Rotasi acak untuk efek kartu 'terbang' lebih realistis - // Karena deck ada di kanan, rotasi Z acak ke arah berlawanan - const rotZ = (Math.random() - 1.0) * 12; // Rotasi Z negatif - const rotX = isInitial ? 0 : (Math.random() - 0.5) * 10; - - cardWrapper.querySelector('.card').style.transition = 'transform .4s cubic-bezier(.68,-0.55,.27,1.55), filter .3s ease'; - cardWrapper.querySelector('.card').style.transform = `rotateZ(${rotZ}deg) rotateX(${rotX}deg)`; - - setTimeout(() => { - // Hapus kelas moving setelah transisi selesai - cardWrapper.querySelector('.card').classList.remove('is-moving'); - cardWrapper.querySelector('.card').style.transform = `rotateZ(0deg) rotateX(0deg)`; // Kembali ke posisi normal - cardWrapper.querySelector('.card').style.transition = ''; // Kembali ke transisi default CSS - }, 400); // Durasi transisi + const inner = cardWrapper.querySelector('.card'); + if(inner) inner.classList.add('is-moving'); + requestAnimationFrame(()=>{ + requestAnimationFrame(()=>{ + cardWrapper.style.transition = ''; + cardWrapper.style.transform = `translate3d(${targetX}px, ${targetY}px, 0) scale(1)`; + const rotZ = (Math.random() - 0.5) * 12; + const rotX = isInitial ? 0 : (Math.random() - 0.5) * 10; + if(inner){ + inner.style.transition = 'transform .4s cubic-bezier(.68,-0.55,.27,1.55), filter .3s ease'; + inner.style.transform = `rotateZ(${rotZ}deg) rotateX(${rotX}deg)`; + } + setTimeout(()=>{ + if(inner){ + inner.classList.remove('is-moving'); + inner.style.transform = `rotateZ(0deg) rotateX(0deg)`; + inner.style.transition = ''; + } + },420); }); }); } @@ -527,86 +370,59 @@ function animateFromDeck(cardWrapper, isInitial = true){ function dealPlayer(){ const card = randomCard(); playerCards.push(card); - const cardEl = createCardEl(card); const wrapper = wrapCardInContainer(cardEl, false); cardsWrapper.appendChild(wrapper); playerEls.push(wrapper); - - layoutOverlap(playerEls, cardsWrapper); - animateFromDeck(wrapper); - + layoutOverlap(playerEls, cardsWrapper); + animateFromDeck(wrapper); updateTotals(); } function dealDealer(faceDown=false){ - let card, wrapper; - + const card = randomCard(); + dealerCards.push(card); + let wrapper; if(faceDown){ - card = randomCard(); - dealerCards.push(card); - const backEl = createBackCardEl(); wrapper = wrapCardInContainer(backEl, true); - dealerWrapper.appendChild(wrapper); - dealerEls.push(wrapper); - dealerHiddenCards.push(card); - dealerHiddenEls.push(wrapper); - + dealerHiddenEls.push(wrapper); } else { - card = randomCard(); - dealerCards.push(card); - const cardEl = createCardEl(card); wrapper = wrapCardInContainer(cardEl, true); - dealerWrapper.appendChild(wrapper); - dealerEls.push(wrapper); } - - layoutOverlap(dealerEls, dealerWrapper); + dealerEls.push(wrapper); + dealerWrapper.appendChild(wrapper); + layoutOverlap(dealerEls, dealerWrapper); animateFromDeck(wrapper); - updateTotals(); } -/* open all hidden dealer cards (flip) */ function flipAllDealerHidden(){ const hiddenWrappers = Array.from(dealerHiddenEls); - hiddenWrappers.forEach((wrapper, i) => { const backEl = wrapper.querySelector('.back-card'); const cardObj = dealerHiddenCards[i]; - const realEl = createCardEl(cardObj); - + const realEl = createCardEl(cardObj); setTimeout(()=>{ - // Flip: Mengubah isi wrapper (punggung -> depan) - backEl.style.transform = 'rotateY(90deg)'; - backEl.style.opacity = '0'; - - realEl.style.transform = 'rotateY(-90deg)'; - realEl.style.opacity = '0'; + if(backEl){ backEl.style.transform = 'rotateY(90deg)'; backEl.style.opacity = '0'; } + realEl.style.transform = 'rotateY(-90deg)'; realEl.style.opacity = '0'; wrapper.appendChild(realEl); - - setTimeout(() => { - backEl.remove(); - realEl.style.transform = 'rotateY(0deg)'; - realEl.style.opacity = '1'; - - updateTotals(); - }, 200); - - }, 300 + i*300); - + setTimeout(()=>{ + if(backEl) backEl.remove(); + realEl.style.transform = 'rotateY(0deg)'; + realEl.style.opacity = '1'; + updateTotals(); + },220); + },300 + i*300); }); - setTimeout(()=>{ dealerHiddenEls = []; dealerHiddenCards = []; - }, 300 + hiddenWrappers.length*300); + }, 300 + hiddenWrappers.length*300 + 100); } - /* ---------- dealer play & game end ---------- */ function dealerPlay(){ gamePhase = 'DEALER_TURN'; @@ -625,34 +441,12 @@ function finishResult(){ const p = calc(playerCards); const d = calc(dealerCards); let msg = ''; - let payout = 0; - - // Logic Payout - if(p > 21) { - msg = 'PLAYER BUST — YOU LOSE'; - } else if(d > 21) { - msg = 'DEALER BUST — YOU WIN!'; - payout = currentBet * 2; - } else if(p === 21 && playerCards.length === 2 && d !== 21) { - msg = 'BLACKJACK! — YOU WIN! (3:2)'; - payout = currentBet + (currentBet * 1.5); - } else if(p > d) { - msg = 'YOU WIN!'; - payout = currentBet * 2; - } else if(p < d) { - msg = 'YOU LOSE!'; - } else { - msg = 'PUSH (DRAW) — TARUHAN KEMBALI'; - payout = currentBet; - } - - // Update saldo dan UI - balance += payout; - balanceUI.innerText = balance; - modalBalanceUI.innerText = balance; - currentBet = 0; - currentBetUI.innerText = currentBet; - + if(p > 21) msg = 'PLAYER BUST — YOU LOSE'; + else if(d > 21) msg = 'DEALER BUST — YOU WIN!'; + else if(p === 21 && playerCards.length === 2 && d !== 21) msg = 'BLACKJACK! — YOU WIN! (3:2)'; + else if(p > d) msg = 'YOU WIN!'; + else if(p < d) msg = 'YOU LOSE!'; + else msg = 'PUSH (DRAW) — TARUHAN KEMBALI'; showEnd(msg); } @@ -661,7 +455,6 @@ function showEnd(msg){ document.getElementById('status').innerText = msg; hitBtn.disabled = true; standBtn.disabled = true; - endMessage.innerText = msg; endScreen.style.display = 'flex'; playAgainBtn.focus(); @@ -672,7 +465,7 @@ function hit(){ if(gamePhase !== 'PLAYING' || hitBtn.disabled) return; dealPlayer(); if(calc(playerCards) > 21){ - setTimeout(() => finishResult(), 500); + setTimeout(() => finishResult(), 500); } } @@ -682,108 +475,49 @@ function stand(){ hitBtn.disabled = true; standBtn.disabled = true; document.getElementById('status').innerText = 'DEALER TURN'; - flipAllDealerHidden(); setTimeout(()=> dealerPlay(), 300 + dealerHiddenEls.length*300); } -/* Fungsi untuk menangani deal dari modal betting */ -function handleDeal(){ - if(gamePhase !== 'BETTING') return; +/* ---------- start / restart ---------- */ +function startGame(){ + // disable during dealing to prevent accidental clicks + hitBtn.disabled = true; + standBtn.disabled = true; + playerCards = []; dealerCards = []; playerEls.forEach(e=>e.remove()); dealerEls.forEach(e=>e.remove()); + playerEls = []; dealerEls = []; dealerHiddenCards = []; dealerHiddenEls = []; + endScreen.style.display = 'none'; - const bet = parseInt(modalBetInput.value, 10); - const minBet = 10; - - if(isNaN(bet) || bet < minBet){ - modalBetError.innerText = `Taruhan minimum adalah $${minBet}.`; - return; - } - if(bet > balance){ - modalBetError.innerText = 'Saldo tidak cukup.'; - return; - } - - // Set taruhan dan kurangi saldo - currentBet = bet; - balance -= currentBet; - - // Update UI di stage utama - balanceUI.innerText = balance; - currentBetUI.innerText = currentBet; - - // Sembunyikan Modal dan mulai permainan - bettingScreen.style.display = 'none'; - modalBetError.innerText = ''; - - // Masuk ke fase bermain - gamePhase = 'PLAYING'; - document.getElementById('status').innerText = 'YOUR TURN'; - - // Aktifkan HIT/STAND - hitBtn.disabled = false; - standBtn.disabled = false; + // dealing sequence + setTimeout(()=> dealPlayer(), 80); + setTimeout(()=> dealDealer(false), 280); // dealer first open + setTimeout(()=> dealPlayer(), 480); + setTimeout(()=> dealDealer(true), 680); // dealer second hidden - // Mulai permainan + // after dealing finished, enable buttons and set phase to PLAYING + setTimeout(()=>{ + gamePhase = 'PLAYING'; + document.getElementById('status').innerText = 'YOUR TURN'; + hitBtn.disabled = false; + standBtn.disabled = false; + // auto-stand on natural + if(calc(playerCards) === 21) stand(); + }, 900); +} + +function restart(){ + // reset and start again startGame(); } - -/* ---------- restart / reset ---------- */ -function restart(){ - // clear state - playerCards = []; - dealerCards = []; - playerEls.forEach(e=> e.remove()); - dealerEls.forEach(e=> e.remove()); - playerEls = []; - dealerEls = []; - dealerHiddenCards = []; - dealerHiddenEls = []; - - // hide end screen & UI reset - endScreen.style.display = 'none'; - document.getElementById('status').innerText = 'PLACE YOUR BET'; - - // Reset UI kontrol - hitBtn.disabled = true; - standBtn.disabled = true; - - gamePhase = 'BETTING'; - updateTotals(); - - // Tampilkan Modal Betting - modalBalanceUI.innerText = balance; - modalBetInput.value = Math.min(100, balance); - bettingScreen.style.display = 'flex'; - modalBetInput.focus(); -} - -/* ---------- initial deal ---------- */ -function startGame(){ - // Urutan dealing dengan jeda 200ms - setTimeout(()=> dealPlayer(), 80); - setTimeout(()=> dealDealer(true), 280); - setTimeout(()=> dealPlayer(), 480); - setTimeout(()=> dealDealer(true), 680); - - setTimeout(() => { - if(calc(playerCards) === 21){ - stand(); - } - }, 800); - - updateTotals(); -} - /* ---------- events ---------- */ -modalDealBtn.addEventListener('click', handleDeal); hitBtn.addEventListener('click', hit); standBtn.addEventListener('click', stand); playAgainBtn.addEventListener('click', restart); window.addEventListener('resize', ()=>{ layoutOverlap(playerEls, cardsWrapper); layoutOverlap(dealerEls, dealerWrapper); }); -/* start - Panggil restart untuk memulai dengan modal betting */ -restart(); +/* start immediately */ +startGame(); - \ No newline at end of file +