| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601 | 
							- import 'dart:math';
 
- import 'dart:async';
 
- import 'package:flame/components.dart';
 
- import 'package:flame/effects.dart';
 
- import 'package:flame/events.dart';
 
- import 'package:flame/collisions.dart';
 
- import 'package:flutter/material.dart';
 
- import '../../utils/colors.dart';
 
- class Bubble extends SpriteComponent
 
-     with HasGameReference, TapCallbacks, CollisionCallbacks {
 
-   static const double startSize = 40.0; // Small starting size for all bubbles
 
-   static const double maxSize = 50.0; // Same final size for all bubbles
 
-   static const double lifecycleDuration =
 
-       12.0; // Same lifecycle duration for all bubbles
 
-   // Triple collision rule constants
 
-   static const int maxCollisions = 3;
 
-   static const double collisionGracePeriod = 2.0; // seconds
 
-   late String bubbleImagePath;
 
-   late Color bubbleColor;
 
-   bool isPopping = false;
 
-   bool poppedByUser = false;
 
-   bool isAutoSpawned =
 
-       true; // Whether bubble was auto-spawned or user-triggered
 
-   Function(Bubble, bool)? onPop;
 
-   // Collision tracking for triple collision rule
 
-   int _collisionCount = 0;
 
-   double _lastCollisionTime = 0.0;
 
-   double _age = 0.0;
 
-   double _lifecycleProgress = 0.0;
 
-   late double _targetSize;
 
-   // Physics properties for collision and movement
 
-   Vector2 velocity = Vector2.zero();
 
-   static const double maxSpeed = 50.0;
 
-   static const double friction = 0.98;
 
-   static const double collisionDamping = 0.7;
 
-   double _floatTimer = 0.0;
 
-   Bubble({required Vector2 position, this.onPop, this.isAutoSpawned = true})
 
-     : super(
 
-         size: Vector2.all(startSize),
 
-         position: position,
 
-         anchor: Anchor.center,
 
-       ) {
 
-     // All bubbles have the same target size regardless of type
 
-     _targetSize = maxSize;
 
-   }
 
-   @override
 
-   Future<void> onLoad() async {
 
-     // Choose random bubble image and set corresponding color
 
-     final bubbleData = _getRandomBubbleData();
 
-     bubbleImagePath = bubbleData['path'];
 
-     bubbleColor = bubbleData['color'];
 
-     // Load the sprite
 
-     sprite = await Sprite.load(bubbleImagePath);
 
-     await super.onLoad();
 
-     // Add circular collision hitbox - will be updated dynamically
 
-     add(
 
-       CircleHitbox(
 
-         radius: startSize / 2, // Start with initial size
 
-         anchor: Anchor.center,
 
-       ),
 
-     );
 
-     // Start with zero scale for smooth entrance animation
 
-     scale = Vector2.zero();
 
-     // Add smooth entrance animation
 
-     _addEntranceAnimation();
 
-     // Add varied, organic floating animations
 
-     _addRandomFloatingAnimation();
 
-     _addRandomRotationAnimation();
 
-     // Initialize random velocity for natural movement
 
-     final random = Random();
 
-     velocity = Vector2(
 
-       (random.nextDouble() - 0.5) * 20, // -10 to +10
 
-       (random.nextDouble() - 0.5) * 20, // -10 to +10
 
-     );
 
-   }
 
-   @override
 
-   void update(double dt) {
 
-     super.update(dt);
 
-     if (isPopping) return;
 
-     // Update age and lifecycle
 
-     _age += dt;
 
-     _lifecycleProgress = (_age / lifecycleDuration).clamp(0.0, 1.0);
 
-     // Update scale based on lifecycle progress - grow from startSize to maxSize
 
-     final currentScale = _lifecycleProgress * (_targetSize / startSize);
 
-     scale = Vector2.all(
 
-       1.0 + currentScale * 3.0,
 
-     ); // Scale from 1.0 to 4.0 over lifetime
 
-     // Update collision hitbox radius to match current visual size
 
-     // Use a slightly smaller radius for collision detection to prevent edge cases
 
-     final currentRadius =
 
-         ((startSize / 2) * scale.x) * 0.9; // 90% of visual size
 
-     final hitbox = children.whereType<CircleHitbox>().firstOrNull;
 
-     if (hitbox != null) {
 
-       hitbox.radius = currentRadius;
 
-     }
 
-     // Update opacity based on lifecycle (fade out towards the end)
 
-     final opacityFactor =
 
-         1.0 - (_lifecycleProgress * 0.6); // Fade to 40% opacity
 
-     opacity = opacityFactor.clamp(0.4, 1.0);
 
-     // Physics-based movement
 
-     _updatePhysics(dt);
 
-     // Keep bubble within screen bounds
 
-     _keepInBounds();
 
-     // Update collision tracking
 
-     _updateCollisionTracking(dt);
 
-     // Periodic overlap check to prevent stuck overlaps
 
-     _preventOverlapSticking(dt);
 
-     // Auto-pop when lifecycle is complete
 
-     if (_lifecycleProgress >= 1.0) {
 
-       pop(userTriggered: false);
 
-     }
 
-   }
 
-   void _updatePhysics(double dt) {
 
-     // Apply velocity to position
 
-     position.add(velocity * dt);
 
-     // Apply friction
 
-     velocity.scale(friction);
 
-     // Add periodic floating effects
 
-     _floatTimer += dt;
 
-     if (_floatTimer > 2.0) {
 
-       _floatTimer = 0.0;
 
-       final random = Random();
 
-       final floatForce = Vector2(
 
-         (random.nextDouble() - 0.5) * 8, // -4 to +4 horizontal
 
-         (random.nextDouble() - 0.5) * 8, // -4 to +4 vertical
 
-       );
 
-       velocity.add(floatForce);
 
-     }
 
-     // Clamp velocity to max speed
 
-     if (velocity.length > maxSpeed) {
 
-       velocity.normalize();
 
-       velocity.scale(maxSpeed);
 
-     }
 
-   }
 
-   void _keepInBounds() {
 
-     const margin = 60.0;
 
-     final gameSize = game.size;
 
-     // Bounce off screen edges
 
-     if (position.x < margin) {
 
-       position.x = margin;
 
-       velocity.x = velocity.x.abs(); // Bounce right
 
-     } else if (position.x > gameSize.x - margin) {
 
-       position.x = gameSize.x - margin;
 
-       velocity.x = -velocity.x.abs(); // Bounce left
 
-     }
 
-     if (position.y < margin + 100) {
 
-       // Account for UI at top
 
-       position.y = margin + 100;
 
-       velocity.y = velocity.y.abs(); // Bounce down
 
-     } else if (position.y > gameSize.y - margin) {
 
-       position.y = gameSize.y - margin;
 
-       velocity.y = -velocity.y.abs(); // Bounce up
 
-     }
 
-   }
 
-   Map<String, dynamic> _getRandomBubbleData() {
 
-     final random = Random();
 
-     final bubbleTypes = [
 
-       {'path': 'bubble_blue.png', 'color': ZenColors.defaultLink},
 
-       {'path': 'bubble_cyan.png', 'color': ZenColors.buttonBackground},
 
-       {'path': 'bubble_green.png', 'color': const Color(0xFF00FF80)},
 
-       {'path': 'bubble_purple.png', 'color': ZenColors.lightModeHover},
 
-     ];
 
-     return bubbleTypes[random.nextInt(bubbleTypes.length)];
 
-   }
 
-   void _addRandomFloatingAnimation() {
 
-     final random = Random();
 
-     // Add a small upward bias to the initial velocity
 
-     velocity.y += -5 - random.nextDouble() * 10; // -5 to -15 upward bias
 
-     // The floating effect will now be handled by the physics system
 
-     // and periodic velocity changes in the update method
 
-   }
 
-   void _addEntranceAnimation() {
 
-     // Start at scale 1.0 (startSize) and grow gradually throughout lifecycle
 
-     // The growth will be handled in the update method based on lifecycle progress
 
-     // Simple fade-in animation only
 
-     opacity = 0.0;
 
-     add(
 
-       OpacityEffect.to(
 
-         1.0,
 
-         EffectController(duration: 0.4, curve: Curves.easeOut),
 
-       ),
 
-     );
 
-   }
 
-   void _addRandomRotationAnimation() {
 
-     final random = Random();
 
-     // 30% chance for rotation animation
 
-     if (random.nextDouble() < 0.3) {
 
-       // Small random rotation between -0.2 and 0.2 radians
 
-       final rotationAngle = (random.nextDouble() - 0.5) * 0.4;
 
-       // Random rotation duration between 3 and 8 seconds
 
-       final rotationDuration = 3.0 + random.nextDouble() * 5.0;
 
-       // Random delay
 
-       final delay = random.nextDouble() * 3.0;
 
-       Future.delayed(Duration(milliseconds: (delay * 1000).round()), () {
 
-         if (isMounted) {
 
-           add(
 
-             RotateEffect.by(
 
-               rotationAngle,
 
-               EffectController(
 
-                 duration: rotationDuration,
 
-                 alternate: true,
 
-                 infinite: true,
 
-                 curve: Curves.easeInOut,
 
-               ),
 
-             ),
 
-           );
 
-         }
 
-       });
 
-     }
 
-   }
 
-   @override
 
-   bool onTapDown(TapDownEvent event) {
 
-     if (!isPopping) {
 
-       pop(userTriggered: true);
 
-     }
 
-     return true;
 
-   }
 
-   void pop({bool userTriggered = false}) {
 
-     if (isPopping) return;
 
-     isPopping = true;
 
-     poppedByUser = userTriggered;
 
-     // Create exciting multi-stage pop animation
 
-     // Stage 1: Quick expand (0.1s)
 
-     final expandEffect = ScaleEffect.to(
 
-       Vector2.all(1.8),
 
-       EffectController(duration: 0.1, curve: Curves.easeOut),
 
-     );
 
-     // Stage 2: Slight compression (0.05s)
 
-     final compressEffect = ScaleEffect.to(
 
-       Vector2.all(0.8),
 
-       EffectController(duration: 0.05, curve: Curves.easeIn),
 
-     );
 
-     // Stage 3: Final explosion (0.15s)
 
-     final explodeEffect = ScaleEffect.to(
 
-       Vector2.all(2.5),
 
-       EffectController(duration: 0.15, curve: Curves.easeOut),
 
-     );
 
-     // Fade out effect
 
-     final fadeEffect = OpacityEffect.to(
 
-       0.0,
 
-       EffectController(duration: 0.3, curve: Curves.easeIn),
 
-     );
 
-     // Rotation effect for more dynamic feel
 
-     final rotateEffect = RotateEffect.by(
 
-       0.5, // Half rotation
 
-       EffectController(duration: 0.3),
 
-     );
 
-     // Chain the scale effects
 
-     add(expandEffect);
 
-     expandEffect.onComplete = () {
 
-       if (isMounted) {
 
-         add(compressEffect);
 
-         compressEffect.onComplete = () {
 
-           if (isMounted) {
 
-             add(explodeEffect);
 
-           }
 
-         };
 
-       }
 
-     };
 
-     add(fadeEffect);
 
-     add(rotateEffect);
 
-     // Create enhanced particle effects
 
-     _createExcitingPopParticles();
 
-     // Notify parent and remove bubble
 
-     onPop?.call(this, userTriggered);
 
-     Future.delayed(const Duration(milliseconds: 300), () {
 
-       if (isMounted) {
 
-         removeFromParent();
 
-       }
 
-     });
 
-   }
 
-   void _createExcitingPopParticles() {
 
-     final random = Random();
 
-     const particleCount = 12; // Further reduced for better performance
 
-     // Use object pooling approach - create fewer, simpler particles
 
-     for (int i = 0; i < particleCount; i++) {
 
-       final angle = (i / particleCount) * 2 * pi;
 
-       // Simplified particle creation
 
-       final baseSpeed = 50 + random.nextDouble() * 30; // 50-80 speed
 
-       final particleVelocity = Vector2(
 
-         cos(angle) * baseSpeed,
 
-         sin(angle) * baseSpeed,
 
-       );
 
-       // Fixed particle size for performance
 
-       const particleSize = 3.0;
 
-       // Simplified particle color
 
-       final particleColor = bubbleColor.withValues(alpha: 0.8);
 
-       final particle = CircleComponent(
 
-         radius: particleSize,
 
-         position: position.clone(),
 
-         paint:
 
-             Paint()
 
-               ..color = particleColor
 
-               ..style = PaintingStyle.fill,
 
-       );
 
-       parent?.add(particle);
 
-       // Combine effects for better performance
 
-       final combinedEffect = MoveEffect.by(
 
-         particleVelocity + Vector2(0, 40), // Combined movement + gravity
 
-         EffectController(
 
-           duration: 0.3,
 
-           curve: Curves.easeOut,
 
-         ), // Faster animation
 
-       );
 
-       particle.add(combinedEffect);
 
-       // Single fade effect instead of multiple effects
 
-       particle.add(
 
-         OpacityEffect.to(
 
-           0.0,
 
-           EffectController(duration: 0.3, curve: Curves.easeIn),
 
-         ),
 
-       );
 
-       // Faster cleanup
 
-       Future.delayed(const Duration(milliseconds: 300), () {
 
-         if (particle.isMounted) {
 
-           particle.removeFromParent();
 
-         }
 
-       });
 
-     }
 
-   }
 
-   // Track if shake effect is currently active to prevent overlapping effects
 
-   bool _isShakeEffectActive = false;
 
-   /// Add shake effect to this bubble (called when phone is shaken)
 
-   /// Fixed version that only adds visual effects without movement or size flickering
 
-   void addShakeEffect() {
 
-     if (isPopping || _isShakeEffectActive) return;
 
-     _isShakeEffectActive = true;
 
-     final random = Random();
 
-     // Store current scale to return to it after effect
 
-     final currentScale = scale.clone();
 
-     final targetScale = currentScale * 1.1; // Only 10% bigger than current size
 
-     // Use absolute scaling to prevent flickering with lifecycle scaling
 
-     final scaleUpEffect = ScaleEffect.to(
 
-       targetScale,
 
-       EffectController(
 
-         duration: 0.1, // Very short duration
 
-         curve: Curves.easeOut,
 
-       ),
 
-     );
 
-     final scaleDownEffect = ScaleEffect.to(
 
-       currentScale,
 
-       EffectController(
 
-         duration: 0.1, // Very short duration
 
-         curve: Curves.easeIn,
 
-       ),
 
-     );
 
-     // Add slight rotation effect - reduced intensity
 
-     final rotateEffect = RotateEffect.by(
 
-       (random.nextDouble() - 0.5) * 0.15, // Further reduced rotation
 
-       EffectController(
 
-         duration: 0.15,
 
-         alternate: true,
 
-         curve: Curves.easeInOut,
 
-       ),
 
-     );
 
-     // Chain the scale effects
 
-     add(scaleUpEffect);
 
-     scaleUpEffect.onComplete = () {
 
-       if (isMounted && !isPopping) {
 
-         add(scaleDownEffect);
 
-       }
 
-     };
 
-     add(rotateEffect);
 
-     // Set completion callback to reset shake effect flag
 
-     Future.delayed(const Duration(milliseconds: 200), () {
 
-       _isShakeEffectActive = false;
 
-     });
 
-   }
 
-   // Collision detection methods
 
-   @override
 
-   bool onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
 
-     super.onCollision(intersectionPoints, other);
 
-     if (other is Bubble && !isPopping && !other.isPopping) {
 
-       _handleBubbleCollision(other, intersectionPoints);
 
-     }
 
-     return true;
 
-   }
 
