2K Arcade Shot Trainer
by StealthFox22328 lines10.6 KB
<!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.