From 5efe95ac227c6264e8915700d817afe755cb9589 Mon Sep 17 00:00:00 2001 From: Learnerbypassion Date: Wed, 4 Feb 2026 12:55:38 +0530 Subject: [PATCH 1/2] chore: commit changes (auto) --- src/plays/soham-bhattacharya/Readme.md | 27 + .../soham-bhattacharya/SohamBhattacharya.js | 1227 +++++++++++++++++ src/plays/soham-bhattacharya/styles.css | 190 +++ 3 files changed, 1444 insertions(+) create mode 100644 src/plays/soham-bhattacharya/Readme.md create mode 100644 src/plays/soham-bhattacharya/SohamBhattacharya.js create mode 100644 src/plays/soham-bhattacharya/styles.css diff --git a/src/plays/soham-bhattacharya/Readme.md b/src/plays/soham-bhattacharya/Readme.md new file mode 100644 index 000000000..6818fbed1 --- /dev/null +++ b/src/plays/soham-bhattacharya/Readme.md @@ -0,0 +1,27 @@ +# Soham Bhattacharya + +Number Guessing Game + +## Play Demographic + +- Language: js +- Level: Intermediate + +## Creator Information + +- User: Learnerbypassion +- Gihub Link: https://github.com/Learnerbypassion +- Blog: +- Video: + +## Implementation Details + +Update your implementation idea and details here + +## Consideration + +Update all considerations(if any) + +## Resources + +Update external resources(if any) diff --git a/src/plays/soham-bhattacharya/SohamBhattacharya.js b/src/plays/soham-bhattacharya/SohamBhattacharya.js new file mode 100644 index 000000000..ee97c82e8 --- /dev/null +++ b/src/plays/soham-bhattacharya/SohamBhattacharya.js @@ -0,0 +1,1227 @@ +import PlayHeader from 'common/playlists/PlayHeader'; +import './styles.css'; +import { useState, useEffect, useRef, useCallback } from 'react'; + +// WARNING: Do not change the entry component name +function SohamBhattacharya(props) { + // Your Code Start below. + + // Game state using React hooks + const [gameState, setGameState] = useState({ + score: 0, + highScore: parseInt(localStorage.getItem('dinoHighScore')) || 0, + isPlaying: false, + isGameOver: false, + isPaused: false, + speed: 5, + isJumping: false, + obstacles: [], + clouds: [], + gameSpeed: 1, + difficulty: 'normal', + powerUps: [], + combo: 0, + distance: 0, + dinoState: 'running' + }); + + const canvasRef = useRef(null); + const gameLoopRef = useRef(null); + const dinoYRef = useRef(0); + const jumpVelocityRef = useRef(0); + const lastTimeRef = useRef(0); + const isJumpingRef = useRef(false); + const obstaclesRef = useRef([]); + const cloudsRef = useRef([]); + const powerUpsRef = useRef([]); + const groundLevelRef = useRef(0); + const lastObstacleTimeRef = useRef(0); + const lastCloudTimeRef = useRef(0); + const lastPowerUpTimeRef = useRef(0); + + // Constants - Adjusted for better gameplay + const GRAVITY = 0.5; + const JUMP_FORCE = -15; + const DINO_WIDTH = 60; + const DINO_HEIGHT = 70; + const GROUND_HEIGHT = 20; + const DINO_X = 50; + + // Spawn intervals (in milliseconds) + const OBSTACLE_SPAWN_INTERVAL = 1800; // 1.8 seconds between obstacles + const CLOUD_SPAWN_INTERVAL = 4000; // 4 seconds between clouds + const POWER_UP_SPAWN_INTERVAL = 12000; // 12 seconds between power-ups + + // Power-up types + const POWER_UP_TYPES = { + SPEED_BOOST: { color: '#fbbf24', duration: 5000, effect: '2x Speed' }, + SLOW_TIME: { color: '#60a5fa', duration: 3000, effect: 'Slow Time' }, + INVINCIBLE: { color: '#dc2626', duration: 4000, effect: 'Invincible' }, + DOUBLE_POINTS: { color: '#10b981', duration: 6000, effect: '2x Points' } + }; + + // RoundRect polyfill for canvas + useEffect(() => { + if (typeof window !== 'undefined' && window.CanvasRenderingContext2D && !CanvasRenderingContext2D.prototype.roundRect) { + CanvasRenderingContext2D.prototype.roundRect = function(x, y, width, height, radius) { + if (width < 2 * radius) radius = width / 2; + if (height < 2 * radius) radius = height / 2; + this.beginPath(); + this.moveTo(x + radius, y); + this.arcTo(x + width, y, x + width, y + height, radius); + this.arcTo(x + width, y + height, x, y + height, radius); + this.arcTo(x, y + height, x, y, radius); + this.arcTo(x, y, x + width, y, radius); + this.closePath(); + return this; + }; + } + }, []); + + // Initialize canvas + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + canvas.width = canvas.offsetWidth; + canvas.height = canvas.offsetHeight; + + // Set initial dino position + groundLevelRef.current = canvas.height - GROUND_HEIGHT - DINO_HEIGHT; + dinoYRef.current = groundLevelRef.current; + + // Generate initial clouds + generateInitialClouds(canvas); + }, []); + + // Update refs when state changes + useEffect(() => { + obstaclesRef.current = gameState.obstacles; + cloudsRef.current = gameState.clouds; + powerUpsRef.current = gameState.powerUps; + isJumpingRef.current = gameState.isJumping; + }, [gameState.obstacles, gameState.clouds, gameState.powerUps, gameState.isJumping]); + + // Game loop effect with controlled spawning + useEffect(() => { + if (!gameState.isPlaying || gameState.isPaused) { + if (gameLoopRef.current) { + cancelAnimationFrame(gameLoopRef.current); + } + return; + } + + const gameLoop = (currentTime) => { + const deltaTime = currentTime - lastTimeRef.current; + lastTimeRef.current = currentTime; + + const canvas = canvasRef.current; + const ctx = canvas?.getContext('2d'); + if (!ctx || !canvas) { + gameLoopRef.current = requestAnimationFrame(gameLoop); + return; + } + + // Update ground level + groundLevelRef.current = canvas.height - GROUND_HEIGHT - DINO_HEIGHT; + + // Clear canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Draw background + drawSky(ctx, canvas); + drawClouds(ctx); + updateObstacles(ctx); + + // Update dino position + if (isJumpingRef.current) { + jumpVelocityRef.current += GRAVITY; + dinoYRef.current += jumpVelocityRef.current; + + // Check if landed + if (dinoYRef.current >= groundLevelRef.current) { + dinoYRef.current = groundLevelRef.current; + isJumpingRef.current = false; + jumpVelocityRef.current = 0; + setGameState(prev => ({ ...prev, isJumping: false })); + } + } + + drawDino(ctx, canvas); + updatePowerUps(ctx); + drawGround(ctx, canvas); + + // Update game stats + updateGameStats(deltaTime); + + // Check collisions + checkCollisions(); + + // Generate new elements with controlled timing + const currentTimeMs = Date.now(); + + // Generate obstacles at controlled intervals + if (currentTimeMs - lastObstacleTimeRef.current > OBSTACLE_SPAWN_INTERVAL / gameState.gameSpeed) { + generateObstacle(canvas); + lastObstacleTimeRef.current = currentTimeMs; + } + + // Generate clouds at controlled intervals + if (currentTimeMs - lastCloudTimeRef.current > CLOUD_SPAWN_INTERVAL) { + generateCloud(canvas); + lastCloudTimeRef.current = currentTimeMs; + } + + // Generate power-ups at controlled intervals + if (currentTimeMs - lastPowerUpTimeRef.current > POWER_UP_SPAWN_INTERVAL) { + generatePowerUp(canvas); + lastPowerUpTimeRef.current = currentTimeMs; + } + + // Cleanup + cleanupObjects(canvas); + + gameLoopRef.current = requestAnimationFrame(gameLoop); + }; + + lastTimeRef.current = performance.now(); + gameLoopRef.current = requestAnimationFrame(gameLoop); + + return () => { + if (gameLoopRef.current) { + cancelAnimationFrame(gameLoopRef.current); + } + }; + }, [gameState.isPlaying, gameState.isPaused, gameState.gameSpeed]); + + // Event listeners + useEffect(() => { + const handleKeyDown = (e) => { + if (!gameState.isPlaying || gameState.isPaused) return; + + switch(e.code) { + case 'Space': + case 'ArrowUp': + e.preventDefault(); + handleJump(); + break; + case 'ArrowDown': + e.preventDefault(); + handleDuck(); + break; + case 'KeyP': + e.preventDefault(); + togglePause(); + break; + } + }; + + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [gameState.isPlaying, gameState.isPaused]); + + // Drawing functions + const drawSky = (ctx, canvas) => { + // Sky gradient + const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); + gradient.addColorStop(0, '#87CEEB'); + gradient.addColorStop(0.7, '#B0E2FF'); + gradient.addColorStop(1, '#E0F7FF'); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Draw sun + ctx.fillStyle = '#FFD700'; + ctx.beginPath(); + ctx.arc(canvas.width - 80, 80, 40, 0, Math.PI * 2); + ctx.fill(); + + // Sun glow + ctx.shadowColor = '#FFD700'; + ctx.shadowBlur = 30; + ctx.fill(); + ctx.shadowBlur = 0; + + // Sun rays + ctx.strokeStyle = '#FFEC8B'; + ctx.lineWidth = 3; + for (let i = 0; i < 12; i++) { + const angle = (i * Math.PI) / 6; + const startX = canvas.width - 80 + Math.cos(angle) * 45; + const startY = 80 + Math.sin(angle) * 45; + const endX = canvas.width - 80 + Math.cos(angle) * 60; + const endY = 80 + Math.sin(angle) * 60; + + ctx.beginPath(); + ctx.moveTo(startX, startY); + ctx.lineTo(endX, endY); + ctx.stroke(); + } + }; + + const drawGround = (ctx, canvas) => { + // Ground base + const groundGradient = ctx.createLinearGradient(0, canvas.height - GROUND_HEIGHT, 0, canvas.height); + groundGradient.addColorStop(0, '#8B7355'); + groundGradient.addColorStop(1, '#6B5B45'); + ctx.fillStyle = groundGradient; + ctx.fillRect(0, canvas.height - GROUND_HEIGHT, canvas.width, GROUND_HEIGHT); + + // Ground texture with moving effect + const offset = (Date.now() / 50) % 20; + ctx.fillStyle = '#A0522D'; + + for (let i = 0; i < canvas.width + 20; i += 20) { + // Create grass/ground texture + ctx.beginPath(); + ctx.moveTo(i - offset, canvas.height - GROUND_HEIGHT); + ctx.lineTo(i - offset + 5, canvas.height - GROUND_HEIGHT - 3); + ctx.lineTo(i - offset + 10, canvas.height - GROUND_HEIGHT); + ctx.lineTo(i - offset + 15, canvas.height - GROUND_HEIGHT - 2); + ctx.lineTo(i - offset + 20, canvas.height - GROUND_HEIGHT); + ctx.fill(); + + // Small pebbles + ctx.fillStyle = '#7A4A2E'; + for (let j = 0; j < 3; j++) { + const pebbleX = i - offset + 5 + j * 5; + const pebbleY = canvas.height - GROUND_HEIGHT + 8; + ctx.beginPath(); + ctx.arc(pebbleX, pebbleY, 2, 0, Math.PI * 2); + ctx.fill(); + } + ctx.fillStyle = '#A0522D'; + } + }; + + const drawDino = (ctx, canvas) => { + const currentDinoY = dinoYRef.current; + const isDucking = gameState.dinoState === 'ducking'; + const currentHeight = isDucking ? DINO_HEIGHT * 0.7 : DINO_HEIGHT; + const legTime = Date.now() / 100; + + // Breathing effect + const breatheFactor = 1 + Math.sin(Date.now() / 1000) * 0.02; + const currentWidth = DINO_WIDTH * breatheFactor; + const adjustedX = DINO_X - (currentWidth - DINO_WIDTH) / 2; + + // Enhanced Dino body with gradient + const bodyGradient = ctx.createLinearGradient( + adjustedX, currentDinoY, + adjustedX + currentWidth, currentDinoY + currentHeight + ); + bodyGradient.addColorStop(0, '#4CAF50'); + bodyGradient.addColorStop(0.5, '#8BC34A'); + bodyGradient.addColorStop(1, '#2E7D32'); + + ctx.fillStyle = bodyGradient; + + // Draw dino body with rounded corners + if (ctx.roundRect) { + ctx.roundRect(adjustedX, currentDinoY, currentWidth, currentHeight, 10); + } else { + // Fallback for browsers that don't support roundRect + ctx.fillRect(adjustedX, currentDinoY, currentWidth, currentHeight); + } + ctx.fill(); + + // Add body texture + ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; + for (let i = 0; i < 3; i++) { + ctx.fillRect( + adjustedX + 5 + i * 15, + currentDinoY + 10, + 8, + currentHeight - 20 + ); + } + + // Enhanced eye with shine + ctx.fillStyle = 'white'; + ctx.beginPath(); + ctx.arc(adjustedX + 45, currentDinoY + (isDucking ? 10 : 15), 8, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = 'black'; + ctx.beginPath(); + ctx.arc(adjustedX + 47, currentDinoY + (isDucking ? 12 : 17), 4, 0, Math.PI * 2); + ctx.fill(); + + // Eye shine + ctx.fillStyle = 'white'; + ctx.beginPath(); + ctx.arc(adjustedX + 46, currentDinoY + (isDucking ? 11 : 16), 2, 0, Math.PI * 2); + ctx.fill(); + + // Enhanced smile + ctx.beginPath(); + ctx.arc(adjustedX + 35, currentDinoY + (isDucking ? 25 : 40), 8, 0, Math.PI, false); + ctx.strokeStyle = '#333'; + ctx.lineWidth = 3; + ctx.stroke(); + + // Teeth + ctx.fillStyle = 'white'; + ctx.fillRect(adjustedX + 30, currentDinoY + (isDucking ? 25 : 40) - 2, 3, 4); + ctx.fillRect(adjustedX + 37, currentDinoY + (isDucking ? 25 : 40) - 2, 3, 4); + + // Legs with running animation + if (!isJumpingRef.current) { + const legOffset = Math.sin(legTime) * 8; + + // Front leg + ctx.fillStyle = '#2E7D32'; + if (ctx.roundRect) { + ctx.roundRect( + adjustedX + 15, + currentDinoY + currentHeight, + 10, + 20 + legOffset, + 5 + ); + } else { + ctx.fillRect(adjustedX + 15, currentDinoY + currentHeight, 10, 20 + legOffset); + } + ctx.fill(); + + // Back leg + if (ctx.roundRect) { + ctx.roundRect( + adjustedX + currentWidth - 25, + currentDinoY + currentHeight, + 10, + 20 - legOffset, + 5 + ); + } else { + ctx.fillRect(adjustedX + currentWidth - 25, currentDinoY + currentHeight, 10, 20 - legOffset); + } + ctx.fill(); + + // Leg stripes + ctx.fillStyle = '#1B5E20'; + ctx.fillRect(adjustedX + 16, currentDinoY + currentHeight + 5, 8, 3); + ctx.fillRect(adjustedX + currentWidth - 24, currentDinoY + currentHeight + 5, 8, 3); + } + + // Enhanced tail with animation + const tailAngle = isJumpingRef.current ? Math.PI / 6 : Math.sin(legTime) * 0.3; + + ctx.save(); + ctx.translate(adjustedX, currentDinoY + 30); + ctx.rotate(tailAngle); + + const tailGradient = ctx.createLinearGradient(0, 0, 30, 0); + tailGradient.addColorStop(0, '#4CAF50'); + tailGradient.addColorStop(1, '#1B5E20'); + + ctx.fillStyle = tailGradient; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(-25, -10); + ctx.lineTo(-35, 0); + ctx.lineTo(-25, 10); + ctx.closePath(); + ctx.fill(); + + // Tail spikes + ctx.fillStyle = '#1B5E20'; + for (let i = 0; i < 3; i++) { + ctx.beginPath(); + ctx.moveTo(-15 + i * -5, 0); + ctx.lineTo(-20 + i * -5, -5); + ctx.lineTo(-20 + i * -5, 5); + ctx.closePath(); + ctx.fill(); + } + + ctx.restore(); + + // Spikes on back + ctx.fillStyle = '#1B5E20'; + for (let i = 0; i < 4; i++) { + ctx.beginPath(); + ctx.moveTo(adjustedX + 15 + i * 12, currentDinoY); + ctx.lineTo(adjustedX + 10 + i * 12, currentDinoY - 10); + ctx.lineTo(adjustedX + 20 + i * 12, currentDinoY - 10); + ctx.closePath(); + ctx.fill(); + } + + // Jump dust effect + if (isJumpingRef.current && jumpVelocityRef.current < -5) { + ctx.fillStyle = 'rgba(139, 115, 85, 0.5)'; + for (let i = 0; i < 5; i++) { + const dustX = adjustedX + Math.random() * 20; + const dustY = currentDinoY + currentHeight + Math.random() * 10; + const dustSize = Math.random() * 4 + 2; + ctx.beginPath(); + ctx.arc(dustX, dustY, dustSize, 0, Math.PI * 2); + ctx.fill(); + } + } + + // Add shadow effect + ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'; + ctx.shadowBlur = 10; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + + // Remove shadow after drawing + setTimeout(() => { + ctx.shadowColor = 'transparent'; + ctx.shadowBlur = 0; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + }, 0); + }; + + const drawClouds = (ctx) => { + cloudsRef.current.forEach(cloud => { + ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; + ctx.beginPath(); + ctx.arc(cloud.x, cloud.y, 15, 0, Math.PI * 2); + ctx.arc(cloud.x + 20, cloud.y - 10, 20, 0, Math.PI * 2); + ctx.arc(cloud.x + 45, cloud.y, 18, 0, Math.PI * 2); + ctx.arc(cloud.x + 25, cloud.y + 10, 15, 0, Math.PI * 2); + ctx.fill(); + }); + }; + + const drawObstacle = (ctx, obstacle) => { + ctx.fillStyle = obstacle.color || '#059669'; + + if (obstacle.type === 'cactus') { + // Draw cactus with details + ctx.fillRect(obstacle.x, obstacle.y, 20, 40); + ctx.fillRect(obstacle.x - 10, obstacle.y + 10, 10, 15); + ctx.fillRect(obstacle.x + 20, obstacle.y + 15, 10, 15); + + // Cactus spines + ctx.strokeStyle = '#027148'; + ctx.lineWidth = 1; + for (let i = 0; i < 5; i++) { + const spineY = obstacle.y + 5 + i * 8; + ctx.beginPath(); + ctx.moveTo(obstacle.x - 2, spineY); + ctx.lineTo(obstacle.x - 8, spineY); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(obstacle.x + 22, spineY); + ctx.lineTo(obstacle.x + 28, spineY); + ctx.stroke(); + } + } else if (obstacle.type === 'bird') { + // Draw bird with details + ctx.beginPath(); + ctx.arc(obstacle.x, obstacle.y, 15, 0, Math.PI * 2); + ctx.fill(); + + // Eye + ctx.fillStyle = 'white'; + ctx.beginPath(); + ctx.arc(obstacle.x + 5, obstacle.y - 3, 4, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = 'black'; + ctx.beginPath(); + ctx.arc(obstacle.x + 6, obstacle.y - 2, 2, 0, Math.PI * 2); + ctx.fill(); + + // Beak + ctx.fillStyle = '#FF9800'; + ctx.beginPath(); + ctx.moveTo(obstacle.x + 15, obstacle.y); + ctx.lineTo(obstacle.x + 25, obstacle.y); + ctx.lineTo(obstacle.x + 20, obstacle.y + 5); + ctx.closePath(); + ctx.fill(); + + // Wings + ctx.fillStyle = obstacle.color; + ctx.beginPath(); + ctx.arc(obstacle.x - 10, obstacle.y, 10, 0, Math.PI * 2); + ctx.arc(obstacle.x + 10, obstacle.y, 10, 0, Math.PI * 2); + ctx.fill(); + } else if (obstacle.type === 'rock') { + // Draw rock with texture + ctx.beginPath(); + ctx.arc(obstacle.x, obstacle.y, 25, 0, Math.PI * 2); + ctx.fill(); + + // Rock texture + ctx.fillStyle = '#7C3A2D'; + for (let i = 0; i < 8; i++) { + const angle = (i * Math.PI) / 4; + const texX = obstacle.x + Math.cos(angle) * 15; + const texY = obstacle.y + Math.sin(angle) * 15; + ctx.beginPath(); + ctx.arc(texX, texY, 3, 0, Math.PI * 2); + ctx.fill(); + } + } + }; + + const drawPowerUp = (ctx, powerUp) => { + // Pulsing animation + const pulseSize = 15 + Math.sin(Date.now() / 200) * 3; + + // Outer glow + ctx.shadowColor = POWER_UP_TYPES[powerUp.type].color; + ctx.shadowBlur = 20; + + // Main circle + ctx.fillStyle = POWER_UP_TYPES[powerUp.type].color; + ctx.beginPath(); + ctx.arc(powerUp.x, powerUp.y, pulseSize, 0, Math.PI * 2); + ctx.fill(); + + // Inner circle + ctx.fillStyle = 'white'; + ctx.beginPath(); + ctx.arc(powerUp.x, powerUp.y, pulseSize - 5, 0, Math.PI * 2); + ctx.fill(); + + // Add icon + ctx.fillStyle = POWER_UP_TYPES[powerUp.type].color; + ctx.font = 'bold 20px Arial'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + const icon = powerUp.type === 'SPEED_BOOST' ? '⚡' : + powerUp.type === 'SLOW_TIME' ? '⏱️' : + powerUp.type === 'INVINCIBLE' ? '🛡️' : '💰'; + ctx.fillText(icon, powerUp.x, powerUp.y); + + // Remove shadow + ctx.shadowColor = 'transparent'; + ctx.shadowBlur = 0; + }; + + // Game logic functions + const generateInitialClouds = (canvas) => { + const clouds = []; + for (let i = 0; i < 5; i++) { + clouds.push({ + x: Math.random() * canvas.width, + y: Math.random() * (canvas.height / 3) + 20, + speed: 0.5 + Math.random() * 0.5 + }); + } + setGameState(prev => ({ ...prev, clouds })); + }; + + const generateCloud = (canvas) => { + const newCloud = { + x: canvas.width + Math.random() * 200, + y: Math.random() * (canvas.height / 3) + 20, + speed: 0.5 + Math.random() * 0.5 + }; + setGameState(prev => ({ ...prev, clouds: [...prev.clouds, newCloud] })); + }; + + const generateObstacle = (canvas) => { + const types = ['cactus', 'bird', 'rock']; + const weights = [0.6, 0.25, 0.15]; // Cacti more common, rocks less common + const random = Math.random(); + + let type = 'cactus'; + if (random < weights[0]) type = 'cactus'; + else if (random < weights[0] + weights[1]) type = 'bird'; + else type = 'rock'; + + let y = canvas.height - GROUND_HEIGHT; + let height = 40; + let width = 30; + + if (type === 'bird') { + y = canvas.height - GROUND_HEIGHT - 100 + Math.random() * 50; + height = 30; + width = 30; + } else if (type === 'rock') { + height = 50; + width = 50; + y = canvas.height - GROUND_HEIGHT - 25; + } else { + height = 60; + width = 30; + } + + const newObstacle = { + x: canvas.width, + y: y - height, + width: width, + height: height, + type: type, + color: type === 'cactus' ? '#059669' : + type === 'bird' ? '#7c3aed' : '#92400e' + }; + + setGameState(prev => ({ ...prev, obstacles: [...prev.obstacles, newObstacle] })); + }; + + const generatePowerUp = (canvas) => { + const types = Object.keys(POWER_UP_TYPES); + const type = types[Math.floor(Math.random() * types.length)]; + + const newPowerUp = { + x: canvas.width, + y: Math.random() * (canvas.height - 100) + 50, + type: type, + startTime: Date.now(), + duration: POWER_UP_TYPES[type].duration + }; + + setGameState(prev => ({ ...prev, powerUps: [...prev.powerUps, newPowerUp] })); + }; + + const updateObstacles = (ctx) => { + const updatedObstacles = obstaclesRef.current.map(obstacle => ({ + ...obstacle, + x: obstacle.x - gameState.speed * gameState.gameSpeed + })); + + if (JSON.stringify(updatedObstacles) !== JSON.stringify(obstaclesRef.current)) { + setGameState(prev => ({ ...prev, obstacles: updatedObstacles })); + } + + updatedObstacles.forEach(obstacle => drawObstacle(ctx, obstacle)); + }; + + const updatePowerUps = (ctx) => { + const updatedPowerUps = powerUpsRef.current.map(powerUp => ({ + ...powerUp, + x: powerUp.x - gameState.speed * gameState.gameSpeed + })); + + if (JSON.stringify(updatedPowerUps) !== JSON.stringify(powerUpsRef.current)) { + setGameState(prev => ({ ...prev, powerUps: updatedPowerUps })); + } + + updatedPowerUps.forEach(powerUp => drawPowerUp(ctx, powerUp)); + }; + + const updateGameStats = (deltaTime) => { + const distanceIncrease = gameState.speed * gameState.gameSpeed * deltaTime / 1000; + const scoreIncrease = Math.floor(gameState.gameSpeed * (gameState.combo + 1) * deltaTime / 500); + + setGameState(prev => ({ + ...prev, + distance: prev.distance + distanceIncrease, + score: prev.score + scoreIncrease + })); + }; + + const checkCollisions = useCallback(() => { + const dinoRect = { + x: DINO_X, + y: dinoYRef.current, + width: DINO_WIDTH, + height: DINO_HEIGHT + }; + + // Check obstacle collisions + obstaclesRef.current.forEach(obstacle => { + if (isColliding(dinoRect, obstacle)) { + const hasInvincibility = powerUpsRef.current.some(p => p.type === 'INVINCIBLE'); + + if (!hasInvincibility) { + gameOver(); + } else { + // If invincible, destroy obstacle and increase combo + setGameState(prev => ({ + ...prev, + obstacles: prev.obstacles.filter(o => o !== obstacle), + score: prev.score + 100, + combo: prev.combo + 1 + })); + } + } + }); + + // Check power-up collisions + powerUpsRef.current.forEach(powerUp => { + const powerUpRect = { + x: powerUp.x, + y: powerUp.y, + width: 30, + height: 30 + }; + + if (isColliding(dinoRect, powerUpRect)) { + activatePowerUp(powerUp); + } + }); + }, []); + + const isColliding = (rect1, rect2) => { + return rect1.x < rect2.x + rect2.width && + rect1.x + rect1.width > rect2.x && + rect1.y < rect2.y + rect2.height && + rect1.y + rect1.height > rect2.y; + }; + + const activatePowerUp = (powerUp) => { + setGameState(prev => ({ + ...prev, + powerUps: prev.powerUps.filter(p => p !== powerUp), + combo: prev.combo + 1 + })); + + switch(powerUp.type) { + case 'SPEED_BOOST': + setGameState(prev => ({ ...prev, gameSpeed: prev.gameSpeed * 1.5 })); + setTimeout(() => { + setGameState(prev => ({ ...prev, gameSpeed: prev.gameSpeed / 1.5 })); + }, powerUp.duration); + break; + case 'SLOW_TIME': + setGameState(prev => ({ ...prev, gameSpeed: prev.gameSpeed * 0.7 })); + setTimeout(() => { + setGameState(prev => ({ ...prev, gameSpeed: prev.gameSpeed / 0.7 })); + }, powerUp.duration); + break; + case 'DOUBLE_POINTS': + const originalScoreMultiplier = 1; + // Double points handled in score calculation + setTimeout(() => { + // Reset double points effect + }, powerUp.duration); + break; + } + }; + + const cleanupObjects = (canvas) => { + // Remove off-screen obstacles + const filteredObstacles = obstaclesRef.current.filter(o => o.x + o.width > -50); + if (filteredObstacles.length !== obstaclesRef.current.length) { + setGameState(prev => ({ ...prev, obstacles: filteredObstacles })); + } + + // Remove off-screen clouds + const filteredClouds = cloudsRef.current.filter(c => c.x + 50 > -50); + if (filteredClouds.length !== cloudsRef.current.length) { + setGameState(prev => ({ ...prev, clouds: filteredClouds })); + } + + // Remove off-screen power-ups + const filteredPowerUps = powerUpsRef.current.filter(p => p.x + 30 > -50); + if (filteredPowerUps.length !== powerUpsRef.current.length) { + setGameState(prev => ({ ...prev, powerUps: filteredPowerUps })); + } + }; + + const handleJump = () => { + if (gameState.isPlaying && !gameState.isPaused && !isJumpingRef.current) { + isJumpingRef.current = true; + jumpVelocityRef.current = JUMP_FORCE; + setGameState(prev => ({ ...prev, isJumping: true })); + + // Add visual feedback + const canvas = canvasRef.current; + if (canvas) { + canvas.style.transform = 'translateY(-5px)'; + setTimeout(() => { + canvas.style.transform = 'translateY(0)'; + }, 100); + } + } + }; + + const handleDuck = () => { + if (gameState.isPlaying && !gameState.isJumping && !gameState.isPaused) { + setGameState(prev => ({ ...prev, dinoState: 'ducking' })); + setTimeout(() => { + setGameState(prev => prev.dinoState === 'ducking' ? + { ...prev, dinoState: 'running' } : prev); + }, 300); + } + }; + + const startGame = () => { + const canvas = canvasRef.current; + if (!canvas) return; + + // Reset timers + lastObstacleTimeRef.current = Date.now(); + lastCloudTimeRef.current = Date.now(); + lastPowerUpTimeRef.current = Date.now(); + + setGameState(prev => ({ + ...prev, + isPlaying: true, + isGameOver: false, + isPaused: false, + score: 0, + obstacles: [], + powerUps: [], + combo: 0, + distance: 0, + gameSpeed: getDifficultySpeed(prev.difficulty), + isJumping: false, + dinoState: 'running' + })); + + groundLevelRef.current = canvas.height - GROUND_HEIGHT - DINO_HEIGHT; + dinoYRef.current = groundLevelRef.current; + jumpVelocityRef.current = 0; + isJumpingRef.current = false; + + // Generate initial clouds if none exist + if (cloudsRef.current.length === 0) { + generateInitialClouds(canvas); + } + }; + + const togglePause = () => { + setGameState(prev => ({ ...prev, isPaused: !prev.isPaused })); + }; + + const gameOver = () => { + setGameState(prev => { + const newHighScore = prev.score > prev.highScore ? prev.score : prev.highScore; + if (prev.score > prev.highScore) { + localStorage.setItem('dinoHighScore', prev.score); + } + + return { + ...prev, + isPlaying: false, + isGameOver: true, + isPaused: false, + highScore: newHighScore, + isJumping: false, + dinoState: 'running' + }; + }); + + isJumpingRef.current = false; + jumpVelocityRef.current = 0; + + // Game over effect + const canvas = canvasRef.current; + if (canvas) { + canvas.style.transform = 'scale(0.98)'; + setTimeout(() => { + canvas.style.transform = 'scale(1)'; + }, 300); + } + }; + + const restartGame = () => { + const canvas = canvasRef.current; + if (!canvas) return; + + // Reset timers + lastObstacleTimeRef.current = Date.now(); + lastCloudTimeRef.current = Date.now(); + lastPowerUpTimeRef.current = Date.now(); + + setGameState(prev => ({ + ...prev, + isPlaying: false, + isGameOver: false, + isPaused: false, + score: 0, + obstacles: [], + powerUps: [], + combo: 0, + distance: 0, + gameSpeed: getDifficultySpeed(prev.difficulty), + isJumping: false, + dinoState: 'running' + })); + + groundLevelRef.current = canvas.height - GROUND_HEIGHT - DINO_HEIGHT; + dinoYRef.current = groundLevelRef.current; + jumpVelocityRef.current = 0; + isJumpingRef.current = false; + + // Reset clouds + generateInitialClouds(canvas); + + // Start game after a brief delay + setTimeout(startGame, 100); + }; + + const updateDifficulty = (difficulty) => { + setGameState(prev => ({ + ...prev, + difficulty, + gameSpeed: getDifficultySpeed(difficulty) + })); + }; + + const getDifficultySpeed = (difficulty) => { + switch(difficulty) { + case 'easy': return 0.8; + case 'normal': return 1.0; + case 'hard': return 1.3; + case 'extreme': return 1.6; + default: return 1.0; + } + }; + + return ( + <> +
+ +
+ {/* Your Code Starts Here */} +
+
+ {/* Header */} +
+
+
🦖
+

