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);
}
}