pbnj

snippet #5

let bubbles = [];
let score = 0;
let magicBubble = null;
let gameStartTime = 0;
let particles = [];
let colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57', '#FF9FF3', '#54A0FF'];
let faceColors = ['#FF9FF3', '#54A0FF', '#FF6B6B', '#4ECDC4', '#FFD93D', '#C7CEEA', '#FFB6C1']; // pastel neon for the face lines
let bubblePopSounds = [];
let magicPopSound;
let audioUnlocked = false;

function setup() {
  createCanvas(1024, 768);
  createSounds();
  gameStartTime = millis();
  spawnBubbles();
}
 


 function createSounds() {
  // Normal bubble pops (3 variations) - all the same good click
  for (let i = 0; i < 3; i++) {
    bubblePopSounds.push({
      play: function() {
        let osc = new p5.Oscillator('sine');
        let env = new p5.Envelope();
        env.setADSR(0.001, 0.02, 0, 0.01);
        osc.freq(150); // Fixed frequency - always the same
        osc.amp(env);
        osc.start();
        env.play(osc, 0, 0.05);
        setTimeout(() => osc.stop(), 20);
      }
    });
  }

  // Magic bubble sound (still special but shorter)
  magicPopSound = {
    play: function() {
      for (let i = 0; i < 2; i++) {
        setTimeout(() => {
          let osc = new p5.Oscillator('sine');
          let env = new p5.Envelope();
          env.setADSR(0.001, 0.04, 0, 0.02);
          osc.freq(300 + 2 * 100);
          osc.amp(env);
          osc.start();
          env.play(osc, 0, 0.1 );
          setTimeout(() => osc.stop(), 20);
        }, i * 20);
      }
    }
  };

  // No bubble rise sound - removed
}





function draw() {
  background(10, 10, 30);

  for (let i = bubbles.length - 1; i >= 0; i--) {
    let b = bubbles[i];
    b.update();
    b.display();
    if (b.pos.y < -100) bubbles.splice(i, 1);
  }

  if (magicBubble) {
    magicBubble.update();
    magicBubble.display();
    if (magicBubble.pos.y < -100) magicBubble = null;
  } else if (millis() - gameStartTime > 20000 && random() < 0.001) {
    spawnMagicBubble();
  }

  for (let i = particles.length - 1; i >= 0; i--) {
    particles[i].update();
    particles[i].display();
    if (particles[i].lifespan <= 0) particles.splice(i, 1);
  }

  fill(255);
  textSize(24);
  text(`Score: ${score}`, 20, 40);

  fill(180);
  textSize(14);
  text("Click bubbles • Red magic bubble clears everything!", 20, height - 20);
}

function mousePressed() {
 
    
  if (!audioUnlocked) {
    userStartAudio();
    audioUnlocked = true;
  }

  if (magicBubble && dist(mouseX, mouseY, magicBubble.pos.x, magicBubble.pos.y) < magicBubble.size / 2) {
    magicPopSound.play();
    explodeAll();
    return;
  }

  for (let i = bubbles.length - 1; i >= 0; i--) {
    let b = bubbles[i];
    if (dist(mouseX, mouseY, b.pos.x, b.pos.y) < b.size / 2) {
      popBubble(b, i);
      random(bubblePopSounds).play();
      return;
    }
  }
}

function spawnBubbles() {
  for (let i = 0; i < 20; i++) bubbles.push(new Bubble());
  setTimeout(spawnBubbles, 3000);
}

function spawnMagicBubble() {
  magicBubble = new Bubble(random(width), height + 50, '#FF0044', random(30, 50), true);
  magicBubble.vel.y = random(-1.2, -0.6);
}

function popBubble(bubble, index) {
  score += 10;
  createParticles(bubble.pos.x, bubble.pos.y, bubble.col);
  bubbles.splice(index, 1);
}

function explodeAll() {
  score += bubbles.length * 50 + 1000;
  for (let b of bubbles) createParticles(b.pos.x, b.pos.y, b.col);
  bubbles = [];
  if (magicBubble) {
    createParticles(magicBubble.pos.x, magicBubble.pos.y, '#FF0044');
    magicBubble = null;
  }
}

function createParticles(x, y, col) {
  for (let i = 0; i < 12; i++) particles.push(new Particle(x, y, col));
}

// ===============================================
// NEW BUBBLE WITH "DEAD / DRUNK" FACE DRAWING
// ===============================================
class Bubble {
  constructor(x = random(width), y = height + random(50), col = random(colors), size = random(25, 45), isMagic = false) {
    this.pos = createVector(x, y);
    this.vel = createVector(random(-0.5, 0.5), random(-8, -1));
    this.col = col;
    this.size = size;
    this.isMagic = isMagic;
    this.faceColor = random(faceColors);   // random pastel neon for the face
    this.wobblePhase = random(TWO_PI);     // for slight deformation animation
  }

  update() {
    this.pos.add(this.vel);
    this.vel.y *= 0.99;
    this.wobblePhase += 0.08;
  }

  display() {
    push();
    translate(this.pos.x, this.pos.y);

    // Magic glow
    if (this.isMagic) {
      drawingContext.shadowBlur = 40;
      drawingContext.shadowColor = this.col;
    }

    // Main bubble circle (semi-transparent for that bubbly look)
    noStroke();
    fill(this.col);
    ellipse(0, 0, this.size);

    // === DRAW THE CUTE X-EYES + WOBBLY MOUTH FACE ===
    let s = this.size;
    let faceCol = this.faceColor;

    stroke(faceCol);
    strokeWeight(s * 0.09);
    noFill();

    // Left X eye
    let offset = sin(this.wobblePhase) * 2;
    line(-s*0.25 + offset, -s*0.18, -s*0.12 + offset, -s*0.05);
    line(-s*0.12 + offset, -s*0.18, -s*0.25 + offset, -s*0.05);

    // Right X eye
    line(s*0.25 - offset, -s*0.18, s*0.12 - offset, -s*0.05);
    line(s*0.12 - offset, -s*0.18, s*0.25 - offset, -s*0.05);

    // Wobbly droopy mouth
    beginShape();
    noFill();
    strokeWeight(s * 0.07);
    for (let a = PI*0.2; a <= PI*0.8; a += 0.1) {
      let wave = sin(a*6 + this.wobblePhase*3) * s*0.04;
      let xx = cos(a) * s*0.32;
      let yy = sin(a) * s*0.22 + wave + s*0.15;
      vertex(xx, yy);
    }
    endShape();

    // Tiny blush circles (optional cute touch)
    noStroke();
    fill(red(faceCol), green(faceCol), blue(faceCol), 80);
    ellipse(-s*0.3, s*0.05, s*0.14);
    ellipse( s*0.3, s*0.05, s*0.14);

    // Reset shadow
    if (this.isMagic) drawingContext.shadowBlur = 0;

    pop();
  }
}

class Particle {
  constructor(x, y, col) {
    this.pos = createVector(x, y);
    this.vel = p5.Vector.random2D().mult(random(3, 7));
    this.lifespan = 255;
    this.col = col;
    this.size = random(3, 7);
  }
  update() {
    this.pos.add(this.vel);
    this.vel.mult(0.95);
    this.lifespan -= 12;
  }
  display() {
    noStroke();
    fill(red(this.col), green(this.col), blue(this.col), this.lifespan);
    ellipse(this.pos.x, this.pos.y, this.size);
  }
}