zeroed-some/cob / 5fa41f1

Browse files

make it quirky (more creative obstacles)

Authored by espadonne
SHA
5fa41f170bc27c2a334db22437aca4c397bc60e4
Parents
9d78b64
Tree
f4fc13f

2 changed files

StatusFile+-
M js/entities.js 397 84
M js/game.js 103 59
js/entities.jsmodified
@@ -16,64 +16,64 @@ class Spider {
16
     this.munchCooldown = 0
16
     this.munchCooldown = 0
17
   }
17
   }
18
 
18
 
19
-jump(targetX, targetY) {
19
+  jump(targetX, targetY) {
20
-        if (!this.canJump) return;
20
+    if (!this.canJump) return;
21
-        
21
+    
22
-        let direction = createVector(targetX - this.pos.x, targetY - this.pos.y);
22
+    let direction = createVector(targetX - this.pos.x, targetY - this.pos.y);
23
-        let clickDistance = direction.mag();
23
+    let clickDistance = direction.mag();
24
-        direction.normalize();
24
+    direction.normalize();
25
-        
25
+    
26
-        // Scale jump power based on click distance (closer clicks = smaller jumps)
26
+    // Scale jump power based on click distance (closer clicks = smaller jumps)
27
-        let actualJumpPower = map(clickDistance, 0, 200, 3, this.jumpPower);
27
+    let actualJumpPower = map(clickDistance, 0, 200, 3, this.jumpPower);
28
-        actualJumpPower = constrain(actualJumpPower, 3, this.jumpPower);
28
+    actualJumpPower = constrain(actualJumpPower, 3, this.jumpPower);
29
-        direction.mult(actualJumpPower);
29
+    direction.mult(actualJumpPower);
30
-        
30
+    
31
-        this.vel = direction;
31
+    this.vel = direction;
32
-        this.isAirborne = true;
32
+    this.isAirborne = true;
33
-        this.canJump = false;
33
+    this.canJump = false;
34
-        this.lastAnchorPoint = this.pos.copy();
34
+    this.lastAnchorPoint = this.pos.copy();
35
+    
36
+    // Check if we're jumping off a web strand
37
+    for (let strand of webStrands) {
38
+      if (strand === currentStrand) continue;
39
+      
40
+      if (this.checkStrandCollision(strand)) {
41
+        // Much simpler shimmy detection based on actual jump power used
42
+        let isShimmy = actualJumpPower < 6; // If we used less than half power, it's a shimmy
35
         
43
         
36
-        // Check if we're jumping off a web strand
44
+        // Apply appropriate recoil based on movement type
37
-        for (let strand of webStrands) {
45
+        if (isShimmy) {
38
-            if (strand === currentStrand) continue;
46
+          // Trigger shimmy visual effect
39
-            
47
+          this.shimmyEffect = 20;
40
-            if (this.checkStrandCollision(strand)) {
48
+          
41
-                // Much simpler shimmy detection based on actual jump power used
49
+          // NO recoil at all for shimmying - just tiny vibration
42
-                let isShimmy = actualJumpPower < 6; // If we used less than half power, it's a shimmy
50
+          strand.vibrate(0.3);
43
-                
51
+          
44
-                // Apply appropriate recoil based on movement type
52
+          // Tiny yellow particles
45
-                if (isShimmy) {
53
+          let p = new Particle(this.pos.x, this.pos.y);
46
-                    // Trigger shimmy visual effect
54
+          p.color = color(255, 255, 100, 80);
47
-                    this.shimmyEffect = 20;
55
+          p.vel = createVector(random(-0.3, 0.3), random(-0.3, 0.3));
48
-                    
56
+          p.size = 2;
49
-                    // NO recoil at all for shimmying - just tiny vibration
57
+          particles.push(p);
50
-                    strand.vibrate(0.3);
58
+        } else {
51
-                    
59
+          // Scale recoil based on actual jump power
52
-                    // Tiny yellow particles
60
+          let recoilForce = -(actualJumpPower / this.jumpPower) * 0.08; // Scale by power ratio
53
-                    let p = new Particle(this.pos.x, this.pos.y);
61
+          strand.applyRecoil(recoilForce);
54
-                    p.color = color(255, 255, 100, 80);
62
+          
55
-                    p.vel = createVector(random(-0.3, 0.3), random(-0.3, 0.3));
63
+          // Create particles only for real jumps
56
-                    p.size = 2;
64
+          for (let i = 0; i < 2; i++) {
57
-                    particles.push(p);
65
+            let p = new Particle(this.pos.x, this.pos.y);
58
-                } else {
66
+            p.color = color(255, 255, 255, 120);
59
-                    // Scale recoil based on actual jump power
67
+            p.vel = createVector(random(-0.8, 0.8), random(1, 2));
60
-                    let recoilForce = -(actualJumpPower / this.jumpPower) * 0.08; // Scale by power ratio
68
+            p.size = 3;
61
-                    strand.applyRecoil(recoilForce);
69
+            particles.push(p);
62
-                    
70
+          }
63
-                    // Create particles only for real jumps
64
-                    for (let i = 0; i < 2; i++) {
65
-                        let p = new Particle(this.pos.x, this.pos.y);
66
-                        p.color = color(255, 255, 255, 120);
67
-                        p.vel = createVector(random(-0.8, 0.8), random(1, 2));
68
-                        p.size = 3;
69
-                        particles.push(p);
70
-                    }
71
-                }
72
-                
73
-                break;
74
-            }
75
         }
71
         }
72
+        
73
+        break;
74
+      }
76
     }
75
     }
76
+  }
77
     
