🎮ArcadeLab

2K Arcade Shot Trainer

by StealthFox22
328 lines10.6 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2K Arcade Shot Trainer</title>
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body {
            background-color: #0b0e14;
            color: #fff;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            overflow: hidden;
        }

        /* 2K Broadcast Scoreboard */
        #scoreboard {
            display: flex;
            align-items: center;
            background: rgba(15, 23, 42, 0.9);
            border: 2px solid #334155;
            border-radius: 8px;
            padding: 10px 20px;
            margin-bottom: 20px;
            box-shadow: 0 0 20px rgba(0,0,0,0.8);
        }
        .team {
            display: flex;
            align-items: center;
            gap: 10px;
            font-size: 20px;
            font-weight: bold;
        }
        .score {
            font-size: 28px;
            color: #f59e0b;
            padding: 0 10px;
        }
        .divider {
            color: #64748b;
            margin: 0 15px;
            font-weight: bold;
        }

        /* Game Arena Canvas */
        #gameCanvas {
            background: radial-gradient(circle, #1e293b 0%, #0f172a 100%);
            border: 3px solid #3b82f6;
            border-radius: 12px;
            box-shadow: 0 0 30px rgba(59, 130, 246, 0.3);
        }

        /* UI Controls & Hints */
        #controls {
            margin-top: 15px;
            text-align: center;
        }
        .btn {
            background: #2563eb;
            color: white;
            border: none;
            padding: 12px 24px;
            font-size: 16px;
            font-weight: bold;
            border-radius: 6px;
            cursor: pointer;
            box-shadow: 0 4px 10px rgba(37, 99, 235, 0.4);
            transition: 0.2s;
        }
        .btn:hover { background: #1d4ed8; }
        .instructions {
            margin-top: 10px;
            color: #94a3b8;
            font-size: 14px;
        }
    </style>
</head>
<body>

    <!-- Scoreboard -->
    <div id="scoreboard">
        <div class="team">
            <span>HOME</span>
            <span id="score" class="score">0</span>
        </div>
        <div class="divider">|</div>
        <div class="team">
            <span>SHOTS: <span id="attempts" style="color:#38bdf8;">0</span></span>
        </div>
    </div>

    <!-- Canvas Court -->
    <canvas id="gameCanvas" width="600" height="400"></canvas>

    <!-- Controls -->
    <div id="controls">
        <button id="shootBtn" class="btn">HOLD / RELEASE SPACE TO SHOOT</button>
        <p class="instructions">Press & Hold <b>SPACEBAR</b> or Click the button to load meter. Release in the GREEN zone!</p>
    </div>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');

        // Audio Effects (Synthesized Web Audio API)
        const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        function playSound(freq, duration, type='sine') {
            try {
                const osc = audioCtx.createOscillator();
                const gain = audioCtx.createGain();
                osc.type = type;
                osc.frequency.value = freq;
                osc.connect(gain);
                gain.connect(audioCtx.destination);
                osc.start();
                gain.gain.exponentialRampToValueAtTime(0.00001, audioCtx.currentTime + duration);
                osc.stop(audioCtx.currentTime + duration);
            } catch(e) {}
        }

        // Game State
        let score = 0;
        let attempts = 0;
        let isShooting = false;
        let ballState = 'idle'; // idle, flying, scored, missed
        let feedback = '';
        let feedbackColor = '#fff';

        // Ball & Hoop Positions
        const player = { x: 100, y: 300 };
        const hoop = { x: 500, y: 180, r: 20 };
        const ball = { x: player.x, y: player.y, vx: 0, vy: 0, radius: 12 };

        // 2K Style Shot Meter Logic
        const meter = {
            value: 0, // 0 to 100
            speed: 1.8,
            active: false,
            targetStart: 75,
            targetEnd: 85
        };

        // Key Listeners
        window.addEventListener('keydown', (e) => {
            if (e.code === 'Space' && !e.repeat && ballState === 'idle') {
                startMeter();
            }
        });

        window.addEventListener('keyup', (e) => {
            if (e.code === 'Space' && meter.active) {
                releaseShot();
            }
        });

        document.getElementById('shootBtn').addEventListener('mousedown', () => {
            if (ballState === 'idle') startMeter();
        });
        document.getElementById('shootBtn').addEventListener('mouseup', () => {
            if (meter.active) releaseShot();
        });

        function startMeter() {
            meter.value = 0;
            meter.active = true;
        }

        function releaseShot() {
            meter.active = false;
            attempts++;
            document.getElementById('attempts').innerText = attempts;

            const val = meter.value;
            let successChance = 0;

            if (val >= meter.targetStart && val <= meter.targetEnd) {
                feedback = "GREEN RELEASE! PERFECT!";
                feedbackColor = "#22c55e";
                successChance = 1.0; // 100% make
                playSound(880, 0.3); // High ping
            } else if (val > meter.targetEnd) {
                feedback = "LATE RELEASE";
                feedbackColor = "#ef4444";
                successChance = 0.15;
                playSound(300, 0.2);
            } else if (val >= meter.targetStart - 15) {
                feedback = "SLIGHTLY EARLY";
                feedbackColor = "#f59e0b";
                successChance = 0.50;
                playSound(440, 0.2);
            } else {
                feedback = "VERY EARLY";
                feedbackColor = "#ef4444";
                successChance = 0.05;
                playSound(200, 0.2);
            }

            // Calculate Trajectory
            ballState = 'flying';
            const willMake = Math.random() < successChance;
            
            const targetX = willMake ? hoop.x : hoop.x + (Math.random() > 0.5 ? 40 : -40);
            const targetY = willMake ? hoop.y : hoop.y + 50;

            const time = 40; // frames
            ball.vx = (targetX - ball.x) / time;
            ball.vy = (targetY - ball.y - 0.5 * 0.4 * (time * time)) / time;
            ball.willMake = willMake;
        }

        function update() {
            // Update Shot Meter
            if (meter.active) {
                meter.value += meter.speed;
                if (meter.value >= 100) {
                    meter.value = 100;
                    releaseShot(); // Auto release if overfilled
                }
            }

            // Update Physics
            if (ballState === 'flying') {
                ball.x += ball.vx;
                ball.y += ball.vy;
                ball.vy += 0.4; // Gravity

                // Check goal reached
                if (ball.y >= hoop.y && ball.vy > 0) {
                    if (ball.willMake) {
                        score += 3;
                        document.getElementById('score').innerText = score;
                        playSound(523, 0.1, 'triangle'); // Swish sound
                        ballState = 'scored';
                    } else {
                        playSound(120, 0.2, 'square'); // Clank sound
                        ballState = 'missed';
                    }
                    
                    // Reset after 1.5 seconds
                    setTimeout(resetBall, 1500);
                }
            }
        }

        function resetBall() {
            ball.x = player.x;
            ball.y = player.y;
            ball.vx = 0;
            ball.vy = 0;
            ballState = 'idle';
            feedback = '';
        }

        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // Draw Court Floor Line
            ctx.strokeStyle = '#475569';
            ctx.lineWidth = 4;
            ctx.beginPath();
            ctx.moveTo(0, 340);
            ctx.lineTo(600, 340);
            ctx.stroke();

            // Draw Basketball Backboard & Rim
            ctx.fillStyle = '#cbd5e1';
            ctx.fillRect(520, 100, 8, 100); // Backboard
            
            ctx.strokeStyle = '#f97316'; // Orange Rim
            ctx.lineWidth = 5;
            ctx.beginPath();
            ctx.arc(hoop.x, hoop.y, hoop.r, 0, Math.PI);
            ctx.stroke();

            // Draw Player Silhouette
            ctx.fillStyle = '#38bdf8';
            ctx.beginPath();
            ctx.arc(player.x, player.y - 20, 15, 0, Math.PI * 2); // Head
            ctx.fillRect(player.x - 10, player.y - 5, 20, 40); // Body
            ctx.fill();

            // Draw Basketball
            ctx.fillStyle = '#ea580c';
            ctx.beginPath();
            ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
            ctx.fill();
            ctx.lineWidth = 1;
            ctx.strokeStyle = '#000';
            ctx.stroke();

            // Draw 2K Shot Meter when active
            if (meter.active) {
                const meterX = player.x - 20;
                const meterY = player.y - 70;
                const width = 80;
                const height = 12;

                // Outer Frame
                ctx.fillStyle = '#1e293b';
                ctx.fillRect(meterX, meterY, width, height);

                // Green Target Zone
                const greenStart = meterX + (meter.targetStart / 100) * width;
                const greenWidth = ((meter.targetEnd - meter.targetStart) / 100) * width;
                ctx.fillStyle = '#22c55e';
                ctx.fillRect(greenStart, meterY, greenWidth, height);

                // Filling Bar
                const fillWidth = (meter.value / 100) * width;
                ctx.fillStyle = '#facc15';
                ctx.fillRect(meterX, meterY, fillWidth, height);

                ctx.strokeStyle = '#fff';
                ctx.strokeRect(meterX, meterY, width, height);
            }

            // Draw Shot Feedback Text
            if (feedback !== '') {
                ctx.fillStyle = feedbackColor;
                ctx.font = 'bold 20px Segoe UI';
                ctx.textAlign = 'center';
                ctx.fillText(feedback, player.x + 20, player.y - 90);
            }

Game Source: 2K Arcade Shot Trainer

Creator: StealthFox22

Libraries: none

Complexity: complex (328 lines, 10.6 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: 2k-arcade-shot-trainer-stealthfox22" to link back to the original. Then publish at arcadelab.ai/publish.