ingame
This commit is contained in:
parent
0d9a8aac42
commit
29b8ca750b
520
ingame.html
520
ingame.html
@ -3,14 +3,13 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Hit or Run — Final</title>
|
<title>Hit or Run</title>
|
||||||
<style>
|
<style>
|
||||||
:root{
|
:root{
|
||||||
--card-w:110px;
|
--card-w:110px;
|
||||||
--card-h:154px;
|
--card-h:154px;
|
||||||
/* Posisi Deck Baru (di Kanan, agak ke atas) */
|
--deck-x: 800px;
|
||||||
--deck-x: 800px; /* Jarak dari kiri stage */
|
--deck-y: 100px;
|
||||||
--deck-y: 100px; /* Jarak dari atas stage */
|
|
||||||
}
|
}
|
||||||
*{box-sizing:border-box}
|
*{box-sizing:border-box}
|
||||||
body{
|
body{
|
||||||
@ -22,7 +21,6 @@
|
|||||||
font-family:Inter,system-ui,Arial;
|
font-family:Inter,system-ui,Arial;
|
||||||
background:#071827
|
background:#071827
|
||||||
}
|
}
|
||||||
|
|
||||||
.stage{
|
.stage{
|
||||||
width:960px;
|
width:960px;
|
||||||
height:540px;
|
height:540px;
|
||||||
@ -31,75 +29,61 @@
|
|||||||
position:relative;
|
position:relative;
|
||||||
box-shadow:0 18px 50px rgba(0,0,0,.6)
|
box-shadow:0 18px 50px rgba(0,0,0,.6)
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg{
|
.bg{
|
||||||
position:absolute;
|
position:absolute;
|
||||||
inset:0;
|
inset:0; background:#153f28
|
||||||
background:#153f28
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay{
|
.overlay{
|
||||||
position:absolute;
|
position:absolute; inset:0;
|
||||||
inset:0;background:linear-gradient(180deg, rgba(11,31,20,.45), rgba(8,24,16,.65));
|
background:linear-gradient(180deg, rgba(11,31,20,.45), rgba(8,24,16,.65));
|
||||||
display:flex;
|
display:flex; flex-direction:column; padding:18px
|
||||||
flex-direction:column;
|
|
||||||
padding:18px
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header{
|
.header{
|
||||||
color:#dff6e9;
|
color:#dff6e9;
|
||||||
font-weight:700;
|
font-weight:700;
|
||||||
display:flex;
|
display:flex;
|
||||||
justify-content:space-between
|
justify-content:space-between
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-area{
|
.table-area{
|
||||||
flex:1;
|
flex:1; display:flex;
|
||||||
display:flex;
|
|
||||||
align-items:center;
|
align-items:center;
|
||||||
justify-content:center;
|
justify-content:center;
|
||||||
position:relative
|
position:relative
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DECK BARU (Dipindah ke Kanan Dealer) */
|
|
||||||
.deck {
|
.deck {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
/* MENGGUNAKAN POSISI BARU */
|
|
||||||
left: var(--deck-x);
|
left: var(--deck-x);
|
||||||
top: var(--deck-y);
|
top: var(--deck-y);
|
||||||
transform: none; /* Dihapus karena sudah ditentukan oleh --deck-x */
|
|
||||||
width: 82px;
|
width: 82px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #113625;
|
background: #113625;
|
||||||
box-shadow: 0 0 0 2px #0d291c inset, 0 10px 20px rgba(0,0,0,0.55);
|
box-shadow: 0 0 0 2px #0d291c inset, 0 10px 20px rgba(0,0,0,0.55);
|
||||||
display: flex;
|
display:flex; align-items:center; justify-content:center;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color:#b9e4d0;
|
color:#b9e4d0;
|
||||||
font-weight:900;
|
font-weight:900;
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
letter-spacing:2px;
|
letter-spacing:2px;
|
||||||
z-index:500;
|
z-index:500;
|
||||||
}
|
}
|
||||||
.deck::before,
|
.player-zone{
|
||||||
.deck::after {
|
|
||||||
content: "";
|
|
||||||
position:absolute;
|
position:absolute;
|
||||||
width: 82px;
|
left:37%;
|
||||||
height: 120px;
|
bottom:18px;
|
||||||
border-radius: 10px;
|
transform:translateX(-50%);
|
||||||
background: #113625;
|
width:78%;
|
||||||
box-shadow: 0 0 0 2px #0d291c inset;
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
align-items:center
|
||||||
|
}
|
||||||
|
.cards-wrapper{
|
||||||
|
position:relative;
|
||||||
|
width:580px;
|
||||||
|
height:170px;
|
||||||
|
display:flex;
|
||||||
|
justify-content:center;
|
||||||
|
align-items:center;
|
||||||
}
|
}
|
||||||
.deck::before { top: -3px; left: -3px; opacity: 0.9; }
|
|
||||||
.deck::after { top: -6px; left: -6px; opacity: 0.8; }
|
|
||||||
|
|
||||||
/* PLAYER */
|
|
||||||
.player-zone{position:absolute;left:50%;bottom:18px;transform:translateX(-50%);width:78%;display:flex;flex-direction:column;align-items:center}
|
|
||||||
.cards-wrapper{position:relative;width:580px;height:170px}
|
|
||||||
|
|
||||||
/* DEALER ZONE */
|
|
||||||
.dealer-zone{
|
.dealer-zone{
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:50%;
|
left:50%;
|
||||||
@ -113,19 +97,20 @@
|
|||||||
color:#dff6e9;
|
color:#dff6e9;
|
||||||
font-weight:700
|
font-weight:700
|
||||||
}
|
}
|
||||||
.dealer-cards{position:relative;width:580px;height:170px}
|
.dealer-cards{
|
||||||
|
position:relative;
|
||||||
/* WRAPPER KARTU */
|
width:580px;
|
||||||
|
height:170px
|
||||||
|
}
|
||||||
.card-wrapper {
|
.card-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: var(--card-w);
|
width: var(--card-w);
|
||||||
height: var(--card-h);
|
height: var(--card-h);
|
||||||
transition: transform .4s cubic-bezier(.68,-0.55,.27,1.55);
|
transition: transform .4s cubic-bezier(.68,-0.55,.27,1.55);
|
||||||
transform-origin: center center;
|
align-items: center;
|
||||||
|
transform-origin: center;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CARDS (Base Card) */
|
|
||||||
.card{
|
.card{
|
||||||
width:var(--card-w);
|
width:var(--card-w);
|
||||||
height:var(--card-h);
|
height:var(--card-h);
|
||||||
@ -138,28 +123,26 @@
|
|||||||
font-weight:800;
|
font-weight:800;
|
||||||
box-shadow:0 10px 22px rgba(0,0,0,.45);
|
box-shadow:0 10px 22px rgba(0,0,0,.45);
|
||||||
position:absolute;
|
position:absolute;
|
||||||
transition: transform .4s cubic-bezier(.68,-0.55,.27,1.55),
|
transition: transform .4s cubic-bezier(.68,-0.55,.27,1.55), opacity .3s ease, filter .3s ease;
|
||||||
opacity .3s ease,
|
|
||||||
filter .3s ease;
|
|
||||||
backface-visibility:hidden;
|
backface-visibility:hidden;
|
||||||
transform-origin:center center;
|
transform-origin:center center;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Efek bayangan saat kartu sedang terbang */
|
|
||||||
.card.is-moving {
|
.card.is-moving {
|
||||||
filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.4));
|
filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.4));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Warna Teks Merah untuk suit ♥ dan ♦ */
|
|
||||||
.card.red{
|
.card.red{
|
||||||
color:#c42b2b
|
color:#c42b2b
|
||||||
}
|
}
|
||||||
|
.card .corner{
|
||||||
.card .corner{font-size:18px}
|
font-size:18px
|
||||||
.card .center{font-size:40px;display:flex;align-items:center;justify-content:center}
|
}
|
||||||
|
.card .center{
|
||||||
/* Punggung kartu dealer (Kartu tertutup) */
|
font-size:40px;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
}
|
||||||
.back-card{
|
.back-card{
|
||||||
background:#122f21;
|
background:#122f21;
|
||||||
display:flex;
|
display:flex;
|
||||||
@ -171,18 +154,25 @@
|
|||||||
font-weight:900;
|
font-weight:900;
|
||||||
border-radius:10px;
|
border-radius:10px;
|
||||||
}
|
}
|
||||||
|
.controls{
|
||||||
.controls{display:flex;gap:12px;margin-top:8px}
|
display:flex;
|
||||||
button{background:#0f5436;color:#e6fff2;border:0;padding:10px 16px;border-radius:8px;cursor:pointer;font-weight:700}
|
gap:12px;
|
||||||
button:disabled{background:#2d2d2d;opacity:.5;cursor:not-allowed}
|
margin-top:8px
|
||||||
|
}
|
||||||
/* Input styling */
|
button{
|
||||||
#modalBetInput {
|
background:#0f5436;
|
||||||
background: #e6fff2;
|
color:#e6fff2;
|
||||||
color: #0b2b18;
|
border:0;
|
||||||
|
padding:10px 16px;
|
||||||
|
border-radius:8px;
|
||||||
|
cursor:pointer;
|
||||||
|
font-weight:700
|
||||||
|
}
|
||||||
|
button:disabled{
|
||||||
|
background:#2d2d2d;
|
||||||
|
opacity:.5;
|
||||||
|
cursor:not-allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
/* END SCREEN */
|
|
||||||
.end-screen {
|
.end-screen {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@ -210,71 +200,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.end-screen button:hover { background: #1ccf86; }
|
.end-screen button:hover {
|
||||||
|
background: #1ccf86;
|
||||||
|
|
||||||
/* BETTING SCREEN (MODAL) */
|
|
||||||
.betting-screen {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(0,0,0,0.6);
|
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
color: white;
|
|
||||||
font-weight: 700;
|
|
||||||
z-index: 900;
|
|
||||||
}
|
|
||||||
.betting-panel {
|
|
||||||
background: #153f28;
|
|
||||||
padding: 30px 40px;
|
|
||||||
border-radius: 15px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0,0,0,0.8);
|
|
||||||
text-align: center;
|
|
||||||
min-width: 300px;
|
|
||||||
}
|
|
||||||
.betting-panel h2 {
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: 24px;
|
|
||||||
color: #e6fff2;
|
|
||||||
}
|
|
||||||
.betting-panel button {
|
|
||||||
margin-top: 20px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.betting-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
.bet-input-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.bet-input-group label {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.bet-input-group input {
|
|
||||||
padding: 8px;
|
|
||||||
font-size: 18px;
|
|
||||||
width: 100px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #0f5436;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.bet-error {
|
|
||||||
min-height: 20px;
|
|
||||||
color: #ff9999;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@ -282,7 +209,6 @@
|
|||||||
<div class="stage" id="stage">
|
<div class="stage" id="stage">
|
||||||
<div class="bg"></div>
|
<div class="bg"></div>
|
||||||
<div class="overlay">
|
<div class="overlay">
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div>Your Total Card: <strong id="playerTotalUI">0</strong></div>
|
<div>Your Total Card: <strong id="playerTotalUI">0</strong></div>
|
||||||
<div id="status">PLACE YOUR BET</div>
|
<div id="status">PLACE YOUR BET</div>
|
||||||
@ -291,7 +217,6 @@
|
|||||||
<div id="deck" class="deck" aria-hidden="true">DECK</div>
|
<div id="deck" class="deck" aria-hidden="true">DECK</div>
|
||||||
|
|
||||||
<div class="table-area">
|
<div class="table-area">
|
||||||
|
|
||||||
<div class="dealer-zone" aria-live="polite">
|
<div class="dealer-zone" aria-live="polite">
|
||||||
<div class="dealer-label">DEALER</div>
|
<div class="dealer-label">DEALER</div>
|
||||||
<div class="dealer-cards" id="dealerWrapper" aria-hidden="false"></div>
|
<div class="dealer-cards" id="dealerWrapper" aria-hidden="false"></div>
|
||||||
@ -299,11 +224,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="player-zone">
|
<div class="player-zone">
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center;width:100%;color:#dff6e9;font-weight:700;margin-bottom:8px">
|
<div style="display:flex;justify-content:center;align-items:center;width:100%;color:#dff6e9;font-weight:700;margin-bottom:8px;gap:40px">
|
||||||
<div>BALANCE: <span id="balanceUI">5000</span></div>
|
|
||||||
<div>CURRENT BET: <span id="currentBetUI">0</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cards-wrapper" id="cardsWrapper" aria-live="polite"></div>
|
<div class="cards-wrapper" id="cardsWrapper" aria-live="polite"></div>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
@ -311,31 +232,13 @@
|
|||||||
<button id="hit" disabled>HIT</button>
|
<button id="hit" disabled>HIT</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="betting-screen" id="bettingScreen" role="dialog" aria-modal="true">
|
|
||||||
<div class="betting-panel">
|
|
||||||
<h2>PLACE YOUR BET</h2>
|
|
||||||
<div class="betting-info">
|
|
||||||
<div>Balance: $<span id="modalBalanceUI">5000</span></div>
|
|
||||||
<div>Min Bet: $10</div>
|
|
||||||
</div>
|
|
||||||
<div class="bet-input-group">
|
|
||||||
<label for="modalBetInput">Bet:</label>
|
|
||||||
<input type="number" id="modalBetInput" min="10" max="5000" value="100">
|
|
||||||
</div>
|
|
||||||
<div class="bet-error" id="modalBetError"></div>
|
|
||||||
<button id="modalDealBtn">DEAL</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="end-screen" id="endScreen" role="dialog" aria-modal="true">
|
<div class="end-screen" id="endScreen" role="dialog" aria-modal="true">
|
||||||
<div id="endMessage">RESULT</div>
|
<div id="endMessage">RESULT</div>
|
||||||
<button id="playAgainBtn">PLAY AGAIN</button>
|
<button id="playAgainBtn">PLAY AGAIN</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -354,32 +257,13 @@ const endScreen = document.getElementById('endScreen');
|
|||||||
const endMessage = document.getElementById('endMessage');
|
const endMessage = document.getElementById('endMessage');
|
||||||
const playAgainBtn = document.getElementById('playAgainBtn');
|
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 playerCards = [];
|
||||||
let dealerCards = [];
|
let dealerCards = [];
|
||||||
let playerEls = [];
|
let playerEls = [];
|
||||||
let dealerEls = [];
|
let dealerEls = [];
|
||||||
|
|
||||||
/* store all hidden dealer cards and their back elements */
|
|
||||||
let dealerHiddenCards = [];
|
let dealerHiddenCards = [];
|
||||||
let dealerHiddenEls = [];
|
let dealerHiddenEls = [];
|
||||||
|
let gamePhase = 'PLAYING'; // langsung start
|
||||||
|
|
||||||
/* ---------- helpers ---------- */
|
/* ---------- helpers ---------- */
|
||||||
function randomCard(){
|
function randomCard(){
|
||||||
@ -388,7 +272,6 @@ function randomCard(){
|
|||||||
return {rank:r, suit:s, color:(s==='♥'||s==='♦')?'red':'black'};
|
return {rank:r, suit:s, color:(s==='♥'||s==='♦')?'red':'black'};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mengembalikan elemen CARD (Wajah)
|
|
||||||
function createCardEl(card){
|
function createCardEl(card){
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.className = 'card' + (card.color==='red' ? ' red' : '');
|
el.className = 'card' + (card.color==='red' ? ' red' : '');
|
||||||
@ -400,7 +283,6 @@ function createCardEl(card){
|
|||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mengembalikan elemen Punggung Kartu
|
|
||||||
function createBackCardEl(){
|
function createBackCardEl(){
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.className = 'card back-card';
|
el.className = 'card back-card';
|
||||||
@ -408,20 +290,14 @@ function createBackCardEl(){
|
|||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fungsi pembungkus untuk menciptakan elemen kartu (front/back) di dalam wrapper (card-wrapper)
|
|
||||||
function wrapCardInContainer(cardEl, isDealer = false){
|
function wrapCardInContainer(cardEl, isDealer = false){
|
||||||
const wrapper = document.createElement('div');
|
const wrapper = document.createElement('div');
|
||||||
wrapper.className = 'card-wrapper';
|
wrapper.className = 'card-wrapper';
|
||||||
|
if (!isDealer) wrapper.style.perspective = '1000px';
|
||||||
if (!isDealer) {
|
|
||||||
wrapper.style.perspective = '1000px';
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.appendChild(cardEl);
|
wrapper.appendChild(cardEl);
|
||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function calc(cards){
|
function calc(cards){
|
||||||
let total=0, ace=0;
|
let total=0, ace=0;
|
||||||
for(const c of cards){
|
for(const c of cards){
|
||||||
@ -436,89 +312,56 @@ function calc(cards){
|
|||||||
/* ---------- UI updates ---------- */
|
/* ---------- UI updates ---------- */
|
||||||
function updateTotals(){
|
function updateTotals(){
|
||||||
document.getElementById('playerTotalUI').innerText = calc(playerCards);
|
document.getElementById('playerTotalUI').innerText = calc(playerCards);
|
||||||
|
|
||||||
const hiddenExists = dealerHiddenEls.length > 0;
|
const hiddenExists = dealerHiddenEls.length > 0;
|
||||||
if(hiddenExists){
|
if(hiddenExists) document.getElementById('dealerTotalUI').innerText = '??';
|
||||||
document.getElementById('dealerTotalUI').innerText = '??';
|
else document.getElementById('dealerTotalUI').innerText = calc(dealerCards);
|
||||||
} else {
|
|
||||||
document.getElementById('dealerTotalUI').innerText = calc(dealerCards);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* layout overlap */
|
/* layout overlap */
|
||||||
function layoutOverlap(list, wrapper){
|
function layoutOverlap(list, wrapper){
|
||||||
const isDealer = (wrapper === dealerWrapper);
|
const isDealer = (wrapper === dealerWrapper);
|
||||||
|
const cardW = 110;
|
||||||
const cardW_base = 110;
|
const cardH = 154;
|
||||||
const cardH_base = 154;
|
|
||||||
|
|
||||||
const cardW = cardW_base;
|
|
||||||
const cardH = cardH_base;
|
|
||||||
|
|
||||||
const overlap = 28;
|
const overlap = 28;
|
||||||
const count = list.length;
|
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;
|
const startX = (wrapper.clientWidth - totalWidth)/2;
|
||||||
|
|
||||||
list.forEach((el, i)=>{
|
list.forEach((el, i)=>{
|
||||||
const x = startX + i*overlap;
|
const x = startX + i*overlap;
|
||||||
|
|
||||||
let y = (wrapper.clientHeight - cardH) / 2;
|
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;
|
el.style.zIndex = 100 + i;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// FUNGSI ANIMASI BARU
|
/* animation from deck */
|
||||||
function animateFromDeck(cardWrapper, isInitial = true){
|
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 startX = deckEl.offsetLeft;
|
||||||
const startY = deckEl.offsetTop;
|
const startY = deckEl.offsetTop;
|
||||||
|
|
||||||
// Ambil posisi target dari transform yang sudah dihitung layoutOverlap
|
|
||||||
const targetTransformMatch = cardWrapper.style.transform.match(/translate3d\((.*?)px,\s*(.*?)px/);
|
const targetTransformMatch = cardWrapper.style.transform.match(/translate3d\((.*?)px,\s*(.*?)px/);
|
||||||
const targetX = parseFloat(targetTransformMatch[1]);
|
const targetX = parseFloat(targetTransformMatch ? targetTransformMatch[1] : startX);
|
||||||
const targetY = parseFloat(targetTransformMatch[2]);
|
const targetY = parseFloat(targetTransformMatch ? targetTransformMatch[2] : startY);
|
||||||
|
cardWrapper.style.transition = 'none';
|
||||||
// Set posisi awal (dari deck, dengan scale dan rotasi)
|
|
||||||
cardWrapper.style.transition = 'none'; // Matikan transisi untuk penempatan awal
|
|
||||||
cardWrapper.style.transform = `translate3d(${startX}px, ${startY}px, 0) scale(0.4)`;
|
cardWrapper.style.transform = `translate3d(${startX}px, ${startY}px, 0) scale(0.4)`;
|
||||||
|
const inner = cardWrapper.querySelector('.card');
|
||||||
// Tambahkan kelas is-moving untuk efek bayangan
|
if(inner) inner.classList.add('is-moving');
|
||||||
cardWrapper.querySelector('.card').classList.add('is-moving');
|
|
||||||
|
|
||||||
// Jeda kecil, lalu aktifkan transisi
|
|
||||||
requestAnimationFrame(()=>{
|
requestAnimationFrame(()=>{
|
||||||
requestAnimationFrame(()=>{
|
requestAnimationFrame(()=>{
|
||||||
cardWrapper.style.transition = ''; // Aktifkan semua transisi
|
cardWrapper.style.transition = '';
|
||||||
|
|
||||||
// Target posisi akhir
|
|
||||||
cardWrapper.style.transform = `translate3d(${targetX}px, ${targetY}px, 0) scale(1)`;
|
cardWrapper.style.transform = `translate3d(${targetX}px, ${targetY}px, 0) scale(1)`;
|
||||||
|
const rotZ = (Math.random() - 0.5) * 12;
|
||||||
// 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;
|
const rotX = isInitial ? 0 : (Math.random() - 0.5) * 10;
|
||||||
|
if(inner){
|
||||||
cardWrapper.querySelector('.card').style.transition = 'transform .4s cubic-bezier(.68,-0.55,.27,1.55), filter .3s ease';
|
inner.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)`;
|
inner.style.transform = `rotateZ(${rotZ}deg) rotateX(${rotX}deg)`;
|
||||||
|
}
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
// Hapus kelas moving setelah transisi selesai
|
if(inner){
|
||||||
cardWrapper.querySelector('.card').classList.remove('is-moving');
|
inner.classList.remove('is-moving');
|
||||||
cardWrapper.querySelector('.card').style.transform = `rotateZ(0deg) rotateX(0deg)`; // Kembali ke posisi normal
|
inner.style.transform = `rotateZ(0deg) rotateX(0deg)`;
|
||||||
cardWrapper.querySelector('.card').style.transition = ''; // Kembali ke transisi default CSS
|
inner.style.transition = '';
|
||||||
}, 400); // Durasi transisi
|
}
|
||||||
|
},420);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -527,86 +370,59 @@ function animateFromDeck(cardWrapper, isInitial = true){
|
|||||||
function dealPlayer(){
|
function dealPlayer(){
|
||||||
const card = randomCard();
|
const card = randomCard();
|
||||||
playerCards.push(card);
|
playerCards.push(card);
|
||||||
|
|
||||||
const cardEl = createCardEl(card);
|
const cardEl = createCardEl(card);
|
||||||
const wrapper = wrapCardInContainer(cardEl, false);
|
const wrapper = wrapCardInContainer(cardEl, false);
|
||||||
cardsWrapper.appendChild(wrapper);
|
cardsWrapper.appendChild(wrapper);
|
||||||
playerEls.push(wrapper);
|
playerEls.push(wrapper);
|
||||||
|
|
||||||
layoutOverlap(playerEls, cardsWrapper);
|
layoutOverlap(playerEls, cardsWrapper);
|
||||||
animateFromDeck(wrapper);
|
animateFromDeck(wrapper);
|
||||||
|
|
||||||
updateTotals();
|
updateTotals();
|
||||||
}
|
}
|
||||||
|
|
||||||
function dealDealer(faceDown=false){
|
function dealDealer(faceDown=false){
|
||||||
let card, wrapper;
|
const card = randomCard();
|
||||||
|
|
||||||
if(faceDown){
|
|
||||||
card = randomCard();
|
|
||||||
dealerCards.push(card);
|
dealerCards.push(card);
|
||||||
|
let wrapper;
|
||||||
|
if(faceDown){
|
||||||
const backEl = createBackCardEl();
|
const backEl = createBackCardEl();
|
||||||
wrapper = wrapCardInContainer(backEl, true);
|
wrapper = wrapCardInContainer(backEl, true);
|
||||||
dealerWrapper.appendChild(wrapper);
|
|
||||||
dealerEls.push(wrapper);
|
|
||||||
|
|
||||||
dealerHiddenCards.push(card);
|
dealerHiddenCards.push(card);
|
||||||
dealerHiddenEls.push(wrapper);
|
dealerHiddenEls.push(wrapper);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
card = randomCard();
|
|
||||||
dealerCards.push(card);
|
|
||||||
|
|
||||||
const cardEl = createCardEl(card);
|
const cardEl = createCardEl(card);
|
||||||
wrapper = wrapCardInContainer(cardEl, true);
|
wrapper = wrapCardInContainer(cardEl, true);
|
||||||
dealerWrapper.appendChild(wrapper);
|
|
||||||
dealerEls.push(wrapper);
|
|
||||||
}
|
}
|
||||||
|
dealerEls.push(wrapper);
|
||||||
|
dealerWrapper.appendChild(wrapper);
|
||||||
layoutOverlap(dealerEls, dealerWrapper);
|
layoutOverlap(dealerEls, dealerWrapper);
|
||||||
animateFromDeck(wrapper);
|
animateFromDeck(wrapper);
|
||||||
|
|
||||||
updateTotals();
|
updateTotals();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* open all hidden dealer cards (flip) */
|
|
||||||
function flipAllDealerHidden(){
|
function flipAllDealerHidden(){
|
||||||
const hiddenWrappers = Array.from(dealerHiddenEls);
|
const hiddenWrappers = Array.from(dealerHiddenEls);
|
||||||
|
|
||||||
hiddenWrappers.forEach((wrapper, i) => {
|
hiddenWrappers.forEach((wrapper, i) => {
|
||||||
const backEl = wrapper.querySelector('.back-card');
|
const backEl = wrapper.querySelector('.back-card');
|
||||||
const cardObj = dealerHiddenCards[i];
|
const cardObj = dealerHiddenCards[i];
|
||||||
const realEl = createCardEl(cardObj);
|
const realEl = createCardEl(cardObj);
|
||||||
|
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
// Flip: Mengubah isi wrapper (punggung -> depan)
|
if(backEl){ backEl.style.transform = 'rotateY(90deg)'; backEl.style.opacity = '0'; }
|
||||||
backEl.style.transform = 'rotateY(90deg)';
|
realEl.style.transform = 'rotateY(-90deg)'; realEl.style.opacity = '0';
|
||||||
backEl.style.opacity = '0';
|
|
||||||
|
|
||||||
realEl.style.transform = 'rotateY(-90deg)';
|
|
||||||
realEl.style.opacity = '0';
|
|
||||||
wrapper.appendChild(realEl);
|
wrapper.appendChild(realEl);
|
||||||
|
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
backEl.remove();
|
if(backEl) backEl.remove();
|
||||||
realEl.style.transform = 'rotateY(0deg)';
|
realEl.style.transform = 'rotateY(0deg)';
|
||||||
realEl.style.opacity = '1';
|
realEl.style.opacity = '1';
|
||||||
|
|
||||||
updateTotals();
|
updateTotals();
|
||||||
}, 200);
|
},220);
|
||||||
|
|
||||||
},300 + i*300);
|
},300 + i*300);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
dealerHiddenEls = [];
|
dealerHiddenEls = [];
|
||||||
dealerHiddenCards = [];
|
dealerHiddenCards = [];
|
||||||
}, 300 + hiddenWrappers.length*300);
|
}, 300 + hiddenWrappers.length*300 + 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ---------- dealer play & game end ---------- */
|
/* ---------- dealer play & game end ---------- */
|
||||||
function dealerPlay(){
|
function dealerPlay(){
|
||||||
gamePhase = 'DEALER_TURN';
|
gamePhase = 'DEALER_TURN';
|
||||||
@ -625,34 +441,12 @@ function finishResult(){
|
|||||||
const p = calc(playerCards);
|
const p = calc(playerCards);
|
||||||
const d = calc(dealerCards);
|
const d = calc(dealerCards);
|
||||||
let msg = '';
|
let msg = '';
|
||||||
let payout = 0;
|
if(p > 21) msg = 'PLAYER BUST — YOU LOSE';
|
||||||
|
else if(d > 21) msg = 'DEALER BUST — YOU WIN!';
|
||||||
// Logic Payout
|
else if(p === 21 && playerCards.length === 2 && d !== 21) msg = 'BLACKJACK! — YOU WIN! (3:2)';
|
||||||
if(p > 21) {
|
else if(p > d) msg = 'YOU WIN!';
|
||||||
msg = 'PLAYER BUST — YOU LOSE';
|
else if(p < d) msg = 'YOU LOSE!';
|
||||||
} else if(d > 21) {
|
else msg = 'PUSH (DRAW) — TARUHAN KEMBALI';
|
||||||
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;
|
|
||||||
|
|
||||||
showEnd(msg);
|
showEnd(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -661,7 +455,6 @@ function showEnd(msg){
|
|||||||
document.getElementById('status').innerText = msg;
|
document.getElementById('status').innerText = msg;
|
||||||
hitBtn.disabled = true;
|
hitBtn.disabled = true;
|
||||||
standBtn.disabled = true;
|
standBtn.disabled = true;
|
||||||
|
|
||||||
endMessage.innerText = msg;
|
endMessage.innerText = msg;
|
||||||
endScreen.style.display = 'flex';
|
endScreen.style.display = 'flex';
|
||||||
playAgainBtn.focus();
|
playAgainBtn.focus();
|
||||||
@ -682,108 +475,49 @@ function stand(){
|
|||||||
hitBtn.disabled = true;
|
hitBtn.disabled = true;
|
||||||
standBtn.disabled = true;
|
standBtn.disabled = true;
|
||||||
document.getElementById('status').innerText = 'DEALER TURN';
|
document.getElementById('status').innerText = 'DEALER TURN';
|
||||||
|
|
||||||
flipAllDealerHidden();
|
flipAllDealerHidden();
|
||||||
setTimeout(()=> dealerPlay(), 300 + dealerHiddenEls.length*300);
|
setTimeout(()=> dealerPlay(), 300 + dealerHiddenEls.length*300);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fungsi untuk menangani deal dari modal betting */
|
/* ---------- start / restart ---------- */
|
||||||
function handleDeal(){
|
function startGame(){
|
||||||
if(gamePhase !== 'BETTING') return;
|
// 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);
|
// dealing sequence
|
||||||
const minBet = 10;
|
setTimeout(()=> dealPlayer(), 80);
|
||||||
|
setTimeout(()=> dealDealer(false), 280); // dealer first open
|
||||||
|
setTimeout(()=> dealPlayer(), 480);
|
||||||
|
setTimeout(()=> dealDealer(true), 680); // dealer second hidden
|
||||||
|
|
||||||
if(isNaN(bet) || bet < minBet){
|
// after dealing finished, enable buttons and set phase to PLAYING
|
||||||
modalBetError.innerText = `Taruhan minimum adalah $${minBet}.`;
|
setTimeout(()=>{
|
||||||
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';
|
gamePhase = 'PLAYING';
|
||||||
document.getElementById('status').innerText = 'YOUR TURN';
|
document.getElementById('status').innerText = 'YOUR TURN';
|
||||||
|
|
||||||
// Aktifkan HIT/STAND
|
|
||||||
hitBtn.disabled = false;
|
hitBtn.disabled = false;
|
||||||
standBtn.disabled = false;
|
standBtn.disabled = false;
|
||||||
|
// auto-stand on natural
|
||||||
|
if(calc(playerCards) === 21) stand();
|
||||||
|
}, 900);
|
||||||
|
}
|
||||||
|
|
||||||
// Mulai permainan
|
function restart(){
|
||||||
|
// reset and start again
|
||||||
startGame();
|
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 ---------- */
|
/* ---------- events ---------- */
|
||||||
modalDealBtn.addEventListener('click', handleDeal);
|
|
||||||
hitBtn.addEventListener('click', hit);
|
hitBtn.addEventListener('click', hit);
|
||||||
standBtn.addEventListener('click', stand);
|
standBtn.addEventListener('click', stand);
|
||||||
playAgainBtn.addEventListener('click', restart);
|
playAgainBtn.addEventListener('click', restart);
|
||||||
window.addEventListener('resize', ()=>{ layoutOverlap(playerEls, cardsWrapper); layoutOverlap(dealerEls, dealerWrapper); });
|
window.addEventListener('resize', ()=>{ layoutOverlap(playerEls, cardsWrapper); layoutOverlap(dealerEls, dealerWrapper); });
|
||||||
|
|
||||||
/* start - Panggil restart untuk memulai dengan modal betting */
|
/* start immediately */
|
||||||
restart();
|
startGame();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user