-   void _handleBubbleCollision(Bubble other, Set<Vector2> intersectionPoints) {
 
-     if (intersectionPoints.isEmpty) return;
 
-     // Track collision for triple collision rule
 
-     _incrementCollisionCount();
 
-     other._incrementCollisionCount();
 
-     // Calculate collision direction
 
-     final direction = (position - other.position);
 
-     final distance = direction.length;
 
-     // Prevent division by zero
 
-     if (distance < 0.1) {
 
-       // If bubbles are too close, separate them with a random direction
 
-       final random = Random();
 
-       direction.setFrom(
 
-         Vector2(
 
-           (random.nextDouble() - 0.5) * 2,
 
-           (random.nextDouble() - 0.5) * 2,
 
-         ),
 
-       );
 
-     }
 
-     direction.normalize();
 
-     // Calculate required separation based on current bubble sizes
 
-     final thisRadius = (startSize / 2) * scale.x;
 
-     final otherRadius = (startSize / 2) * other.scale.x;
 
-     final requiredDistance =
 
-         thisRadius + otherRadius + 10.0; // Increased padding to 10px
 
-     // If bubbles are overlapping, separate them immediately with more aggressive separation
 
-     if (distance < requiredDistance) {
 
-       final separationNeeded =
 
-           requiredDistance - distance + 5.0; // Extra 5px buffer
 
-       final separationVector = direction * (separationNeeded / 2);
 
-       // Move both bubbles away from each other
 
-       position.add(separationVector);
 
-       other.position.sub(separationVector);
 
-       // Ensure bubbles stay within bounds after separation
 
-       _keepInBounds();
 
-       other._keepInBounds();
 
-       // Additional check: if still overlapping after separation, apply emergency separation
 
-       final newDistance = (position - other.position).length;
 
-       if (newDistance < requiredDistance) {
 
-         final emergencyVector =
 
-             direction * ((requiredDistance - newDistance + 10.0) / 2);
 
-         position.add(emergencyVector);
 
-         other.position.sub(emergencyVector);
 
-         _keepInBounds();
 
-         other._keepInBounds();
 
-       }
 
-     }
 
-     // Apply collision response - bubbles bounce off each other
 
-     final collisionForce = 25.0; // Reduced from 30.0 for gentler bouncing
 
-     velocity.add(direction * collisionForce);
 
-     other.velocity.add(direction * -collisionForce);
 
-     // Apply stronger damping to prevent infinite acceleration
 
-     velocity.scale(0.6); // Increased damping from 0.7 to 0.6
 
-     other.velocity.scale(0.6);
 
-   }
 
