CF4VN - Mini Bầu Cua

Mini game "Bầu Cua" dạng vũ khí cho cf4vn.com

// ==UserScript==
// @name         CF4VN - Mini Bầu Cua
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Mini game "Bầu Cua" dạng vũ khí cho cf4vn.com
// @author       ChatGPT
// @match        https://cf4vn.com/*
// @icon         https://cf4vn.com/favicon.ico
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    /* ---------- Cấu hình item (6 loại) ---------- */
    const ITEMS = [
        { id: 'm4', name: 'M4 Commando', img: 'https://cf4vn.com/images/items/ItemIcon_1158.png' },
        { id: 'ak', name: 'AK-47- Scope Red Dragon', img: 'https://cf4vn.com/images/items/ItemIcon_547.png' },
        { id: 'barret', name: 'Barret M99 Ancient Dragon', img: 'https://cf4vn.com/images/items/ItemIcon_1120.png' },
        { id: 'kac', name: 'KAC Chainsaw', img: 'https://cf4vn.com/images/items/ItemIcon_1021.png' },
        { id: 'shovel', name: 'Shovel Red Dragon', img: 'https://cf4vn.com/images/items/ItemIcon_583.png' },
        { id: 'kukri', name: 'Kukri Gold', img: 'https://cf4vn.com/images/items/ItemIcon_753.png' },
    ];

    /* ---------- Storage keys ---------- */
    const STORAGE_BALANCE_KEY = 'cf4vn_minigame_balance_v1';

    /* ---------- Mặc định ---------- */
    const DEFAULT_BALANCE = 999999;

    /* ---------- Utility ---------- */
    function $(sel, root = document) { return root.querySelector(sel); }
    function $all(sel, root = document) { return Array.from(root.querySelectorAll(sel)); }
    function moneyFormat(n) { return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.'); }

    /* ---------- Lấy username từ trang (theo mẫu user-info-bar) ---------- */
    function getUsername() {
        try {
            const el = document.querySelector('.user-info-bar .user-stat.stat-nick .value');
            if (el) return el.textContent.trim();
        } catch (e) { /* ignore */ }
        // fallback
        return 'Khách';
    }

    /* ---------- Balance (persist vào localStorage) ---------- */
    function loadBalance() {
        const v = localStorage.getItem(STORAGE_BALANCE_KEY);
        if (!v) {
            localStorage.setItem(STORAGE_BALANCE_KEY, String(DEFAULT_BALANCE));
            return DEFAULT_BALANCE;
        }
        const n = parseInt(v, 10);
        return isNaN(n) ? DEFAULT_BALANCE : n;
    }
    function saveBalance(n) {
        localStorage.setItem(STORAGE_BALANCE_KEY, String(n));
    }

    /* ---------- Tạo UI ---------- */
    function createUI() {
        // tránh chèn nhiều lần
        if (document.getElementById('cf4vn-minigame-root')) return;

        const root = document.createElement('div');
        root.id = 'cf4vn-minigame-root';
        root.style.cssText = `
            position: fixed;
            right: 12px;
            bottom: 12px;
            width: 360px;
            z-index: 999999;
            font-family: Arial, Helvetica, sans-serif;
            user-select: none;
        `;
        root.innerHTML = `
            <div id="minigame-card" style="background:rgba(0,0,0,0.85); color:#fff; border-radius:12px; padding:12px; box-shadow:0 8px 24px rgba(0,0,0,0.6);">
                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
                    <div style="font-weight:700;">Sàn cược CF4VN</div>
                    <div style="font-size:12px;opacity:0.85;">Huyền Cốt Lão Nhân</div>
                </div>

                <div id="mg-welcome" style="font-size:13px;margin-bottom:8px;"></div>

                <div style="display:flex;gap:8px;align-items:center;margin-bottom:8px;">
                    <label style="font-size:12px;">Mức cược:</label>
                    <select id="mg-bet-select" style="flex:1;padding:6px;border-radius:6px;border:1px solid #333;background:#111;color:#fff;">
                        <option value="100">100</option>
                        <option value="1000">1.000</option>
                        <option value="10000">10.000</option>
                    </select>
                    <button id="mg-reset-bets" style="padding:6px 8px;border-radius:6px;border:none;background:#b02a2a;color:#fff;cursor:pointer;">Reset</button>
                </div>

                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
                    <div style="font-size:12px;">Số dư: <span id="mg-balance" style="font-weight:700;"></span> VCoin</div>
                    <div style="font-size:12px;">Kèo: <span id="mg-total-bet" style="font-weight:700;">0</span> VCoin</div>
                </div>

                <div style="display:flex;gap:8px;margin-bottom:8px;align-items:center;">
                    <div style="flex:1;text-align:center;font-size:12px;color:#ddd;">Kết quả</div>
                </div>

                <div id="mg-results" style="display:flex;justify-content:space-between;gap:8px;margin-bottom:10px;">
                    <div class="mg-res-slot" data-slot="0" style="flex:1;height:68px;border-radius:8px;background:#111;display:flex;align-items:center;justify-content:center;border:1px solid #222;"></div>
                    <div class="mg-res-slot" data-slot="1" style="flex:1;height:68px;border-radius:8px;background:#111;display:flex;align-items:center;justify-content:center;border:1px solid #222;"></div>
                    <div class="mg-res-slot" data-slot="2" style="flex:1;height:68px;border-radius:8px;background:#111;display:flex;align-items:center;justify-content:center;border:1px solid #222;"></div>
                </div>

                <div style="margin-bottom:8px;">
                    <div style="font-size:12px;margin-bottom:6px;">Đặt cược (click để chọn / click lại để huỷ)</div>
                    <div id="mg-bet-grid" style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;"></div>
                </div>

                <div style="display:flex;gap:8px;justify-content:space-between;align-items:center;">
                    <button id="mg-start" style="flex:1;padding:8px;border-radius:8px;background:#1f8a1f;border:none;color:#fff;font-weight:700;cursor:pointer;">Start</button>
                    <div style="width:8px;"></div>
                    <div style="width:110px;text-align:right;">
                        <div style="font-size:12px;color:#bbb;">Lãi/lỗ vòng này</div>
                        <div id="mg-round-result" style="font-weight:700;">0</div>
                    </div>
                </div>

                <div id="mg-log" style="margin-top:8px;font-size:12px;color:#ccc;height:68px;overflow:auto;padding:6px;background:rgba(255,255,255,0.02);border-radius:6px;"></div>
            </div>
        `;
        document.body.appendChild(root);

        // Render bet grid (6 items)
        const grid = document.getElementById('mg-bet-grid');
        ITEMS.forEach(it => {
            const card = document.createElement('div');
            card.className = 'mg-bet-item';
            card.dataset.itemId = it.id;
            card.style.cssText = `
                background:#0f0f0f;
                border-radius:8px;
                padding:8px;
                height:86px;
                display:flex;
                flex-direction:column;
                align-items:center;
                justify-content:center;
                border:1px solid #222;
                cursor:pointer;
                position:relative;
            `;
            card.innerHTML = `
                <img src="${it.img}" alt="${it.name}" title="${it.name}" style="width:52px;height:52px;object-fit:contain;filter:drop-shadow(0 2px 2px rgba(0,0,0,0.6));"/>
                <div style="font-size:11px;margin-top:6px;text-align:center;">${it.name.split(' ').slice(0,2).join(' ')}...</div>
                <div class="mg-bet-overlay" style="position:absolute;left:6px;top:6px;background:rgba(0,0,0,0.6);padding:2px 6px;border-radius:6px;font-size:12px;display:none;">0</div>
            `;
            grid.appendChild(card);
        });

        // initial bindings
        bindUI();
        refreshUI();
    }

    /* ---------- State ---------- */
    let selectedBets = {}; // key: itemId -> betAmount (number)
    let balance = loadBalance();

    /* ---------- UI binding + handlers ---------- */
    function bindUI() {
        const username = getUsername();
        $('#mg-welcome').textContent = `Chào mừng ${username} đã đến với sàn cược CF4VN!`;

        $('#mg-bet-select').addEventListener('change', () => {
            // nothing immediate
        });

        $('#mg-reset-bets').addEventListener('click', () => {
            selectedBets = {};
            updateBetOverlays();
            refreshTotals();
            log('Đã xóa cược.');
        });

        // attach click selection to each bet item
        $all('.mg-bet-item').forEach(el => {
            el.addEventListener('click', () => {
                const id = el.dataset.itemId;
                const betAmt = parseInt($('#mg-bet-select').value, 10);
                if (selectedBets[id]) {
                    // toggle off
                    delete selectedBets[id];
                    log(`Bỏ cược ${id}.`);
                } else {
                    selectedBets[id] = betAmt;
                    log(`Đặt ${moneyFormat(betAmt)} VCoin vào ${id}.`);
                }
                updateBetOverlays();
                refreshTotals();
            });
        });

        $('#mg-start').addEventListener('click', async () => {
            await startRound();
        });
    }

    function updateBetOverlays() {
        $all('.mg-bet-item').forEach(el => {
            const id = el.dataset.itemId;
            const ov = el.querySelector('.mg-bet-overlay');
            if (selectedBets[id]) {
                ov.style.display = 'block';
                ov.textContent = moneyFormat(selectedBets[id]);
                el.style.boxShadow = '0 6px 16px rgba(0,200,100,0.12)';
                el.style.border = '1px solid rgba(0,200,100,0.25)';
            } else {
                ov.style.display = 'none';
                el.style.boxShadow = '';
                el.style.border = '1px solid #222';
            }
        });
    }

    function refreshUI() {
        $('#mg-balance').textContent = moneyFormat(balance);
        $('#mg-round-result').textContent = '0';
        updateBetOverlays();
        refreshTotals();
    }

    function refreshTotals() {
        const total = Object.values(selectedBets).reduce((a,b)=>a+b,0);
        $('#mg-total-bet').textContent = moneyFormat(total);
    }

    function log(text) {
        const el = $('#mg-log');
        const t = new Date().toLocaleTimeString();
        el.innerHTML = `<div>[${t}] ${text}</div>` + el.innerHTML;
    }

    /* ---------- Spin / Round logic ---------- */
    function randomPick() {
        const idx = Math.floor(Math.random() * ITEMS.length);
        return ITEMS[idx];
    }

    function setResultSlot(slotIndex, item) {
        const slot = document.querySelector(`#mg-results .mg-res-slot[data-slot="${slotIndex}"]`);
        slot.innerHTML = `<img src="${item.img}" title="${item.name}" style="width:56px;height:56px;object-fit:contain;"/>`;
    }

    async function startRound() {
        // validate
        const totalBet = Object.values(selectedBets).reduce((a,b)=>a+b,0);
        if (totalBet <= 0) { alert('Hãy chọn ít nhất 1 cược trước khi Start.'); return; }
        if (balance < totalBet) { alert('Số dư không đủ để đặt cược này.'); return; }

        // disable UI while spinning
        $('#mg-start').disabled = true;
        $('#mg-start').style.opacity = '0.6';
        $all('.mg-bet-item').forEach(el => el.style.pointerEvents = 'none');
        $('#mg-reset-bets').disabled = true;

        // Deduct bets at start
        balance -= totalBet;
        saveBalance(balance);
        refreshUI();
        log(`Bắt đầu vòng - tổng cược ${moneyFormat(totalBet)} VCoin đã bị trừ.`);

        // Animation: nhanh -> chậm -> final
        const slots = [0,1,2];
        const durations = [2200, 2400, 2600]; // ms when final stops (slightly stagger)
        const intervals = [];

        // for each slot, cycle images quickly then stop at random
        const finalResults = [];
        for (let i = 0; i < slots.length; i++) {
            const slot = slots[i];
            finalResults[slot] = randomPick(); // decide final now (fair)
        }

        // Start intervals
        const startTime = Date.now();
        const spinIntervals = [];
        slots.forEach((slot, i) => {
            // initial fast interval
            const iv = setInterval(() => {
                const tmp = randomPick();
                setResultSlot(slot, tmp);
            }, 80 + Math.floor(Math.random()*40));
            spinIntervals.push(iv);

            // schedule stop
            setTimeout(() => {
                // clear interval and set final
                clearInterval(iv);
                setResultSlot(slot, finalResults[slot]);
            }, durations[i]);
        });

        // wait until last duration done
        await new Promise(res => setTimeout(res, Math.max(...durations) + 80));

        // Calculate payout
        let totalWon = 0;
        const counts = {}; // count occurrence of each item id in finalResults
        finalResults.forEach(it => { counts[it.id] = (counts[it.id] || 0) + 1; });

        // For each bet placed, if it appears k times, win = betAmount * 2 * k
        Object.entries(selectedBets).forEach(([id, betAmt]) => {
            const k = counts[id] || 0;
            if (k > 0) {
                const win = betAmt * 2 * k; // multiplier 2 per match
                totalWon += win;
            }
        });

        // Apply winnings to balance
        const profit = totalWon - totalBet; // net change compared to before round
        balance += totalWon;
        saveBalance(balance);

        // Update UI and logs
        refreshUI();
        $('#mg-round-result').textContent = (profit >= 0 ? `+${moneyFormat(profit)}` : `${moneyFormat(profit)}`);
        $('#mg-round-result').style.color = profit >= 0 ? '#b7ffb7' : '#ffb7b7';

        // friendly message showing final results names
        const names = finalResults.map(x => x.name).join(' | ');
        log(`Kết quả: ${names}`);
        if (totalWon > 0) {
            log(`Bạn thắng ${moneyFormat(totalWon)} VCoin (lợi nhuận ${profit >= 0 ? '+' : ''}${moneyFormat(profit)})`);
        } else {
            log(`Bạn thua vòng này: mất ${moneyFormat(totalBet)} VCoin`);
        }

        // re-enable UI
        $('#mg-start').disabled = false;
        $('#mg-start').style.opacity = '1';
        $all('.mg-bet-item').forEach(el => el.style.pointerEvents = 'auto');
        $('#mg-reset-bets').disabled = false;
    }

    /* ---------- Kick off ---------- */
    function init() {
        createUI();
        // initial results show random images
        for (let i = 0; i < 3; i++) setResultSlot(i, randomPick());
        refreshUI();
        log('Mini-game sẵn sàng. Chọn kèo và bấm Start!');
    }

    // run after slight delay (chờ DOM)
    setTimeout(init, 800);

})();