Sélectionner une page

Performance Tracking

Track With Divi X

import React, { useState, useEffect, useRef, useCallback } from 'react'; const GAME_WIDTH = 800; const GAME_HEIGHT = 400; const GROUND_HEIGHT = 80; const SLIME_RADIUS = 40; const BALL_RADIUS = 10; const GOAL_WIDTH = 80; const GOAL_HEIGHT = 120; const GRAVITY = 0.6; const SLIME_SPEED = 5; const SLIME_JUMP_POWER = -12; const BALL_DAMPING = 0.99; const BALL_BOUNCE_DAMPING = 0.8; const MAX_BALL_SPEED = 13; const AI_REACTION_DISTANCE = 300; const AI_PREDICTION_TIME = 30; const SlimeSoccer = () => { const canvasRef = useRef(null); const animationRef = useRef(null); const keysRef = useRef({}); // Game state const [gameMode, setGameMode] = useState(null); const [playerMode, setPlayerMode] = useState(null); const [timeLeft, setTimeLeft] = useState(0); const [score, setScore] = useState({ left: 0, right: 0 }); const [gameStarted, setGameStarted] = useState(false); const [winner, setWinner] = useState(null); // Game objects state const gameStateRef = useRef({ leftSlime: { x: 200, y: GAME_HEIGHT - GROUND_HEIGHT, vx: 0, vy: 0, isGrabbing: false, hasBall: false, goalLineTime: 0 }, rightSlime: { x: 600, y: GAME_HEIGHT - GROUND_HEIGHT, vx: 0, vy: 0, isGrabbing: false, hasBall: false, goalLineTime: 0 }, ball: { x: GAME_WIDTH / 2, y: 150, vx: 0, vy: 0, grabbedBy: null, grabAngle: 0, grabAngularVelocity: 0 } }); // Handle keyboard input useEffect(() => { const handleKeyDown = (e) => { // Don't prevent default for input fields if (e.target.tagName === 'INPUT') return; e.preventDefault(); const key = e.key.toLowerCase(); if (key === 'arrowup' || key === 'arrowdown' || key === 'arrowleft' || key === 'arrowright') { keysRef.current[key] = true; } else { keysRef.current[key] = true; } }; const handleKeyUp = (e) => { // Don't prevent default for input fields if (e.target.tagName === 'INPUT') return; e.preventDefault(); const key = e.key.toLowerCase(); if (key === 'arrowup' || key === 'arrowdown' || key === 'arrowleft' || key === 'arrowright') { keysRef.current[key] = false; } else { keysRef.current[key] = false; } }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); }; }, []); // Timer useEffect(() => { if (gameStarted && timeLeft > 0) { const timer = setInterval(() => { setTimeLeft(prev => { if (prev <= 1) { setGameStarted(false); determineWinner(); return 0; } return prev - 1; }); }, 1000); return () => clearInterval(timer); } }, [gameStarted, timeLeft]); const determineWinner = () => { if (score.left > score.right) { setWinner('Cyan Team'); } else if (score.right > score.left) { setWinner('Red Team'); } else { setWinner('Draw'); } }; const resetPositions = () => { const state = gameStateRef.current; // Reset slimes to starting positions state.leftSlime.x = 200; state.leftSlime.y = GAME_HEIGHT - GROUND_HEIGHT; state.leftSlime.vx = 0; state.leftSlime.vy = 0; state.leftSlime.isGrabbing = false; state.leftSlime.hasBall = false; state.leftSlime.goalLineTime = 0; state.rightSlime.x = 600; state.rightSlime.y = GAME_HEIGHT - GROUND_HEIGHT; state.rightSlime.vx = 0; state.rightSlime.vy = 0; state.rightSlime.isGrabbing = false; state.rightSlime.hasBall = false; state.rightSlime.goalLineTime = 0; // Reset ball state.ball.x = GAME_WIDTH / 2; state.ball.y = 150; state.ball.vx = 0; state.ball.vy = 0; state.ball.grabbedBy = null; state.ball.grabAngle = 0; state.ball.grabAngularVelocity = 0; }; const resetGame = () => { resetPositions(); setScore({ left: 0, right: 0 }); setWinner(null); }; const startGame = (mode) => { const times = { '1min': 60, '2min': 120, '4min': 240, '8min': 480, 'worldcup': 300 }; resetGame(); setGameMode(mode); setTimeLeft(times[mode]); setGameStarted(true); }; // AI logic for single player mode const updateAI = useCallback(() => { if (playerMode !== 'single') return; const state = gameStateRef.current; const ai = state.leftSlime; // AI is now left slime const opponent = state.rightSlime; const ball = state.ball; // Enhanced AI parameters const FIELD_WIDTH = GAME_WIDTH; const OPPONENT_GOAL_X = FIELD_WIDTH - GOAL_WIDTH / 2; const AI_GOAL_X = GOAL_WIDTH / 2; // Add some randomness to AI behavior const randomFactor = Math.random(); const aggressiveness = 0.7 + randomFactor * 0.3; // 70-100% aggressive // Predict ball trajectory with more detail let predictions = []; let tempX = ball.x; let tempY = ball.y; let tempVx = ball.vx; let tempVy = ball.vy; for (let t = 0; t < 100; t++) { tempVy += GRAVITY; tempVx *= BALL_DAMPING; tempX += tempVx; tempY += tempVy; // Boundary bounces if (tempX < BALL_RADIUS) { tempX = BALL_RADIUS; tempVx = -tempVx * BALL_BOUNCE_DAMPING; } if (tempX > FIELD_WIDTH - BALL_RADIUS) { tempX = FIELD_WIDTH - BALL_RADIUS; tempVx = -tempVx * BALL_BOUNCE_DAMPING; } predictions.push({ x: tempX, y: tempY, vx: tempVx, vy: tempVy, time: t }); if (tempY > GAME_HEIGHT - GROUND_HEIGHT - BALL_RADIUS) { tempY = GAME_HEIGHT - GROUND_HEIGHT - BALL_RADIUS; tempVy = -tempVy * BALL_BOUNCE_DAMPING; break; } } // Analyze game state const ballDistanceToOpponentGoal = Math.abs(ball.x - OPPONENT_GOAL_X); const ballDistanceToAIGoal = Math.abs(ball.x - AI_GOAL_X); const aiDistanceToBall = Math.abs(ai.x - ball.x); const opponentDistanceToBall = Math.abs(opponent.x - ball.x); const ballMovingTowardsAIGoal = ball.vx < -1; const ballMovingTowardsOpponentGoal = ball.vx > 1; const ballHeight = GAME_HEIGHT - GROUND_HEIGHT - ball.y; // Prevent repetitive jumping - track if we're stuck if (!ai.lastBallY) ai.lastBallY = ball.y; if (!ai.stuckCounter) ai.stuckCounter = 0; const ballStuck = Math.abs(ball.y - ai.lastBallY) < 5 && Math.abs(ball.vx) < 2; if (ballStuck) { ai.stuckCounter++; } else { ai.stuckCounter = 0; } ai.lastBallY = ball.y; // Determine optimal position and strategy let targetX = ai.x; let shouldJump = false; let shouldGrab = false; let moveSpeed = SLIME_SPEED; // Starting behavior - add variety if (timeLeft > 58 && gameMode === '1min') { const startStrategy = randomFactor; if (startStrategy < 0.3) { // Defensive start targetX = 150 + randomFactor * 100; } else if (startStrategy < 0.7) { // Midfield start targetX = FIELD_WIDTH * 0.3 + randomFactor * 100; } else { // Aggressive start targetX = ball.x - 50; moveSpeed = SLIME_SPEED * aggressiveness; } } // SUPER AGGRESSIVE OFFENSE - Multiple strategies else if (ballDistanceToOpponentGoal < ballDistanceToAIGoal * 1.5 || (ball.x > FIELD_WIDTH * 0.35 && !ballMovingTowardsAIGoal)) { // Calculate multiple attack angles const directAttackX = ball.x - 30; const overheadAttackX = ball.x - 45; const underAttackX = ball.x - 20; // Choose attack based on ball height and position if (ballHeight > 60 && aiDistanceToBall < 150) { targetX = overheadAttackX; // Set up for overhead kick } else if (ballHeight < 30 && aiDistanceToBall < 100) { targetX = underAttackX; // Get under low balls } else { targetX = directAttackX + (randomFactor - 0.5) * 20; // Add variety } moveSpeed = SLIME_SPEED * 1.2; // Faster when attacking // Smart offensive decisions if (aiDistanceToBall < 100) { // If ball is stuck, hit it harder if (ai.stuckCounter > 30) { shouldJump = true; targetX = ball.x - 40; // Get better angle } // Grab low balls for control else if (ballHeight < 35 && aiDistanceToBall < 60 && !ai.hasBall && ball.vy > -2) { shouldGrab = true; } // Jump for high balls or to create angles else if ((ballHeight > 30 && ballHeight < 90) || (ball.x > FIELD_WIDTH * 0.6 && ballHeight < 120)) { if (ai.y >= GAME_HEIGHT - GROUND_HEIGHT - 1) { const timeToReachBall = Math.abs(ai.x - ball.x) / SLIME_SPEED; const ballHeightWhenReached = ball.y + ball.vy * timeToReachBall + 0.5 * GRAVITY * timeToReachBall * timeToReachBall; if (ballHeightWhenReached > GAME_HEIGHT - GROUND_HEIGHT - 100 && ballHeightWhenReached < GAME_HEIGHT - GROUND_HEIGHT - 20) { shouldJump = true; } } } } // Strategic ball release if (ai.hasBall) { const angleToGoal = Math.atan2(0, OPPONENT_GOAL_X - ai.x); if (Math.abs(angleToGoal) < 0.5 || ai.x > FIELD_WIDTH * 0.7) { shouldGrab = false; // Release toward goal } } } // SMART DEFENSE - Predictive and aggressive else if (ball.x < FIELD_WIDTH * 0.65 || ballMovingTowardsAIGoal) { // Predict where to intercept let bestInterceptX = ball.x; let interceptTime = 0; for (let pred of predictions) { if (pred.x < FIELD_WIDTH * 0.4) { const timeToReach = Math.abs(ai.x - pred.x) / (SLIME_SPEED * 1.2); if (timeToReach <= pred.time + 5) { bestInterceptX = pred.x; interceptTime = pred.time; break; } } } targetX = bestInterceptX; // Emergency defense if (ball.x < GOAL_WIDTH * 2.5 && ballMovingTowardsAIGoal) { targetX = Math.max(ball.x - 10, SLIME_RADIUS); moveSpeed = SLIME_SPEED * 1.3; // Jump to block shots if (aiDistanceToBall < 120 && ballHeight < 100) { shouldJump = true; } } // Clear stuck balls in defense if (ai.stuckCounter > 20 && ball.x < FIELD_WIDTH * 0.3) { shouldJump = true; targetX = ball.x + 30; // Position to clear forward } } // MIDFIELD CONTROL - Dynamic positioning else { // Multiple positioning strategies const strategies = [ { x: FIELD_WIDTH * 0.35, weight: 0.3 }, // Defensive mid { x: FIELD_WIDTH * 0.45, weight: 0.4 }, // Central mid { x: ball.x - 60, weight: 0.3 } // Ball-oriented ]; // Weighted random selection let strategyRoll = randomFactor; for (let strategy of strategies) { if (strategyRoll < strategy.weight) { targetX = strategy.x + (Math.random() - 0.5) * 40; break; } strategyRoll -= strategy.weight; } // Intercept high balls in midfield for (let pred of predictions) { if (pred.y < GAME_HEIGHT - GROUND_HEIGHT - 50 && Math.abs(pred.x - FIELD_WIDTH * 0.4) < 100) { const timeToReach = Math.abs(ai.x - pred.x) / SLIME_SPEED; if (timeToReach < pred.time && pred.time < 30) { targetX = pred.x; if (pred.time < 20 && ai.y >= GAME_HEIGHT - GROUND_HEIGHT - 1) { shouldJump = true; } break; } } } } // Enhanced grab logic if (shouldGrab && !ai.isGrabbing && ai.y >= GAME_HEIGHT - GROUND_HEIGHT - 1) { ai.isGrabbing = true; } else if (!shouldGrab) { ai.isGrabbing = false; } // Smoother movement with acceleration const difference = targetX - ai.x; const absDistance = Math.abs(difference); if (absDistance > 3) { // Accelerate/decelerate based on distance const speedMultiplier = Math.min(absDistance / 50, 1.5); ai.vx = Math.sign(difference) * moveSpeed * speedMultiplier; } else { ai.vx = 0; } // Execute jump with timing variations if (shouldJump && ai.vy === 0 && !ai.isGrabbing) { // Vary jump power slightly for different trajectories const jumpVariation = 0.9 + randomFactor * 0.2; ai.vy = SLIME_JUMP_POWER * jumpVariation; } // NO BOUNDARIES - AI can go anywhere on the field! }, [playerMode, timeLeft, gameMode]); const updatePhysics = useCallback(() => { const state = gameStateRef.current; const keys = keysRef.current; // Update left slime controls (always human player) if (playerMode === 'multi') { // Multiplayer: WASD for left player if (keys['a']) state.leftSlime.vx = -SLIME_SPEED; else if (keys['d']) state.leftSlime.vx = SLIME_SPEED; else state.leftSlime.vx = 0; if (keys['w'] && state.leftSlime.y >= GAME_HEIGHT - GROUND_HEIGHT - 1 && !state.leftSlime.isGrabbing) { state.leftSlime.vy = SLIME_JUMP_POWER; } // Grab control for left player state.leftSlime.isGrabbing = keys['s']; // Arrow keys for right player if (keys['arrowleft']) state.rightSlime.vx = -SLIME_SPEED; else if (keys['arrowright']) state.rightSlime.vx = SLIME_SPEED; else state.rightSlime.vx = 0; if (keys['arrowup'] && state.rightSlime.y >= GAME_HEIGHT - GROUND_HEIGHT - 1 && !state.rightSlime.isGrabbing) { state.rightSlime.vy = SLIME_JUMP_POWER; } // Grab control for right player state.rightSlime.isGrabbing = keys['arrowdown']; } else { // Single player: Arrow keys for human player (right side) if (keys['arrowleft']) state.rightSlime.vx = -SLIME_SPEED; else if (keys['arrowright']) state.rightSlime.vx = SLIME_SPEED; else state.rightSlime.vx = 0; if (keys['arrowup'] && state.rightSlime.y >= GAME_HEIGHT - GROUND_HEIGHT - 1 && !state.rightSlime.isGrabbing) { state.rightSlime.vy = SLIME_JUMP_POWER; } // Grab control for human player state.rightSlime.isGrabbing = keys['arrowdown']; // AI controls left slime updateAI(); } // Update slime positions and physics [state.leftSlime, state.rightSlime].forEach((slime, index) => { slime.vy += GRAVITY; slime.x += slime.vx; slime.y += slime.vy; // Boundary collision if (slime.x < SLIME_RADIUS) slime.x = SLIME_RADIUS; if (slime.x > GAME_WIDTH - SLIME_RADIUS) slime.x = GAME_WIDTH - SLIME_RADIUS; // Ground collision if (slime.y > GAME_HEIGHT - GROUND_HEIGHT) { slime.y = GAME_HEIGHT - GROUND_HEIGHT; slime.vy = 0; } // Check if slime is camping in their OWN goal area const isLeftSlime = index === 0; const inOwnGoalArea = (isLeftSlime && slime.x < GOAL_WIDTH) || (!isLeftSlime && slime.x > GAME_WIDTH - GOAL_WIDTH); if (inOwnGoalArea) { // Slime is camping in their own goal slime.goalLineTime += 1/60; // Assuming 60 FPS // Check if exceeded 1 second if (slime.goalLineTime >= 1) { // Award goal to other team (penalty for camping) if (isLeftSlime) { setScore(prev => ({ ...prev, right: prev.right + 1 })); } else { setScore(prev => ({ ...prev, left: prev.left + 1 })); } resetPositions(); } } else { // Reset timer if not in own goal area slime.goalLineTime = 0; } }); // Update ball physics if (state.ball.grabbedBy) { // Ball is grabbed by a slime const grabber = state.ball.grabbedBy === 'left' ? state.leftSlime : state.rightSlime; // Apply rotational physics based on slime movement const slimeDirection = state.ball.grabbedBy === 'left' ? 1 : -1; // When slime moves, ball rotates in opposite direction (slower rotation) state.ball.grabAngularVelocity += -grabber.vx * 0.008 * slimeDirection; // Apply angular damping state.ball.grabAngularVelocity *= 0.85; // Update angle state.ball.grabAngle += state.ball.grabAngularVelocity; // Constrain angle based on slime direction // For left slime: -90° (left) to +90° (right) // For right slime: 90° (left) to 270° (right) if (state.ball.grabbedBy === 'left') { // Left slime: constrain between -π/2 and π/2 if (state.ball.grabAngle < -Math.PI / 2) { state.ball.grabAngle = -Math.PI / 2; state.ball.grabAngularVelocity = 0; } else if (state.ball.grabAngle > Math.PI / 2) { state.ball.grabAngle = Math.PI / 2; state.ball.grabAngularVelocity = 0; } } else { // Right slime: keep angle between π/2 and 3π/2 // Normalize angle to 0-2π range first while (state.ball.grabAngle < 0) state.ball.grabAngle += Math.PI * 2; while (state.ball.grabAngle > Math.PI * 2) state.ball.grabAngle -= Math.PI * 2; // Now constrain if (state.ball.grabAngle < Math.PI / 2 && state.ball.grabAngle >= 0) { state.ball.grabAngle = Math.PI / 2; state.ball.grabAngularVelocity = 0; } else if (state.ball.grabAngle > 3 * Math.PI / 2 || (state.ball.grabAngle < Math.PI / 2 && state.ball.grabAngle < 0)) { state.ball.grabAngle = 3 * Math.PI / 2; state.ball.grabAngularVelocity = 0; } } // Calculate ball position based on angle const holdDistance = SLIME_RADIUS + BALL_RADIUS - 5; state.ball.x = grabber.x + Math.cos(state.ball.grabAngle) * holdDistance; state.ball.y = grabber.y + Math.sin(state.ball.grabAngle) * holdDistance; // Ball inherits slime velocity state.ball.vx = grabber.vx; state.ball.vy = grabber.vy; // Check if slime released the grab if (!grabber.isGrabbing) { // Release the ball with angular momentum converted to linear const releaseAngle = state.ball.grabAngle; const releaseSpeed = Math.abs(state.ball.grabAngularVelocity) * 20; state.ball.vx = grabber.vx * 1.5 + Math.cos(releaseAngle) * (3 + releaseSpeed); state.ball.vy = grabber.vy - 2 + Math.sin(releaseAngle) * releaseSpeed * 0.3; state.ball.grabbedBy = null; state.ball.grabAngle = 0; state.ball.grabAngularVelocity = 0; grabber.hasBall = false; } } else { // Normal ball physics state.ball.vy += GRAVITY; state.ball.vx *= BALL_DAMPING; state.ball.x += state.ball.vx; state.ball.y += state.ball.vy; } // Ball boundary collision if (state.ball.x < BALL_RADIUS) { state.ball.x = BALL_RADIUS; state.ball.vx = -state.ball.vx * BALL_BOUNCE_DAMPING; } if (state.ball.x > GAME_WIDTH - BALL_RADIUS) { state.ball.x = GAME_WIDTH - BALL_RADIUS; state.ball.vx = -state.ball.vx * BALL_BOUNCE_DAMPING; } // Ball ground collision and goal detection if (state.ball.y > GAME_HEIGHT - GROUND_HEIGHT - BALL_RADIUS) { state.ball.y = GAME_HEIGHT - GROUND_HEIGHT - BALL_RADIUS; state.ball.vy = -state.ball.vy * BALL_BOUNCE_DAMPING; } // Goal detection - ball hits back wall at any height up to goal height if (state.ball.x <= BALL_RADIUS && state.ball.y > GAME_HEIGHT - GROUND_HEIGHT - GOAL_HEIGHT) { // Goal for right team setScore(prev => ({ ...prev, right: prev.right + 1 })); resetPositions(); } else if (state.ball.x >= GAME_WIDTH - BALL_RADIUS && state.ball.y > GAME_HEIGHT - GROUND_HEIGHT - GOAL_HEIGHT) { // Goal for left team setScore(prev => ({ ...prev, left: prev.left + 1 })); resetPositions(); } // Ball ceiling collision if (state.ball.y < BALL_RADIUS) { state.ball.y = BALL_RADIUS; state.ball.vy = -state.ball.vy * BALL_BOUNCE_DAMPING; } // Ball-slime collision and grab detection [state.leftSlime, state.rightSlime].forEach((slime, index) => { const slimeName = index === 0 ? 'left' : 'right'; const otherSlime = index === 0 ? state.rightSlime : state.leftSlime; const dx = state.ball.x - slime.x; const dy = state.ball.y - slime.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < SLIME_RADIUS + BALL_RADIUS) { // If ball is grabbed by opponent, check if we can knock it out if (state.ball.grabbedBy && state.ball.grabbedBy !== slimeName) { // Calculate collision force const angle = Math.atan2(dy, dx); const speed = Math.sqrt(slime.vx * slime.vx + slime.vy * slime.vy); // If slime is moving fast enough, knock the ball out if (speed > 2 || Math.abs(slime.vy) > 5) { // Release ball from opponent's grab state.ball.grabbedBy = null; state.ball.grabAngle = 0; state.ball.grabAngularVelocity = 0; otherSlime.hasBall = false; // Apply knockback force state.ball.vx = Math.cos(angle) * 8 + slime.vx; state.ball.vy = Math.sin(angle) * 8 + slime.vy; } } // Check if slime is trying to grab an ungrabbed ball else if (slime.isGrabbing && !state.ball.grabbedBy) { // Grab the ball and set initial angle based on position state.ball.grabbedBy = slimeName; state.ball.grabAngle = Math.atan2(dy, dx); state.ball.grabAngularVelocity = 0; slime.hasBall = true; } // Normal collision if not grabbing else if (!state.ball.grabbedBy) { const angle = Math.atan2(dy, dx); const targetX = slime.x + Math.cos(angle) * (SLIME_RADIUS + BALL_RADIUS); const targetY = slime.y + Math.sin(angle) * (SLIME_RADIUS + BALL_RADIUS); // Only collide if ball is above slime center (semicircle collision) if (state.ball.y < slime.y || Math.abs(angle) < Math.PI * 0.5) { state.ball.x = targetX; state.ball.y = targetY; // Transfer velocity const speed = Math.sqrt(state.ball.vx * state.ball.vx + state.ball.vy * state.ball.vy); state.ball.vx = Math.cos(angle) * speed * 1.5 + slime.vx * 0.5; state.ball.vy = Math.sin(angle) * speed * 1.5 + slime.vy * 0.5; // Cap ball speed to prevent it from going too fast const newSpeed = Math.sqrt(state.ball.vx * state.ball.vx + state.ball.vy * state.ball.vy); if (newSpeed > MAX_BALL_SPEED) { const scale = MAX_BALL_SPEED / newSpeed; state.ball.vx *= scale; state.ball.vy *= scale; } } } } }); }, [playerMode, updateAI]); const draw = useCallback(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); const state = gameStateRef.current; // Clear canvas ctx.fillStyle = '#0000FF'; ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); // Draw ground ctx.fillStyle = '#808080'; ctx.fillRect(0, GAME_HEIGHT - GROUND_HEIGHT, GAME_WIDTH, GROUND_HEIGHT); // Draw goals with new design ctx.strokeStyle = '#FFFFFF'; ctx.lineWidth = 3; // Left goal ctx.beginPath(); // Horizontal line on ground ctx.moveTo(0, GAME_HEIGHT - GROUND_HEIGHT); ctx.lineTo(GOAL_WIDTH, GAME_HEIGHT - GROUND_HEIGHT); // Vertical line at halfway point ctx.moveTo(GOAL_WIDTH / 2, GAME_HEIGHT - GROUND_HEIGHT); ctx.lineTo(GOAL_WIDTH / 2, GAME_HEIGHT - GROUND_HEIGHT - GOAL_HEIGHT); ctx.stroke(); // Left goal net (between wall and vertical line) ctx.lineWidth = 1.5; // Updated from 0.6 ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)'; // Vertical net lines for (let i = 0; i < GOAL_WIDTH / 2; i += 10) { ctx.beginPath(); ctx.moveTo(i, GAME_HEIGHT - GROUND_HEIGHT - GOAL_HEIGHT); ctx.lineTo(i, GAME_HEIGHT - GROUND_HEIGHT); ctx.stroke(); } // Horizontal net lines for (let j = GAME_HEIGHT - GROUND_HEIGHT - GOAL_HEIGHT; j <= GAME_HEIGHT - GROUND_HEIGHT; j += 10) { ctx.beginPath(); ctx.moveTo(0, j); ctx.lineTo(GOAL_WIDTH / 2, j); ctx.stroke(); } // Reset for right goal ctx.strokeStyle = '#FFFFFF'; ctx.lineWidth = 3; // Right goal ctx.beginPath(); // Horizontal line on ground ctx.moveTo(GAME_WIDTH - GOAL_WIDTH, GAME_HEIGHT - GROUND_HEIGHT); ctx.lineTo(GAME_WIDTH, GAME_HEIGHT - GROUND_HEIGHT); // Vertical line at halfway point ctx.moveTo(GAME_WIDTH - GOAL_WIDTH / 2, GAME_HEIGHT - GROUND_HEIGHT); ctx.lineTo(GAME_WIDTH - GOAL_WIDTH / 2, GAME_HEIGHT - GROUND_HEIGHT - GOAL_HEIGHT); ctx.stroke(); // Right goal net (between wall and vertical line) ctx.lineWidth = 1.5; // Updated from 0.6 ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)'; // Vertical net lines for (let i = GAME_WIDTH - GOAL_WIDTH / 2; i <= GAME_WIDTH; i += 10) { ctx.beginPath(); ctx.moveTo(i, GAME_HEIGHT - GROUND_HEIGHT - GOAL_HEIGHT); ctx.lineTo(i, GAME_HEIGHT - GROUND_HEIGHT); ctx.stroke(); } // Horizontal net lines for (let j = GAME_HEIGHT - GROUND_HEIGHT - GOAL_HEIGHT; j <= GAME_HEIGHT - GROUND_HEIGHT; j += 10) { ctx.beginPath(); ctx.moveTo(GAME_WIDTH - GOAL_WIDTH / 2, j); ctx.lineTo(GAME_WIDTH, j); ctx.stroke(); } // Draw goal line timers const drawGoalLineTimer = (slime, goalX, goalWidth) => { if (slime.goalLineTime > 0) { const percentage = 1 - (slime.goalLineTime / 1); // 1 second max const timerWidth = goalWidth * percentage; ctx.strokeStyle = percentage > 0.3 ? '#FFFF00' : '#FF0000'; // Yellow to red ctx.lineWidth = 5; ctx.beginPath(); ctx.moveTo(goalX, GAME_HEIGHT - GROUND_HEIGHT + 10); ctx.lineTo(goalX + timerWidth, GAME_HEIGHT - GROUND_HEIGHT + 10); ctx.stroke(); // Reset stroke style ctx.strokeStyle = '#FFFFFF'; ctx.lineWidth = 3; } }; // Check and draw timers for slimes camping in their own goals if (state.leftSlime.x < GOAL_WIDTH) { drawGoalLineTimer(state.leftSlime, 0, GOAL_WIDTH); } if (state.rightSlime.x > GAME_WIDTH - GOAL_WIDTH) { drawGoalLineTimer(state.rightSlime, GAME_WIDTH - GOAL_WIDTH, GOAL_WIDTH); } // Draw slimes (as semicircles) const drawSlime = (slime, isRightSlime, color, accentColor) => { ctx.save(); ctx.imageSmoothingEnabled = false; ctx.fillStyle = color; ctx.beginPath(); ctx.arc(slime.x, slime.y, SLIME_RADIUS, Math.PI, 0); ctx.closePath(); ctx.fill(); // Add accent stripe ctx.fillStyle = accentColor; ctx.beginPath(); ctx.arc(slime.x, slime.y, SLIME_RADIUS - 5, Math.PI + 0.3, Math.PI + 0.7); ctx.arc(slime.x, slime.y, SLIME_RADIUS - 15, Math.PI + 0.7, Math.PI + 0.3, true); ctx.closePath(); ctx.fill(); // Add grab indicator if grabbing if (slime.isGrabbing) { // Remove visual indicator - no yellow outline } ctx.restore(); // Draw eye ctx.fillStyle = '#FFFFFF'; ctx.beginPath(); // Adjust eye position based on which slime const eyeXOffset = isRightSlime ? -SLIME_RADIUS * 0.3 : SLIME_RADIUS * 0.3; ctx.arc(slime.x + eyeXOffset, slime.y - SLIME_RADIUS * 0.3, 5, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#000000'; ctx.beginPath(); const pupilXOffset = isRightSlime ? -SLIME_RADIUS * 0.35 : SLIME_RADIUS * 0.35; ctx.arc(slime.x + pupilXOffset, slime.y - SLIME_RADIUS * 0.3, 2, 0, Math.PI * 2); ctx.fill(); }; drawSlime(state.leftSlime, false, '#00CED1', '#008B8B'); drawSlime(state.rightSlime, true, '#DC143C', '#8B0000'); // Draw ball ctx.fillStyle = '#FFD700'; ctx.beginPath(); ctx.arc(state.ball.x, state.ball.y, BALL_RADIUS, 0, Math.PI * 2); ctx.fill(); }, []); const gameLoop = useCallback(() => { if (gameStarted) { updatePhysics(); draw(); animationRef.current = requestAnimationFrame(gameLoop); } }, [gameStarted, updatePhysics, draw]); useEffect(() => { if (gameStarted) { animationRef.current = requestAnimationFrame(gameLoop); } return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [gameStarted, gameLoop]); const formatTime = (seconds) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; return (
{!gameStarted && !gameMode && !playerMode && (

Claude Soccer Slime

Written by Quin Pendragon (originally)

Adapted by none other than Claude

)} {playerMode && !gameStarted && !gameMode && (

Select Game Duration

Cyan Team vs Red Team
{playerMode === 'multi' ? ( <>

Left Team: W (jump), A/D (move), S (grab)

Right Team: ↑ (jump), ←/→ (move), ↓ (grab)

) : ( <>

Use Arrow Keys: ↑ (jump), ←/→ (move), ↓ (grab)

Hold ↓ to grab the ball when it's near!

)}
)} {(gameStarted || winner) && (
Cyan Team: {score.left} {formatTime(timeLeft)} {score.right} : Red Team
{winner && (

{winner === 'Draw' ? "It's a Draw!" : `${winner} Wins!`}

)}
)}
); }; export default SlimeSoccer;
Content Management
Pellentesque in ipsum id orci porta dapibus. Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem.
Data Management
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sed finibus nisi, sed dictum eros. Donec ultricies lobortis eros nec auctor nisl semper.
CRM Management
Ac feugiat ante. Donec ultricies lobortis eros, nec auctor nisl semper ultricies. Aliquam sodales nulla dolor. Fermentum nulla non justo aliquet.