-   /// Update collision tracking and handle grace period reset
 
-   void _updateCollisionTracking(double dt) {
 
-     final currentTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
 
-     // Reset collision count if grace period has passed
 
-     if (currentTime - _lastCollisionTime > collisionGracePeriod) {
 
-       _collisionCount = 0;
 
-     }
 
-   }
 
-   /// Increment collision count and check for auto-pop
 
-   void _incrementCollisionCount() {
 
-     if (isPopping) return;
 
-     _collisionCount++;
 
-     _lastCollisionTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
 
-     // Auto-pop after max collisions
 
-     if (_collisionCount >= maxCollisions) {
 
-       pop(userTriggered: false); // Auto-pop due to collision rule
 
-     }
 
-   }
 
-   /// Get current collision count (for debugging/testing)
 
-   int get collisionCount => _collisionCount;
 
-   /// Apply tilt force to bubble based on device orientation
 
-   void applyTiltForce(double tiltStrength) {
 
-     if (isPopping) return;
 
-     // Reduced tilt force to prevent excessive movement during shaking
 
-     // Apply force opposite to tilt direction
 
-     // If device tilts right, bubbles move left and vice versa
 
-     final tiltForce = -tiltStrength * 8.0; // Reduced from 15.0 to 8.0
 
-     velocity.x += tiltForce;
 
-     // Add slight vertical component for more natural movement
 
-     velocity.y += tiltForce * 0.2; // Reduced from 0.3 to 0.2
 
-   }
 