77
     
78
   munch () {
78
   munch () {
79
     if (this.munchCooldown > 0) return
79
     if (this.munchCooldown > 0) return
@@ -559,14 +559,46 @@ class Fly {
559
 
559
 
560
 class Obstacle {
560
 class Obstacle {
561
   constructor (x, y, radius, type) {
561
   constructor (x, y, radius, type) {
562
+    // Store original position for drift tracking
563
+    this.originalX = x
564
+    this.originalY = y
562
     this.x = x
565
     this.x = x
563
     this.y = y
566
     this.y = y
564
     this.radius = radius
567
     this.radius = radius
565
-    this.type = type || (random() < 0.5 ? 'branch' : 'leaf')
568
+    this.type = type || 'leaf'
566
     this.rotation = random(TWO_PI)
569
     this.rotation = random(TWO_PI)
567
     this.leafPoints = []
570
     this.leafPoints = []
568
-
571
+    
569
-    if (this.type === 'leaf') {
572
+    // Movement properties for all types
573
+    this.bobOffset = random(TWO_PI)
574
+    this.bobSpeed = random(0.02, 0.04)
575
+    this.bobAmount = 0
576
+    
577
+    // Type-specific initialization
578
+    if (this.type === 'balloon') {
579
+      this.bobAmount = 8 // Balloons bob more
580
+      this.balloonColors = [
581
+        color(255, 100, 100), // Red
582
+        color(100, 200, 255), // Blue  
583
+        color(255, 200, 100)  // Yellow
584
+      ]
585
+      this.balloonColor = random(this.balloonColors)
586
+      this.stringWave = 0
587
+      this.antLegPhase = random(TWO_PI)
588
+      
589
+    } else if (this.type === 'beetle') {
590
+      this.bobAmount = 4
591
+      this.driftSpeed = random(0.15, 0.35)
592
+      this.driftAngle = random(TWO_PI)
593
+      this.driftChangeRate = random(0.005, 0.015)
594
+      this.wingPhase = random(TWO_PI)
595
+      this.beetleColor = random() < 0.5 ? 
596
+        color(20, 60, 20) : // Dark green
597
+        color(40, 20, 60)   // Purple
598
+      this.driftDistance = 0 // Track total drift
599
+        
600
+    } else if (this.type === 'leaf') {
601
+      this.bobAmount = 2 // Leaves bob slightly
570
       let numPoints = 8
602
       let numPoints = 8
571
       for (let i = 0; i < numPoints; i++) {
603
       for (let i = 0; i < numPoints; i++) {
572
         let angle = (TWO_PI / numPoints) * i
604
         let angle = (TWO_PI / numPoints) * i
@@ -574,44 +606,292 @@ class Obstacle {
574
         if (i === 0 || i === numPoints / 2) r = radius * 1.3
606
         if (i === 0 || i === numPoints / 2) r = radius * 1.3
575
         this.leafPoints.push({ angle: angle, radius: r })
607
         this.leafPoints.push({ angle: angle, radius: r })
576
       }
608
       }
609
+    } else if (this.type === 'branch') {
610
+      // Keep for backwards compatibility
611
+      this.bobAmount = 0
612
+    }
613
+  }
614
+  
615
+  update() {
616
+    // Bobbing motion for all types
617
+    let bob = sin(frameCount * this.bobSpeed + this.bobOffset) * this.bobAmount
618
+    this.y = this.originalY + bob
619
+    
620
+    // Beetle-specific drift
621
+    if (this.type === 'beetle') {
622
+      // Store initial position if not set
623
+      if (!this.initialX) {
624
+        this.initialX = this.x
625
+        this.initialY = this.y
626
+      }
627
+      
628
+      // Slowly change drift direction using Perlin noise
629
+      this.driftAngle += (noise(frameCount * this.driftChangeRate, this.originalX * 0.01) - 0.5) * 0.1
630
+      
631
+      // Apply drift to original position
632
+      this.originalX += cos(this.driftAngle) * this.driftSpeed
633
+      this.originalY += sin(this.driftAngle) * this.driftSpeed * 0.5
634
+      
635
+      // Calculate total drift distance from initial position
636
+      this.driftDistance = dist(this.originalX, this.originalY, this.initialX, this.initialY)
637
+      
638
+      // Keep beetles on screen with soft boundaries
639
+      if (this.originalX < 80) {
640
+        this.driftAngle = random(-PI/4, PI/4)
641
+        this.originalX = 80
642
+      }
643
+      if (this.originalX > width - 80) {
644
+        this.driftAngle = random(3*PI/4, 5*PI/4)
645
+        this.originalX = width - 80
646
+      }
647
+      if (this.originalY < 80) {
648
+        this.driftAngle = random(-3*PI/4, -PI/4)
649
+        this.originalY = 80
650
+      }
651
+      if (this.originalY > height - 150) {
652
+        this.driftAngle = random(PI/4, 3*PI/4)
653
+        this.originalY = height - 150
654
+      }
655
+      
656
+      // Update actual position (with bob already applied to y)
657
+      this.x = this.originalX
658
+      
659
+      // Check if beetle has drifted too far and break attached strands
660
+      if (this.driftDistance > 100) {
661
+        this.breakAttachedStrands()
662
+      }
663
+    }
664
+    
665
+    // Update animation phases
666
+    if (this.type === 'balloon') {
667
+      this.stringWave = sin(frameCount * 0.05 + this.bobOffset) * 0.1
668
+      this.antLegPhase += 0.1
669
+    } else if (this.type === 'beetle') {
670
+      this.wingPhase += 0.15
671
+    }
672
+    
673
+    // For all moving obstacles, update any attached web strands
674
+    if (this.bobAmount > 0 || this.type === 'beetle') {
675
+      this.updateAttachedStrands()
676
+    }
677
+  }
678
+  
679
+  updateAttachedStrands() {
680
+    // Update web strands that are connected to this obstacle
681
+    for (let strand of webStrands) {
682
+      // Check if strand starts at this obstacle
683
+      if (dist(strand.start.x, strand.start.y, this.x, this.y) < this.radius + 10) {
684
+        strand.start.x = this.x
685
+        strand.start.y = this.y
686
+        if (strand.path && strand.path.length > 0) {
687
+          strand.path[0].x = this.x
688
+          strand.path[0].y = this.y
689
+        }
690
+      }
691
+      
692
+      // Check if strand ends at this obstacle
693
+      if (strand.end && dist(strand.end.x, strand.end.y, this.x, this.y) < this.radius + 10) {
694
+        strand.end.x = this.x
695
+        strand.end.y = this.y
696
+        if (strand.path && strand.path.length > 0) {
697
+          strand.path[strand.path.length - 1].x = this.x
698
+          strand.path[strand.path.length - 1].y = this.y
699
+        }
700
+      }
701
+    }
702
+  }
703
+  
704
+  breakAttachedStrands() {
705
+    // Break any strands attached to this beetle that has drifted too far
706
+    for (let strand of webStrands) {
707
+      let attachedToStart = dist(strand.start.x, strand.start.y, this.x, this.y) < this.radius + 10
708
+      let attachedToEnd = strand.end && dist(strand.end.x, strand.end.y, this.x, this.y) < this.radius + 10
709
+      
710
+      if (attachedToStart || attachedToEnd) {
711
+        // Mark strand as broken
712
+        strand.broken = true
713
+        
714
+        // Create dramatic snap particles
715
+        let snapX = attachedToStart ? strand.start.x : strand.end.x
716
+        let snapY = attachedToStart ? strand.start.y : strand.end.y
717
+        
718
+        // Red/pink particles for the snap
719
+        for (let i = 0; i < 8; i++) {
720
+          let p = new Particle(snapX, snapY)
721
+          p.color = color(255, random(100, 200), random(100, 150))
722
+          p.vel = createVector(random(-5, 5), random(-5, 2))
723
+          p.size = random(4, 8)
724
+          particles.push(p)
725
+        }
726
+        
727
+        // White strand particles
728
+        for (let i = 0; i < 4; i++) {
729
+          let p = new Particle(snapX, snapY)
730
+          p.color = color(255, 255, 255)
731
+          p.vel = createVector(random(-3, 3), random(-3, 0))
732
+          p.size = 3
733
+          particles.push(p)
734
+        }
735
+        
736
+        // Reset beetle drift after breaking strands
737
+        this.initialX = this.x
738
+        this.initialY = this.y
739
+        this.driftDistance = 0
740
+      }
577
     }
741
     }
578
   }
742
   }
579
 
743
 
580
   display () {
744
   display () {
581
     push()
745
     push()
582
     translate(this.x, this.y)
746
     translate(this.x, this.y)
583
-    rotate(this.rotation)
747
+    
584
-
748
+    if (this.type === 'balloon') {
585
-    if (this.type === 'branch') {
749
+      // Balloon with ant in basket!
586
-      if (gamePhase === 'NIGHT') {
750
+      push()
587
-        stroke(40, 20, 0)
751
+      
588
-        fill(50, 25, 5)
752
+      // String first (behind balloon)
589
-      } else {
753
+      stroke(80, 60, 40)
590
-        stroke(101, 67, 33)
754
+      strokeWeight(1)
591
-        fill(139, 90, 43)
755
+      noFill()
756
+      beginShape()
757
+      for (let i = 0; i <= 10; i++) {
758
+        let t = i / 10
759
+        let stringX = sin(t * PI * 2 + this.stringWave) * 3
760
+        let stringY = t * 40 + this.radius
761
+        curveVertex(stringX, stringY)
592
       }
762
       }
593
-      strokeWeight(3)
763
+      endShape()
594
-
764
+      
765
+      // Balloon shadow
766
+      noStroke()
767
+      fill(0, 0, 0, 30)
768
+      ellipse(5, 5, this.radius * 2.2, this.radius * 2.5)
769
+      
770
+      // Main balloon
771
+      noStroke()
772
+      fill(red(this.balloonColor), green(this.balloonColor), blue(this.balloonColor), 150)
773
+      ellipse(0, 0, this.radius * 2.2, this.radius * 2.5)
774
+      fill(red(this.balloonColor) + 30, green(this.balloonColor) + 30, blue(this.balloonColor) + 30, 200)
775
+      ellipse(-this.radius * 0.3, -this.radius * 0.3, this.radius * 1.2, this.radius * 1.4)
776
+      // Highlight
777
+      fill(255, 255, 255, 120)
778
+      ellipse(-this.radius * 0.4, -this.radius * 0.5, this.radius * 0.5, this.radius * 0.6)
779
+      
780
+      // Basket
781
+      translate(0, this.radius + 10)
782
+      fill(139, 90, 43)
783
+      stroke(100, 60, 20)
784
+      strokeWeight(1)
785
+      // Trapezoid basket
786
+      beginShape()
787
+      vertex(-8, 0)
788
+      vertex(8, 0)
789
+      vertex(6, 10)
790
+      vertex(-6, 10)
791
+      endShape(CLOSE)
792
+      // Basket weave pattern
793
+      stroke(100, 60, 20, 100)
794
+      for (let i = -6; i < 6; i += 3) {
795
+        line(i, 2, i, 8)
796
+      }
797
+      for (let i = 2; i < 8; i += 3) {
798
+        line(-6, i, 6, i)
799
+      }
800
+      
801
+      // Ant in basket
802
+      translate(0, 5)
803
+      fill(20)
804
+      noStroke()
805
+      // Ant body
806
+      ellipse(0, 0, 6, 4) // Head
807
+      ellipse(0, 3, 5, 6) // Thorax
808
+      ellipse(0, 7, 7, 9) // Abdomen
809
+      // Ant legs (animated)
810
+      stroke(20)
811
+      strokeWeight(0.5)
812
+      for (let i = 0; i < 3; i++) {
813
+        let legAngle = this.antLegPhase + i * 0.5
814
+        let legSpread = 4 + sin(legAngle) * 2
815
+        line(-2, 3 + i * 2, -legSpread, 3 + i * 2)
816
+        line(2, 3 + i * 2, legSpread, 3 + i * 2)
817
+      }
818
+      // Antennae
819
+      line(-1, -1, -3, -3)
820
+      line(1, -1, 3, -3)
821
+      
822
+      pop()
823
+      
824
+    } else if (this.type === 'beetle') {
825
+      // Big beetle!
595
       push()
826
       push()
596
-      strokeWeight(this.radius / 3)
827
+      rotate(this.rotation)
597
-      line(-this.radius, 0, this.radius, 0)
828
+      
598
-
829
+      // Shadow
830
+      noStroke()
831
+      fill(0, 0, 0, 40)
832
+      ellipse(3, 3, this.radius * 1.8, this.radius * 2.2)
833
+      
834
+      // Wings (if flying at night)
835
+      if (gamePhase === 'NIGHT') {
836
+        push()
837
+        fill(255, 255, 255, 100 + sin(this.wingPhase) * 50)
838
+        noStroke()
839
+        let wingSpread = sin(this.wingPhase) * 15
840
+        ellipse(-wingSpread, 0, 20, 12)
841
+        ellipse(wingSpread, 0, 20, 12)
842
+        pop()
843
+      }
844
+      
845
+      // Main beetle body
846
+      fill(red(this.beetleColor), green(this.beetleColor), blue(this.beetleColor))
847
+      stroke(0)
599
       strokeWeight(2)
848
       strokeWeight(2)
600
-      line(-this.radius / 2, 0, -this.radius / 2 - 10, -10)
849
+      ellipse(0, 0, this.radius * 1.6, this.radius * 2)
601
-      line(this.radius / 3, 0, this.radius / 3 + 8, -8)
850
+      
602
-      line(0, 0, 5, -15)
851
+      // Shell split line
603
-
852
+      stroke(0)
604
-      stroke(80, 50, 20, 100)
605
       strokeWeight(1)
853
       strokeWeight(1)
606
-      for (let i = -this.radius; i < this.radius; i += 5) {
854
+      line(0, -this.radius, 0, this.radius)
607
-        line(i, -2, i + 2, 2)
855
+      
856
+      // Head
857
+      fill(10)
858
+      ellipse(0, -this.radius * 0.8, this.radius * 0.8, this.radius * 0.6)
859
+      
860
+      // Spots/pattern
861
+      noStroke()
862
+      fill(0, 0, 0, 80)
863
+      ellipse(-this.radius * 0.3, 0, this.radius * 0.4)
864
+      ellipse(this.radius * 0.3, -this.radius * 0.2, this.radius * 0.3)
865
+      ellipse(this.radius * 0.2, this.radius * 0.4, this.radius * 0.35)
866
+      ellipse(-this.radius * 0.25, this.radius * 0.3, this.radius * 0.25)
867
+      
868
+      // Legs
869
+      stroke(0)
870
+      strokeWeight(2)
871
+      for (let i = 0; i < 3; i++) {
872
+        let legY = -this.radius * 0.3 + i * this.radius * 0.3
873
+        let legMove = sin(this.wingPhase * 2 + i) * 2
874
+        line(-this.radius * 0.8, legY, -this.radius * 1.2 + legMove, legY + 5)
875
+        line(this.radius * 0.8, legY, this.radius * 1.2 - legMove, legY + 5)
608
       }
876
       }
609
-      pop()
877
+      
610
-
878
+      // Antennae
879
+      strokeWeight(1)
880
+      line(-3, -this.radius * 1.1, -8, -this.radius * 1.4)
881
+      line(3, -this.radius * 1.1, 8, -this.radius * 1.4)
882
+      
883
+      // Eyes
884
+      fill(255, 0, 0)
611
       noStroke()
885
       noStroke()
612
-      fill(255, 255, 255, 30)
886
+      ellipse(-5, -this.radius * 0.7, 4)
613
-      ellipse(0, 0, this.radius * 2)
887
+      ellipse(5, -this.radius * 0.7, 4)
888
+      
889
+      pop()
890
+      
614
     } else if (this.type === 'leaf') {
891
     } else if (this.type === 'leaf') {
892
+      // Original leaf code
893
+      rotate(this.rotation)
894
+      
615
       if (gamePhase === 'NIGHT') {
895
       if (gamePhase === 'NIGHT') {
616
         fill(20, 40, 20)
896
         fill(20, 40, 20)
617
         stroke(10, 20, 10)
897
         stroke(10, 20, 10)
@@ -646,6 +926,39 @@ class Obstacle {
646
       line(0, 0, this.radius / 2, -this.radius / 2)
926
       line(0, 0, this.radius / 2, -this.radius / 2)
647
       line(0, 0, -this.radius / 2, this.radius / 2)
927
       line(0, 0, -this.radius / 2, this.radius / 2)
648
       line(0, 0, this.radius / 2, this.radius / 2)
928
       line(0, 0, this.radius / 2, this.radius / 2)
929
+      
930
+    } else if (this.type === 'branch') {
931
+      // Keep old branch code for backwards compatibility
932
+      rotate(this.rotation)
933
+      
934
+      if (gamePhase === 'NIGHT') {
935
+        stroke(40, 20, 0)
936
+        fill(50, 25, 5)
937
+      } else {
938
+        stroke(101, 67, 33)
939
+        fill(139, 90, 43)
940
+      }
941
+      strokeWeight(3)
942
+
943
+      push()
944
+      strokeWeight(this.radius / 3)
945
+      line(-this.radius, 0, this.radius, 0)
946
+
947
+      strokeWeight(2)
948
+      line(-this.radius / 2, 0, -this.radius / 2 - 10, -10)
949
+      line(this.radius / 3, 0, this.radius / 3 + 8, -8)
950
+      line(0, 0, 5, -15)
951
+
952
+      stroke(80, 50, 20, 100)
953
+      strokeWeight(1)
954
+      for (let i = -this.radius; i < this.radius; i += 5) {
955
+        line(i, -2, i + 2, 2)
956
+      }
957
+      pop()
958
+
959
+      noStroke()
960
+      fill(255, 255, 255, 30)
961
+      ellipse(0, 0, this.radius * 2)
649
     }
962
     }
650
 
963
 
651
     pop()
964
     pop()
@@ -734,4 +1047,4 @@ class Particle {
734
   isDead () {
1047
   isDead () {
735
     return this.lifetime <= 0
1048
     return this.lifetime <= 0
736
   }
1049
   }
737
-}
1050
+}
js/game.jsmodified
@@ -114,77 +114,120 @@ function setup() {
114
             homeBranchLength * t : 
114
             homeBranchLength * t : 
115
             width - homeBranchLength * t;
115
             width - homeBranchLength * t;
116
         let y = homeBranchY + sin(t * PI) * 10; // Slight curve
116
         let y = homeBranchY + sin(t * PI) * 10; // Slight curve
117
-        obstacles.push(new Obstacle(x, y, 20, 'branch'));
117
+        obstacles.push(new Obstacle(x, y, 20, 'leaf')); // Use leaf as invisible anchor
118
     }
118
     }
119
     
119
     
120
-    // Create obstacles with better distribution
120
+    // Create fewer, bigger, quirkier obstacles
121
-    let numObstacles = Math.floor((width * height) / 60000);
121
+    let numObstacles = Math.floor((width * height) / 120000); // Much fewer
122
-    numObstacles = constrain(numObstacles, 10, 25);
122
+    numObstacles = constrain(numObstacles, 8, 15);
123
     
123
     
124
-    // Divide screen into zones for better distribution
124
+    // Create ant balloons (2-3)
125
-    let zones = [
125
+    let numBalloons = Math.floor(random(2, 4));
126
-        { minY: 50, maxY: height * 0.3 },        // Top zone
126
+    for (let i = 0; i < numBalloons; i++) {
127
-        { minY: height * 0.3, maxY: height * 0.6 }, // Middle zone
127
+        let attempts = 0;
128
-        { minY: height * 0.6, maxY: height - 100 }  // Bottom zone
128
+        let placed = false;
129
-    ];
129
+        
130
-    
130
+        while (!placed && attempts < 30) {
131
-    let obstaclesPerZone = Math.ceil(numObstacles / 3);
131
+            let x = random(120, width - 120);
132
-    
132
+            let y = random(80, height * 0.5); // Balloons float in upper half
133
-    for (let zone of zones) {
133
+            let radius = random(40, 55); // Bigger
134
-        for (let i = 0; i < obstaclesPerZone; i++) {
135
-            let attempts = 0;
136
-            let placed = false;
137
             
134
             
138
-            while (!placed && attempts < 20) {
135
+            let valid = true;
139
-                let x = random(80, width - 80);
136
+            // Check distance from other obstacles
140
-                let y = random(zone.minY, zone.maxY);
137
+            for (let obstacle of obstacles) {
141
-                let radius = random(25, 45);
138
+                if (dist(x, y, obstacle.x, obstacle.y) < radius + obstacle.radius + 80) {
142
-                let type = random() < 0.6 ? 'branch' : 'leaf';
139
+                    valid = false;
143
-                
140
+                    break;
144
-                let valid = true;
145
-                for (let obstacle of obstacles) {
146
-                    if (dist(x, y, obstacle.x, obstacle.y) < radius + obstacle.radius + 40) {
147
-                        valid = false;
148
-                        break;
149
-                    }
150
                 }
141
                 }
151
-                // Avoid overlapping the home branch body (transform point into branch frame)
142
+            }
152
-                if (valid) {
143
+            
153
-                    const ca = Math.cos(window.homeBranch.angle);
144
+            // Check distance from home branch
154
-                    const sa = Math.sin(window.homeBranch.angle);
145
+            if (valid && window.homeBranch) {
155
-                    const relY = y - window.homeBranch.y; // translate to branch's local origin
146
+                let branchY = window.homeBranch.y;
156
-                    const xr = x * ca + relY * sa;        // rotate into branch frame
147
+                if (Math.abs(y - branchY) < radius + 50) {
157
-                    const yr = -x * sa + relY * ca;
148
+                    valid = false;
158
-                    const minX = Math.min(window.homeBranch.startX, window.homeBranch.endX) - radius - 8;
149
+                }
159
-                    const maxX = Math.max(window.homeBranch.startX, window.homeBranch.endX) + radius + 8;
150
+            }
160
-                    const halfThickness = window.homeBranch.thickness + radius + 6;
151
+            
161
-                    if (xr >= minX && xr <= maxX && Math.abs(yr) <= halfThickness) {
152
+            if (valid) {
162
-                        valid = false; // too close to the branch hull
153
+                obstacles.push(new Obstacle(x, y, radius, 'balloon'));
163
-                    }
154
+                placed = true;
155
+            }
156
+            attempts++;
157
+        }
158
+    }
159
+    
160
+    // Create beetles (2-3)
161
+    let numBeetles = Math.floor(random(2, 4));
162
+    for (let i = 0; i < numBeetles; i++) {
163
+        let attempts = 0;
164
+        let placed = false;
165
+        
166
+        while (!placed && attempts < 30) {
167
+            let x = random(100, width - 100);
168
+            let y = random(height * 0.3, height * 0.7); // Middle areas
169
+            let radius = random(35, 50);
170
+            
171
+            let valid = true;
172
+            for (let obstacle of obstacles) {
173
+                if (dist(x, y, obstacle.x, obstacle.y) < radius + obstacle.radius + 70) {
174
+                    valid = false;
175
+                    break;
164
                 }
176
                 }
165
-                
177
+            }
166
-                if (valid) {
178
+            
167
-                    obstacles.push(new Obstacle(x, y, radius, type));
179
+            // Check distance from home branch
168
-                    placed = true;
180
+            if (valid && window.homeBranch) {
181
+                let branchY = window.homeBranch.y;
182
+                if (Math.abs(y - branchY) < radius + 40) {
183
+                    valid = false;
169
                 }
184
                 }
170
-                attempts++;
171
             }
185
             }
186
+            
187
+            if (valid) {
188
+                obstacles.push(new Obstacle(x, y, radius, 'beetle'));
189
+                placed = true;
190
+            }
191
+            attempts++;
172
         }
192
         }
173
     }
193
     }
174
     
194
     
175
-    // Add guaranteed anchor points with better bottom coverage
195
+    // Create leaves (3-4) for more stable anchor points
176
-    obstacles.push(new Obstacle(50, height/2, 35, 'branch'));
196
+    let numLeaves = Math.floor(random(3, 5));
177
-    obstacles.push(new Obstacle(width - 50, height/2, 35, 'branch'));
197
+    for (let i = 0; i < numLeaves; i++) {
178
-    obstacles.push(new Obstacle(width/2, 50, 40, 'leaf'));
198
+        let attempts = 0;
199
+        let placed = false;
200
+        
201
+        while (!placed && attempts < 30) {
202
+            let x = random(80, width - 80);
203
+            let y = random(100, height - 150);
204
+            let radius = random(30, 40);
205
+            
206
+            let valid = true;
207
+            for (let obstacle of obstacles) {
208
+                if (dist(x, y, obstacle.x, obstacle.y) < radius + obstacle.radius + 60) {
209
+                    valid = false;
210
+                    break;
211
+                }
212
+            }
213
+            
214
+            if (valid) {
215
+                obstacles.push(new Obstacle(x, y, radius, 'leaf'));
216
+                placed = true;
217
+            }
218
+            attempts++;
219
+        }
220
+    }
179
     
221
     
180
-    // More bottom anchors for reachability
222
+    // Add guaranteed edge anchor points (smaller, stable leaves)
181
-    obstacles.push(new Obstacle(width/4, height - 120, 35, 'leaf'));
223
+    obstacles.push(new Obstacle(50, height/2, 25, 'leaf'));
182
-    obstacles.push(new Obstacle(3*width/4, height - 120, 35, 'branch'));
224
+    obstacles.push(new Obstacle(width - 50, height/2, 25, 'leaf'));
183
-    obstacles.push(new Obstacle(width/2, height - 150, 30, 'branch'));
225
+    obstacles.push(new Obstacle(width/2, 60, 25, 'leaf'));
184
     
226
     
185
-    if (width > 1200) {
227
+    // Bottom anchors for better coverage
186
-        obstacles.push(new Obstacle(width/3, height/3, 35, 'leaf'));
228
+    if (width > 1000) {
187
-        obstacles.push(new Obstacle(2*width/3, height/3, 35, 'branch'));
229
+        obstacles.push(new Obstacle(width/3, height - 130, 25, 'leaf'));
230
+        obstacles.push(new Obstacle(2*width/3, height - 130, 25, 'leaf'));
188
     }
231
     }
189
     
232
     
190
     // Spawn initial food boxes
233
     // Spawn initial food boxes
@@ -224,8 +267,9 @@ function draw() {
224
         drawMoon();
267
         drawMoon();
225
     }
268
     }
226
     
269
     
227
-    // Display game objects
270
+    // Update and display game objects
228
     for (let obstacle of obstacles) {
271
     for (let obstacle of obstacles) {
272
+        obstacle.update(); // Update movement and animations
229
         obstacle.display();
273
         obstacle.display();
230
     }
274
     }
231
     
275