401 lines
10 KiB
JavaScript
401 lines
10 KiB
JavaScript
|
||
// =========================
|
||
// SET MESSAGE
|
||
// =========================
|
||
function setMessage(text, status) {
|
||
if (messageEl) {
|
||
messageEl.textContent = text;
|
||
messageEl.className = status;
|
||
}
|
||
}
|
||
|
||
// =========================
|
||
// KONSTANTA KARTU
|
||
// =========================
|
||
const SUITS = ['♠', '♥', '♦', '♣'];
|
||
const RANKS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
|
||
|
||
// =========================
|
||
// DOM ELEMENTS
|
||
// =========================
|
||
const dealerHandEl = document.getElementById('dealer-hand');
|
||
const playerHandEl = document.getElementById('player-hand');
|
||
const dealerValueEl = document.getElementById('dealer-value');
|
||
const playerValueEl = document.getElementById('player-value');
|
||
const balanceEl = document.getElementById('balance');
|
||
const betInput = document.getElementById('bet-input');
|
||
const betBtn = document.getElementById('bet-btn');
|
||
const currentBetEl = document.getElementById('current-bet');
|
||
const hitBtn = document.getElementById('hit');
|
||
const standBtn = document.getElementById('stand');
|
||
const doubleBtn = document.getElementById('double');
|
||
const newRoundBtn = document.getElementById('new-round');
|
||
const messageEl = document.getElementById('message');
|
||
|
||
// TOPUP DOM
|
||
const topupBtn = document.getElementById('topup-btn');
|
||
const topupModal = document.getElementById('topup-modal');
|
||
const topupForm = document.getElementById('topup-form');
|
||
const topupMessage = document.getElementById('topup-message');
|
||
const modalAmount = document.getElementById('modal-amount');
|
||
|
||
// =========================
|
||
// VARIABEL GAME
|
||
// =========================
|
||
let deck = [];
|
||
let dealer = [];
|
||
let player = [];
|
||
let dealerHidden = true;
|
||
let balance = 0;
|
||
let currentBet = 0;
|
||
let inRound = false;
|
||
|
||
// =========================================
|
||
// AMBIL VALUE DARI PHP (HIDDEN INPUT)
|
||
// =========================================
|
||
let gameBalance = parseInt(document.getElementById('php-balance').value);
|
||
let userId = parseInt(document.getElementById('php-userid').value);
|
||
|
||
// =========================
|
||
// UPDATE BALANCE DISPLAY
|
||
// =========================
|
||
function updateBalanceDisplay() {
|
||
if (balanceEl) balanceEl.textContent = gameBalance.toLocaleString('id-ID');
|
||
const modalBalance = document.getElementById('modal-balance');
|
||
if (modalBalance) modalBalance.textContent = gameBalance.toLocaleString('id-ID');
|
||
}
|
||
|
||
// =========================
|
||
// SYNC KE SERVER
|
||
// =========================
|
||
async function syncBalanceToServer() {
|
||
try {
|
||
const response = await fetch('update_balance.php', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ userId, balance: gameBalance })
|
||
});
|
||
|
||
const data = await response.json();
|
||
return data.success;
|
||
} catch (err) {
|
||
console.error("Error:", err);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// =========================
|
||
// UPDATE SESSION PHP
|
||
// =========================
|
||
async function updateSessionBalance() {
|
||
try {
|
||
await fetch('update_session.php', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ balance: gameBalance })
|
||
});
|
||
} catch (err) {
|
||
console.error("Error:", err);
|
||
}
|
||
}
|
||
|
||
// =========================
|
||
// UPDATE BALANCE LOGIC
|
||
// =========================
|
||
function updateGameBalance(amount, type) {
|
||
if (type === 'win') gameBalance += amount;
|
||
else if (type === 'loss') gameBalance -= amount;
|
||
else if (type === 'bet') gameBalance -= amount;
|
||
else if (type === 'refund') gameBalance += amount;
|
||
|
||
updateBalanceDisplay();
|
||
syncBalanceToServer();
|
||
updateSessionBalance();
|
||
}
|
||
|
||
window.updateGameBalance = updateGameBalance;
|
||
window.gameBalance = gameBalance;
|
||
window.userId = userId;
|
||
|
||
// =========================
|
||
// FUNGSI KARTU
|
||
// =========================
|
||
function makeDeck() {
|
||
deck = [];
|
||
for (const s of SUITS) {
|
||
for (const r of RANKS) deck.push({ suit: s, rank: r });
|
||
}
|
||
}
|
||
|
||
function shuffle() {
|
||
for (let i = deck.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[deck[i], deck[j]] = [deck[j], deck[i]];
|
||
}
|
||
}
|
||
|
||
function cardValue(card) {
|
||
if (card.rank === 'A') return [1, 11];
|
||
if (['J', 'Q', 'K'].includes(card.rank)) return [10];
|
||
return [parseInt(card.rank)];
|
||
}
|
||
|
||
function handValues(hand) {
|
||
let totals = [0];
|
||
|
||
for (const card of hand) {
|
||
const cv = cardValue(card);
|
||
const newTotals = [];
|
||
|
||
totals.forEach(t => cv.forEach(v => newTotals.push(t + v)));
|
||
|
||
totals = [...new Set(newTotals)];
|
||
}
|
||
|
||
const valid = totals.filter(t => t <= 21);
|
||
return valid.length ? Math.max(...valid) : Math.min(...totals);
|
||
}
|
||
|
||
function renderHand(el, hand, hideFirst = false) {
|
||
el.innerHTML = '';
|
||
|
||
hand.forEach((c, i) => {
|
||
const div = document.createElement('div');
|
||
|
||
if (hideFirst && i === 0) {
|
||
div.className = 'card back';
|
||
div.textContent = 'HIDE';
|
||
} else {
|
||
div.className = 'card' + ((c.suit === '♥' || c.suit === '♦') ? ' red' : '');
|
||
const top = document.createElement('div');
|
||
const bot = document.createElement('div');
|
||
|
||
top.textContent = c.rank + ' ' + c.suit;
|
||
bot.textContent = c.rank + ' ' + c.suit;
|
||
bot.style.alignSelf = 'flex-end';
|
||
|
||
div.appendChild(top);
|
||
div.appendChild(bot);
|
||
}
|
||
|
||
el.appendChild(div);
|
||
});
|
||
}
|
||
|
||
// =========================
|
||
// UPDATE UI
|
||
// =========================
|
||
function updateUI() {
|
||
renderHand(dealerHandEl, dealer, dealerHidden);
|
||
renderHand(playerHandEl, player);
|
||
|
||
dealerValueEl.textContent = dealerHidden ? '??' : 'Nilai: ' + handValues(dealer);
|
||
playerValueEl.textContent = 'Nilai: ' + handValues(player);
|
||
updateBalanceDisplay();
|
||
|
||
if (currentBetEl) currentBetEl.textContent = currentBet.toLocaleString('id-ID');
|
||
}
|
||
|
||
// =========================
|
||
// START ROUND
|
||
// =========================
|
||
function startRound() {
|
||
if (inRound) return;
|
||
|
||
const bet = Number(betInput.value);
|
||
if (bet <= 0) return alert("Masukkan jumlah taruhan!");
|
||
if (bet > gameBalance) return alert("BANK TIDAK CUKUP!");
|
||
|
||
currentBet = bet;
|
||
updateGameBalance(bet, 'bet');
|
||
inRound = true;
|
||
dealerHidden = true;
|
||
setMessage("", "");
|
||
|
||
makeDeck();
|
||
shuffle();
|
||
|
||
dealer = [deck.pop(), deck.pop()];
|
||
player = [deck.pop(), deck.pop()];
|
||
updateUI();
|
||
|
||
// NATURAL BLACKJACK
|
||
if (handValues(player) === 21) {
|
||
dealerHidden = false;
|
||
updateUI();
|
||
|
||
if (handValues(dealer) === 21) {
|
||
updateGameBalance(currentBet, 'refund');
|
||
setMessage("Tie!", "");
|
||
} else {
|
||
updateGameBalance(Math.floor(currentBet * 2.5), 'win');
|
||
setMessage("Blackjack! You win!", "win");
|
||
}
|
||
|
||
inRound = false;
|
||
currentBet = 0;
|
||
}
|
||
}
|
||
|
||
// =========================
|
||
// HIT
|
||
// =========================
|
||
function playerHit() {
|
||
if (!inRound) return;
|
||
|
||
player.push(deck.pop());
|
||
updateUI();
|
||
|
||
if (handValues(player) > 21) {
|
||
dealerHidden = false;
|
||
setMessage("Bust! You Lose!", "lose");
|
||
inRound = false;
|
||
currentBet = 0;
|
||
}
|
||
}
|
||
|
||
// =========================
|
||
// STAND
|
||
// =========================
|
||
function playerStand() {
|
||
if (!inRound) return;
|
||
|
||
dealerHidden = false;
|
||
|
||
while (handValues(dealer) < 17) dealer.push(deck.pop());
|
||
|
||
updateUI();
|
||
|
||
const pv = handValues(player);
|
||
const dv = handValues(dealer);
|
||
|
||
if (dv > 21 || pv > dv) {
|
||
updateGameBalance(currentBet * 2, 'win');
|
||
setMessage("You Win!", "win");
|
||
} else if (pv === dv) {
|
||
updateGameBalance(currentBet, 'refund');
|
||
setMessage("Tie!", "");
|
||
} else {
|
||
setMessage("You Lose!", "lose");
|
||
}
|
||
|
||
inRound = false;
|
||
currentBet = 0;
|
||
}
|
||
|
||
// =========================
|
||
// DOUBLE
|
||
// =========================
|
||
function playerDouble() {
|
||
if (!inRound) return;
|
||
if (gameBalance < currentBet) return alert("Bank tidak cukup untuk double.");
|
||
|
||
updateGameBalance(currentBet, 'bet');
|
||
currentBet *= 2;
|
||
|
||
player.push(deck.pop());
|
||
updateUI();
|
||
|
||
if (handValues(player) > 21) {
|
||
dealerHidden = false;
|
||
setMessage("Bust! You Lose!", "lose");
|
||
inRound = false;
|
||
currentBet = 0;
|
||
return;
|
||
}
|
||
|
||
playerStand();
|
||
}
|
||
|
||
// =========================
|
||
// TOP UP MODAL
|
||
// =========================
|
||
topupBtn.addEventListener('click', () => {
|
||
topupModal.style.display = 'flex';
|
||
topupForm.reset();
|
||
topupMessage.style.display = 'none';
|
||
updateBalanceDisplay();
|
||
});
|
||
|
||
function closeTopUpModal() {
|
||
topupModal.style.display = 'none';
|
||
topupMessage.style.display = 'none';
|
||
}
|
||
|
||
window.addEventListener('click', e => {
|
||
if (e.target === topupModal) closeTopUpModal();
|
||
});
|
||
|
||
function setModalAmount(amount) {
|
||
modalAmount.value = amount;
|
||
}
|
||
|
||
function showTopUpMessage(msg, type) {
|
||
topupMessage.textContent = msg;
|
||
topupMessage.className = 'topup-message ' + type;
|
||
topupMessage.style.display = 'block';
|
||
}
|
||
|
||
topupForm.addEventListener('submit', e => {
|
||
e.preventDefault();
|
||
|
||
const bankMethod = document.querySelector('input[name="bank_method"]:checked');
|
||
const amount = parseInt(modalAmount.value);
|
||
|
||
if (!bankMethod) return showTopUpMessage("Pilih metode pembayaran!", "error");
|
||
if (amount <= 0 || amount > 1000000) return showTopUpMessage("Jumlah harus 1–1.000.000", "error");
|
||
|
||
const formData = new FormData(topupForm);
|
||
formData.append('userId', userId);
|
||
|
||
fetch('process_topup.php', { method: 'POST', body: formData })
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
gameBalance = data.new_balance;
|
||
updateBalanceDisplay();
|
||
showTopUpMessage("✓ Top up berhasil!", "success");
|
||
setTimeout(closeTopUpModal, 2000);
|
||
} else {
|
||
showTopUpMessage(data.message, "error");
|
||
}
|
||
})
|
||
.catch(() => showTopUpMessage("Terjadi kesalahan.", "error"));
|
||
});
|
||
|
||
// =========================
|
||
// EVENT LISTENERS
|
||
// =========================
|
||
betBtn.addEventListener('click', startRound);
|
||
hitBtn.addEventListener('click', playerHit);
|
||
standBtn.addEventListener('click', playerStand);
|
||
doubleBtn.addEventListener('click', playerDouble);
|
||
|
||
newRoundBtn.addEventListener('click', () => {
|
||
if (inRound && !confirm("Masih dalam ronde. Reset?")) return;
|
||
|
||
dealer = [];
|
||
player = [];
|
||
deck = [];
|
||
inRound = false;
|
||
dealerHidden = true;
|
||
currentBet = 0;
|
||
|
||
setMessage("Game di-reset.", "");
|
||
updateUI();
|
||
});
|
||
|
||
window.addEventListener('keydown', e => {
|
||
if (e.key === 'h') playerHit();
|
||
if (e.key === 's') playerStand();
|
||
if (e.key === 'd') playerDouble();
|
||
if (e.key === 'Enter') startRound();
|
||
});
|
||
|
||
// =========================
|
||
// INIT
|
||
// =========================
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
updateBalanceDisplay();
|
||
});
|