document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const startScreen = document.getElementById('startScreen');
const gameOverScreen = document.getElementById('gameOverScreen');
const startBtn = document.getElementById('startBtn');
const restartBtn = document.getElementById('restartBtn');
const scoreDisplay = document.getElementById('scoreDisplay');
// Set canvas size
function resizeCanvas() {
const container = canvas.parentElement;
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Game variables
let ball = {
x: 0,
y: 0,
radius: 110,
minRadius: 5,
maxRadius: 200,
color: '#8B5CF6',
speed: 3,
dx: 0,
dy: 0 // Start with no movement
};
let rings = [];
let currentRing = 0;
let gameActive = false;
let animationId;
let touchStartX = 0;
let touchStartY = 0;
// Initialize game
function initGame() {
ball.x = canvas.width / 2;
ball.y = canvas.height - 100;
ball.dx = 0;
ball.dy = 0; // Start with no movement (let player control)
rings = [];
currentRing = 0;
// Create 100 rings with random gaps
for (let i = 0; i < 100; i++) {
rings.push({
x: canvas.width / 2,
y: 50 + i * 60,
radius: 40 + i * 2,
thickness: 8,
gapStart: Math.random() * Math.PI * 2,
gapSize: Math.PI / 3 + Math.random() * Math.PI / 6,
color: i % 2 === 0 ? '#7C3AED' : '#6D28D9'
});
}
gameActive = true;
startScreen.classList.add('hidden');
gameOverScreen.classList.add('hidden');
animate();
}
// Draw ball
function drawBall() {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath();
// Add shine effect
ctx.beginPath();
ctx.arc(ball.x - ball.radius/3, ball.y - ball.radius/3, ball.radius/4, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
ctx.fill();
ctx.closePath();
// Draw size counter
ctx.font = `${Math.min(20, ball.radius/2)}px Arial`;
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.fillText(Math.round(ball.radius), ball.x, ball.y + 5);
}
// Draw rings
function drawRings() {
for (let i = currentRing; i < Math.min(currentRing + 10, rings.length); i++) {
const ring = rings[i];
// Draw the ring with gap
ctx.beginPath();
ctx.arc(ring.x, ring.y, ring.radius, ring.gapStart + ring.gapSize, ring.gapStart, true);
ctx.lineWidth = ring.thickness;
ctx.strokeStyle = ring.color;
ctx.stroke();
ctx.closePath();
// Add glow effect
ctx.beginPath();
ctx.arc(ring.x, ring.y, ring.radius + 5, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(167, 139, 250, 0.3)';
ctx.lineWidth = 2;
ctx.stroke();
ctx.closePath();
}
}
// Check collision with current ring
function checkCollision() {
const ring = rings[currentRing];
const distance = Math.sqrt((ball.x - ring.x) ** 2 + (ball.y - ring.y) ** 2);
// Check if ball is within ring boundaries
if (distance > ring.radius - ring.thickness/2 && distance < ring.radius + ring.thickness/2) {
// Calculate angle to check if ball is passing through the gap
const angle = Math.atan2(ball.y - ring.y, ball.x - ring.x) + Math.PI;
const normalizedAngle = (angle - ring.gapStart + Math.PI * 2) % (Math.PI * 2);
if (normalizedAngle > ring.gapSize) {
return true; // Collision detected
}
} else if (distance < ring.radius - ring.thickness/2) {
return true; // Collision with inner part
}
return false;
}
// Update game state
function update() {
// Move ball (auto movement only)
ball.x += ball.dx;
ball.y += ball.dy;
// Wall collision - change size
if (ball.x - ball.radius < 0 || ball.x + ball.radius > canvas.width) {
ball.dx = -ball.dx;
ball.radius = Math.max(ball.minRadius, Math.min(ball.maxRadius, ball.radius + (ball.dx > 0 ? -5 : 5)));
}
if (ball.y - ball.radius < 0 || ball.y + ball.radius > canvas.height) {
ball.dy = -ball.dy;
ball.radius = Math.max(ball.minRadius, Math.min(ball.maxRadius, ball.radius + (ball.dy > 0 ? -5 : 5)));
}
// Check if ball passed current ring
if (currentRing < rings.length) {
const ring = rings[currentRing];
if (ball.y < ring.y - ring.radius - ring.thickness/2) {
currentRing++;
// Check if game is won
if (currentRing >= rings.length) {
gameWon();
return;
}
}
// Check collision with current ring
if (checkCollision()) {
gameOver();
return;
}
}
}
// Game over
function gameOver() {
gameActive = false;
cancelAnimationFrame(animationId);
scoreDisplay.innerHTML = `You reached ring: ${currentRing}`;
gameOverScreen.classList.remove('hidden');
}
// Game won
function gameWon() {
gameActive = false;
cancelAnimationFrame(animationId);
scoreDisplay.innerHTML = `Congratulations! You completed all 100 rings!`;
gameOverScreen.classList.remove('hidden');
}
// Animation loop
function animate() {
animationId = requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRings();
drawBall();
if (gameActive) {
update();
}
}
// Event listeners
startBtn.addEventListener('click', () => {
initGame();
// Enable controls after game starts
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
canvas.addEventListener('touchstart', handleTouchStart);
canvas.addEventListener('touchmove', handleTouchMove);
});
restartBtn.addEventListener('click', () => {
initGame();
// Re-enable controls on restart
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
canvas.addEventListener('touchstart', handleTouchStart);
canvas.addEventListener('touchmove', handleTouchMove);
});
// Control handlers
function handleKeyDown(e) {
if (!gameActive) return;
if (e.key === 'ArrowLeft') {
ball.dx = -ball.speed;
} else if (e.key === 'ArrowRight') {
ball.dx = ball.speed;
} else if (e.key === 'ArrowUp') {
ball.dy = -ball.speed;
} else if (e.key === 'ArrowDown') {
ball.dy = ball.speed;
}
}
function handleKeyUp(e) {
if (!gameActive) return;
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
ball.dx = 0;
} else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
ball.dy = 0;
}
}
function handleTouchStart(e) {
if (!gameActive) return;
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}
function handleTouchMove(e) {
if (!gameActive) return;
e.preventDefault();
const touchX = e.touches[0].clientX;
const touchY = e.touches[0].clientY;
const diffX = touchX - touchStartX;
const diffY = touchY - touchStartY;
if (Math.abs(diffX) > 5) { // Threshold to prevent accidental movement
ball.dx = diffX > 0 ? ball.speed : -ball.speed;
}
if (Math.abs(diffY) > 5) {
ball.dy = diffY > 0 ? ball.speed : -ball.speed;
}
}
});