-   double _lastOverlapCheck = 0.0;
 
-   static const double overlapCheckInterval = 0.1; // Check every 100ms
 
-   /// Periodic check to prevent bubbles from getting stuck overlapping
 
-   void _preventOverlapSticking(double dt) {
 
-     _lastOverlapCheck += dt;
 
-     // Only check periodically to avoid performance issues
 
-     if (_lastOverlapCheck < overlapCheckInterval) return;
 
-     _lastOverlapCheck = 0.0;
 
-     // Get all other bubbles from the game
 
-     final allBubbles = game.children.whereType<Bubble>();
 
-     for (final otherBubble in allBubbles) {
 
-       if (otherBubble == this || otherBubble.isPopping || isPopping) continue;
 
-       final distance = position.distanceTo(otherBubble.position);
 
-       final thisRadius = (startSize / 2) * scale.x;
 
-       final otherRadius = (startSize / 2) * otherBubble.scale.x;
 
-       final requiredDistance = thisRadius + otherRadius + 8.0; // 8px padding
 
-       // If bubbles are overlapping, apply gentle separation force
 
-       if (distance < requiredDistance && distance > 0.1) {
 
-         final direction = (position - otherBubble.position).normalized();
 
-         final separationForce =
 
-             (requiredDistance - distance) * 0.5; // Gentle force
 
-         // Apply separation force to velocities instead of direct position changes
 
-         // to make the movement more natural
 
-         velocity.add(direction * separationForce);
 
-         otherBubble.velocity.add(direction * -separationForce);
 
-       }
 
-     }
 
-   }
 
- }
 
 
  |