+ T-REX RUNNER +

+
+
+
+
HIGH SCORE
+
{gameState.highScore}
+
+
+
+ + {/* Game Stats */} +
+
+
SCORE
+
{Math.floor(gameState.score)}
+
+
+
DISTANCE
+
{Math.floor(gameState.distance)}m
+
+
+
COMBO
+
{gameState.combo}x
+
+
+
SPEED
+
{gameState.gameSpeed.toFixed(1)}x
+
+
+ + {/* Game Canvas */} +
+
+ + + {/* Start Screen */} + {!gameState.isPlaying && !gameState.isGameOver && ( +
+
+
+ 🦖 + READY TO RUN? +
+
+
+
SPACE
+ or +
+ to JUMP +
+
+
+ to DUCK +
+
+
P
+ to PAUSE +
+
+
+ +
+ {['easy', 'normal', 'hard', 'extreme'].map(diff => ( + + ))} +
+
+ +
+
+ )} + + {/* Game Over Screen */} + {gameState.isGameOver && ( +
+
+
+ 💥 + GAME OVER + 💥 +
+
+
+
Score: {Math.floor(gameState.score)}
+
+
+
High Score: {gameState.highScore}
+
+
+
Distance: {Math.floor(gameState.distance)}m
+
+
+ +
+
+ )} + + {/* Pause Screen */} + {gameState.isPaused && gameState.isPlaying && ( +
+
+
+ ⏸️ +
PAUSED
+
+
+

Game is paused

+

Take a break, but come back soon!

+
+ +
+
+ )} +
+
+ + {/* Controls */} +
+ + + + +
+ + {/* Power-ups Display */} +
+
+ + ACTIVE POWER-UPS: +
+
+ {gameState.powerUps.length === 0 ? ( +
+ No active power-ups. Collect them during gameplay! +
+ ) : ( + gameState.powerUps.map((powerUp, index) => ( +
+
+ {powerUp.type === 'SPEED_BOOST' ? '⚡' : + powerUp.type === 'SLOW_TIME' ? '⏱️' : + powerUp.type === 'INVINCIBLE' ? '🛡️' : '💰'} +
+
+
{POWER_UP_TYPES[powerUp.type]?.effect}
+
+ {Math.ceil((powerUp.duration - (Date.now() - powerUp.startTime)) / 1000)}s remaining +
+
+
+ )) + )} +
+
+ + {/* Instructions */} +
+
+ + 🌵 + Avoid obstacles! + + + + + Collect power-ups! + + + + 🏆 + Beat high score! + +
+
+
+
+ {/* Your Code Ends Here */} +
+
+ + ); +} + +export default SohamBhattacharya; \ No newline at end of file diff --git a/src/plays/soham-bhattacharya/styles.css b/src/plays/soham-bhattacharya/styles.css new file mode 100644 index 000000000..b074cf2ce --- /dev/null +++ b/src/plays/soham-bhattacharya/styles.css @@ -0,0 +1,190 @@ +/* styles.css */ +@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); + +.game-font { + font-family: 'Press Start 2P', monospace; +} + +/* Animation keyframes */ +@keyframes dinoPulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.02); } + 100% { transform: scale(1); } +} + +@keyframes scoreGlow { + 0%, 100% { + text-shadow: 0 0 10px #ff6b6b, 0 0 20px #ff6b6b; + } + 50% { + text-shadow: 0 0 20px #ff6b6b, 0 0 30px #ff6b6b, 0 0 40px #ff6b6b; + } +} + +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); } + 20%, 40%, 60%, 80% { transform: translateX(5px); } +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } + 40% { transform: translateY(-10px); } + 60% { transform: translateY(-5px); } +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@keyframes ripple { + 0% { + transform: scale(0, 0); + opacity: 0.5; + } + 100% { + transform: scale(20, 20); + opacity: 0; + } +} + +/* Apply animations */ +.dino-animate { + animation: dinoPulse 2s infinite; +} + +.score-glow { + animation: scoreGlow 1s infinite; +} + +.animate-shake { + animation: shake 0.5s ease-in-out; +} + +.animate-bounce { + animation: bounce 0.5s; +} + +.animate-pulse { + animation: pulse 1.5s infinite; +} + +.animate-spin { + animation: spin 1s linear; +} + +/* Game canvas effects */ +.game-canvas { + transition: all 0.3s ease; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; +} + +/* Button effects */ +.game-button { + transition: all 0.3s ease; + position: relative; + overflow: hidden; + cursor: pointer; +} + +.game-button:hover { + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(0,0,0,0.3) !important; +} + +.game-button:active { + transform: translateY(1px); + transition: transform 0.1s; +} + +.game-button:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 5px; + height: 5px; + background: rgba(255,255,255,0.5); + opacity: 0; + border-radius: 100%; + transform: scale(1, 1) translate(-50%); + transform-origin: 50% 50%; +} + +.game-button:focus:not(:active)::after { + animation: ripple 1s ease-out; +} + +/* Scrollbar styling */ +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 5px; +} + +::-webkit-scrollbar-thumb { + background: #888; + border-radius: 5px; +} + +::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* Selection styling */ +::selection { + background: rgba(79, 209, 197, 0.5); + color: white; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .game-font { + font-size: 0.8em; + } + + .text-4xl { + font-size: 1.5rem; + } + + .text-3xl { + font-size: 1.25rem; + } + + .text-2xl { + font-size: 1.125rem; + } +} + +/* Loading states */ +.loading { + position: relative; + overflow: hidden; +} + +.loading:after { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + animation: loading 1.5s infinite; +} + +@keyframes loading { + 0% { left: -100%; } + 100% { left: 100%; } +} \ No newline at end of file From cd655a372f92cf15c09ff2a3157a86cb9dc8e5c1 Mon Sep 17 00:00:00 2001 From: Learnerbypassion Date: Wed, 4 Feb 2026 12:56:26 +0530 Subject: [PATCH 2/2] fix(husky): use npm run pre-commit to avoid global yarn requirement --- .husky/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 7e2936624..14ccce4a2 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -yarn pre-commit \ No newline at end of file +npm run pre-commit \ No newline at end of file