Take A Quick Tour

Donec rutrum congue leo eget malesuada. Vestibulum ac diam sit amet

10x

Analyze Financial Data
Build a Stronger Relationship with Your Customers
Managing Your Business Doesn’t Have to Be Hard
Business Management Software

Live Demo

Mobile Ready

Donec rutrum congue leo eget malesuada. Quisque velit nisi, pretium ut lacinia in, elementum id enim. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Donec rutrum congue leo eget malesuada. Curabitur aliquet quam id dui posuere blandit.

Product Features

Quisque velit nisi, pretium ut lacinia in, elementum id enim. Praesent sapien massa, convallis.

Analyze Customer Data

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sed finibus nisi, sed dictum eros. Quisque aliquet velit sit amet sem interdum faucibus. In feugiat aliquet mollis. Etiam tincidunt ligula ut hendrerit semper. Quisque luctus lectus non turpis bibendum posuere. Morbi tortor nibh, fringilla sed pretium sit amet, pharetra non ex. Fusce vel egestas nisl.

Analyze Financial Data

Curabitur fermentum nulla non justo aliquet, quis vehicula quam consequat. Duis ut hendrerit tellus, elementum lacinia elit. Maecenas at consectetur ex, vitae consequat augue. Vivamus eget dolor vel quam condimentum sodales. In bibendum odio urna, sit amet fermentum purus venenatis amet.
Social
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sed finibus nisi, sed dictum eros.
Secure
Quisque aliquet velit sit amet sem interdum faucibus. In feugiat aliquet mollis etiam tincidunt ligula.
Connected
Luctus lectus non quisque turpis bibendum posuere. Morbi tortor nibh, fringilla sed pretium sit amet.

Analyze Customer Data

Vivamus suscipit tortor eget felis porttitor volutpat. Nulla quis lorem ut libero malesuada feugiat. Curabitur aliquet quam id dui posuere blandit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus suscipit tortor eget felis porttitor volutpat. Cras ultricies ligula sed magna dictum porta. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Donec
★★★★★

« Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Lorem ipsum dolor sit amet, consectetur. »

★★★★★

« Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sed finibus nisi, sed dictum eros. »

★★★★★

« Quisque aliquet velit sit amet sem interdum faucibus. In feugiat aliquet mollis etiam tincidunt ligula. »

★★★★★

« Luctus lectus non quisque turpis bibendum posuere. Morbi tortor nibh, fringilla sed pretium sit amet. Aliquam sodales nulla dolor sed vulputate sapien. »

Get on Track

Join 28k+ users & teams

Vivamus suscipit tortor eget felis porttitor volutpat. Nulla quis lorem ut libero malesuada feugiat. Curabitur aliquet quam id dui

Copyright © 2025 Divi. All Rights Reserved.