xxxxxxxxxx
201
// Matter.js module aliases
const { Engine, World, Bodies, Composite, Events } = Matter;
let engine, world;
let ground;
let balls = [];
let droppedBalls = []; // Array to store balls that have been dropped
let score = 0;
let gameOver = false; // Game over state
// Ball properties (10 total sizes and colors)
const ballSizes = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
const colors = ["#FF6347", "#4682B4", "#FFD700", "#32CD32", "#FF69B4", "#8A2BE2", "#FF4500", "#1E90FF", "#DA70D6", "#FFB6C1"];
let previewBall = {};
let lastSize = null; // Keep track of the last chosen ball size
// Left and right boundary walls
let leftWall, rightWall;
// Scoring values for each size index
const scoreValues = [0, 2, 5, 7, 12, 15, 20, 25, 35, 50];
// Sound variables
let dropSound, mergeSound;
// Load sounds
function preload() {
dropSound = loadSound('fall.mp3');
mergeSound = loadSound('pop.mp3');
}
function setup() {
createCanvas(450, 600);
// Create an engine and world
engine = Engine.create();
world = engine.world;
// Set up the ground
ground = Bodies.rectangle(width / 2, height - 20, width, 40, { isStatic: true });
Composite.add(world, ground);
// Set up the left and right boundaries
leftWall = Bodies.rectangle(-10, height / 2, 20, height, { isStatic: true });
rightWall = Bodies.rectangle(width + 10, height / 2, 20, height, { isStatic: true });
Composite.add(world, [leftWall, rightWall]);
// Initialize the first preview ball
createPreviewBall();
// Add collision event listener to handle merging and scoring
Events.on(engine, 'collisionStart', handleCollisions);
}
function draw() {
background(34);
Engine.update(engine);
// ground
fill(255);
noStroke();
rectMode(CENTER);
rect(ground.position.x, ground.position.y, width, 40);
// Draw the "Game Over" line at y = 30
stroke(255, 0, 0);
strokeWeight(2);
line(0, 30, width, 30);
// Display the score
fill(255);
noStroke();
textSize(24);
textAlign(LEFT, TOP);
text(`Score: ${score}`, 10, 10);
// Display the "Game Over" message if the game is over
if (gameOver) {
textSize(48);
textAlign(CENTER, CENTER);
fill(255, 0, 0);
text("Game Over", width / 2, height / 2);
return;
}
// Draw falling balls
for (let ball of droppedBalls) {
fill(ball.color);
ellipse(ball.body.position.x, ball.body.position.y, ball.size * 2);
// Check if any "old" ball crosses the game over line
if (ball.hasCollided && ball.body.position.y - ball.size <= 30) {
gameOver = true;
}
}
// Draw the preview ball at the mouse x-position
if (previewBall) {
fill(previewBall.color);
ellipse(mouseX, previewBall.size, previewBall.size * 2);
}
}
// Function to create a new preview ball with a random size and color from the smallest 4
function createPreviewBall() {
const smallestSizes = ballSizes.slice(0, 4); // Selects [10, 20, 30, 40]
const smallestColors = colors.slice(0, 4);
let size, color, randomIndex;
do {
randomIndex = Math.floor(random(smallestSizes.length));
size = smallestSizes[randomIndex];
color = smallestColors[randomIndex];
} while (size === lastSize && random() > 0.45); // Only allow last size to reappear 45% of the time
// Update the preview ball and store the last size
previewBall = { size, color };
lastSize = size;
}
// Function to create a ball with Matter.js body at the specified x position
function dropBall(x, size, color) {
if (gameOver) return; // Prevent dropping new balls if the game is over
let ball = Bodies.circle(x, 0, size, { restitution: 0.4 });
ball.size = size;
ball.color = color;
ball.sizeIndex = ballSizes.indexOf(size); // Track the ball's size index for easy size scaling
ball.hasCollided = false; // Track whether this ball has collided with another ball
Composite.add(world, ball);
// Add to droppedBalls array for game over line check
droppedBalls.push({ body: ball, size, color, hasCollided: false });
// Play drop sound when ball is dropped
dropSound.play();
}
// Drop the preview ball when mouse is clicked and create a new preview ball
function mousePressed() {
if (!gameOver) {
dropBall(mouseX, previewBall.size, previewBall.color);
createPreviewBall(); // Create a new preview ball for the next drop
}
}
// Handle collisions to merge balls of the same size, update the score, and mark balls as "old" after any collision
function handleCollisions(event) {
if (gameOver) return; // Ignore collisions if the game is over
const pairs = event.pairs;
for (let pair of pairs) {
let ballA = droppedBalls.find(b => b.body === pair.bodyA);
let ballB = droppedBalls.find(b => b.body === pair.bodyB);
if (ballA && ballB) {
// Mark both balls as "old" since they've collided
ballA.hasCollided = true;
ballB.hasCollided = true;
// Check if they are of the same size and can merge
if (ballA.size === ballB.size) {
const nextSizeIndex = ballA.body.sizeIndex + 1;
if (nextSizeIndex < ballSizes.length) {
const newSize = ballSizes[nextSizeIndex];
const newColor = colors[nextSizeIndex];
// Create a new merged ball at the midpoint of the two colliding balls
const midX = (ballA.body.position.x + ballB.body.position.x) / 2;
const midY = (ballA.body.position.y + ballB.body.position.y) / 2;
const mergedBall = Bodies.circle(midX, midY, newSize, { restitution: 0.8 });
mergedBall.size = newSize;
mergedBall.color = newColor;
mergedBall.sizeIndex = nextSizeIndex;
mergedBall.hasCollided = true; // Mark new merged ball as "old"
Composite.add(world, mergedBall);
droppedBalls.push({ body: mergedBall, size: newSize, color: newColor, hasCollided: true });
// Update the score based on the size of the merged ball
score += scoreValues[nextSizeIndex];
// Play merge sound
mergeSound.play();
// Remove the original balls from the world and array
Composite.remove(world, ballA.body);
Composite.remove(world, ballB.body);
droppedBalls = droppedBalls.filter(b => b !== ballA && b !== ballB);
}
}
}
}
}