/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
// Attach a red ellipse as the main bird body
var birdBody = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
birdBody.width = 90;
birdBody.height = 90;
// Add a white ellipse for the belly
var belly = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
belly.width = birdBody.width * 0.7;
belly.height = birdBody.height * 0.5;
belly.x = 0;
belly.y = birdBody.height * 0.18;
belly.tint = 0xffffff;
// Add a black ellipse for the left eye
var leftEye = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
leftEye.width = birdBody.width * 0.18;
leftEye.height = birdBody.height * 0.18;
leftEye.x = -birdBody.width * 0.18;
leftEye.y = -birdBody.height * 0.18;
leftEye.tint = 0x000000;
// Add a black ellipse for the right eye
var rightEye = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
rightEye.width = birdBody.width * 0.18;
rightEye.height = birdBody.height * 0.18;
rightEye.x = birdBody.width * 0.08;
rightEye.y = -birdBody.height * 0.18;
rightEye.tint = 0x000000;
// Add a yellow triangle for the beak
var beak = self.attachAsset('beakTriangle', {
anchorX: 0.1,
anchorY: 0.5
});
beak.width = birdBody.width * 0.48;
beak.height = birdBody.height * 0.36;
beak.x = birdBody.width * 0.56;
beak.y = 0;
beak.rotation = Math.PI / 8;
// Physics properties
self.vx = 0;
self.vy = 0;
self.launched = false;
self.radius = birdBody.width / 2;
self.gravity = 0.35;
self.friction = 0.998;
self.bounce = 0.5;
self.stopped = false;
// Called every tick
self.update = function () {
// Track lastX and lastY for physics events
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
if (self.launched && !self.stopped) {
// Standard projectile physics, no tracking or homing
self.vy += self.gravity;
self.x += self.vx;
self.y += self.vy;
// Friction
self.vx *= self.friction;
self.vy *= self.friction;
// Floor collision
if (self.y + self.radius > groundY) {
self.y = groundY - self.radius;
if (Math.abs(self.vy) > 2) {
self.vy = -self.vy * self.bounce;
self.vx *= 0.8;
self.stopped = false; // Don't stop if still bouncing
} else {
self.vy = 0;
self.vx *= 0.8;
// Only stop if both vx and vy are very small and bird is on ground
if (Math.abs(self.vx) < 1 && Math.abs(self.vy) < 0.5) {
self.stopped = true;
} else {
self.stopped = false;
}
}
}
// Wall collision
if (self.x - self.radius < 0) {
self.x = self.radius;
self.vx = -self.vx * self.bounce;
}
if (self.x + self.radius > 2048) {
self.x = 2048 - self.radius;
self.vx = -self.vx * self.bounce;
}
}
// Update lastX and lastY after movement
if (typeof self.x === "number" && !isNaN(self.x)) {
self.lastX = self.x;
} else if (typeof self.x === "string" && !isNaN(parseFloat(self.x))) {
self.lastX = parseFloat(self.x);
} else if (typeof self.lastX === "number" && !isNaN(self.lastX)) {
// keep previous
} else {
self.lastX = 0;
}
if (typeof self.y === "number" && !isNaN(self.y)) {
self.lastY = self.y;
} else if (typeof self.y === "string" && !isNaN(parseFloat(self.y))) {
self.lastY = parseFloat(self.y);
} else if (typeof self.lastY === "number" && !isNaN(self.lastY)) {
// keep previous
} else {
self.lastY = 0;
}
};
// Simple circle collision
self.intersectsCircle = function (other) {
var dx = self.x - other.x;
var dy = self.y - other.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < self.radius + other.radius;
};
return self;
});
// Block class (rectangular, can be horizontal or vertical)
var Block = Container.expand(function () {
var self = Container.call(this);
// Attach a brown box as the block
var blockAsset = self.attachAsset('block', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = blockAsset.width;
self.height = blockAsset.height;
self.vx = 0;
self.vy = 0;
self.gravity = 0.7;
self.friction = 0.98;
self.bounce = 0.3;
self.stopped = false;
// Add health property for wood blocks
self.health = 100;
// Called every tick
self.update = function () {
// Track lastX and lastY for physics events
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
if (!self.stopped) {
// Apply gravity to wood blocks
self.vy += self.gravity;
self.x += self.vx;
self.y += self.vy;
self.vx *= self.friction;
self.vy *= self.friction;
// --- Ragdoll effect: angular physics ---
if (typeof self.angularVelocity === "undefined") self.angularVelocity = 0;
if (typeof self.angle === "undefined") self.angle = self.rotation || 0;
// Apply angular velocity to rotation
self.angle += self.angularVelocity;
self.rotation = self.angle;
// Dampen angular velocity (simulate friction)
self.angularVelocity *= 0.97;
// Block-on-block collision (stand or crash)
for (var i = 0; i < blocks.length; i++) {
var other = blocks[i];
if (other === self) continue;
// Axis-aligned bounding box collision
var selfLeft = self.x - self.width / 2;
var selfRight = self.x + self.width / 2;
var selfBottom = self.y + self.height / 2;
var selfTop = self.y - self.height / 2;
var otherLeft = other.x - other.width / 2;
var otherRight = other.x + other.width / 2;
var otherTop = other.y - other.height / 2;
var otherBottom = other.y + other.height / 2;
// Check if horizontally overlapping and self is falling onto other
if (selfRight > otherLeft && selfLeft < otherRight && selfBottom > otherTop && selfTop < otherTop && self.vy > 0 && self.y < other.y) {
// Land on top of the other block
self.y = otherTop - self.height / 2;
// If falling fast, bounce/crash
if (Math.abs(self.vy) > 2) {
self.vy = -self.vy * self.bounce;
self.vx *= 0.8;
// Transfer some force to the block below (crash effect)
other.vx += self.vx * 0.2;
other.vy += self.vy * 0.2;
// --- Ragdoll: add angular velocity based on impact ---
var impact = self.vx * 0.1 + self.vy * 0.1;
if (typeof self.angularVelocity === "undefined") self.angularVelocity = 0;
self.angularVelocity += impact * (Math.random() - 0.5) * 0.2;
if (typeof other.angularVelocity === "undefined") other.angularVelocity = 0;
other.angularVelocity += impact * (Math.random() - 0.5) * 0.2;
// Reduce health for both blocks based on impact
var damage = Math.min(30, Math.max(5, Math.floor(Math.abs(self.vy) * 2)));
self.health -= damage;
other.health -= Math.floor(damage / 2);
// Destroy self if health <= 0
if (self.health <= 0) {
self.destroy();
return;
}
// Destroy other if health <= 0
if (other.health <= 0) {
other.destroy();
}
// Bird-block collision damage is handled in game.update
} else {
self.vy = 0;
self.vx *= 0.8;
if (Math.abs(self.vx) < 1) {
self.stopped = true;
}
}
}
// Also allow side-to-side collision (prevent overlap horizontally)
if (selfBottom > otherTop && selfTop < otherBottom) {
// Check if self is moving right into other's left side
if (selfRight > otherLeft && selfLeft < otherLeft && self.vx > 0) {
self.x = otherLeft - self.width / 2;
self.vx = -self.vx * self.bounce;
// Transfer some force to the other block (right hit)
other.vx += self.vx * 0.2;
// --- Ragdoll: add angular velocity based on side impact ---
var impact = self.vx * 0.1;
if (typeof self.angularVelocity === "undefined") self.angularVelocity = 0;
self.angularVelocity += impact * 0.1;
if (typeof other.angularVelocity === "undefined") other.angularVelocity = 0;
other.angularVelocity -= impact * 0.1;
}
// Check if self is moving left into other's right side
if (selfLeft < otherRight && selfRight > otherRight && self.vx < 0) {
self.x = otherRight + self.width / 2;
self.vx = -self.vx * self.bounce;
// Transfer some force to the other block (left hit)
other.vx += self.vx * 0.2;
// --- Ragdoll: add angular velocity based on side impact ---
var impact = self.vx * 0.1;
if (typeof self.angularVelocity === "undefined") self.angularVelocity = 0;
self.angularVelocity -= impact * 0.1;
if (typeof other.angularVelocity === "undefined") other.angularVelocity = 0;
other.angularVelocity += impact * 0.1;
}
}
}
// Floor collision
if (self.y + self.height / 2 > groundY) {
self.y = groundY - self.height / 2;
if (Math.abs(self.vy) > 2) {
self.vy = -self.vy * self.bounce;
self.vx *= 0.8;
} else {
self.vy = 0;
self.vx *= 0.8;
if (Math.abs(self.vx) < 1) {
self.stopped = true;
}
}
}
// Wall collision
if (self.x - self.width / 2 < 0) {
self.x = self.width / 2;
self.vx = -self.vx * self.bounce;
}
if (self.x + self.width / 2 > 2048) {
self.x = 2048 - self.width / 2;
self.vx = -self.vx * self.bounce;
}
}
// Update lastX and lastY after movement
self.lastX = self.x;
self.lastY = self.y;
};
// Simple rectangle collision with circle (bird)
self.intersectsCircle = function (circle) {
var cx = circle.x;
var cy = circle.y;
var rx = self.x;
var ry = self.y;
var hw = self.width / 2;
var hh = self.height / 2;
// Closest point on rectangle to circle center
var closestX = Math.max(rx - hw, Math.min(cx, rx + hw));
var closestY = Math.max(ry - hh, Math.min(cy, ry + hh));
var dx = cx - closestX;
var dy = cy - closestY;
return dx * dx + dy * dy < circle.radius * circle.radius;
};
return self;
});
// Pig class
var Pig = Container.expand(function () {
var self = Container.call(this);
// Attach a green ellipse as the pig
var pigAsset = self.attachAsset('pig', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = pigAsset.width / 2;
self.alive = true;
// Physics properties for pigs
self.vx = 0;
self.vy = 0;
self.gravity = 0.7;
self.friction = 0.98;
self.bounce = 0.3;
self.stopped = false;
// Called every tick
self.update = function () {
if (!self.alive) return;
// Track lastX and lastY for physics events
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
if (!self.stopped) {
self.vy += self.gravity;
self.x += self.vx;
self.y += self.vy;
self.vx *= self.friction;
self.vy *= self.friction;
// Pig-on-block collision: prevent pig from going inside the wood blocks
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
// Axis-aligned bounding box for block
var blockLeft = block.x - block.width / 2;
var blockRight = block.x + block.width / 2;
var blockTop = block.y - block.height / 2;
var blockBottom = block.y + block.height / 2;
// Only check if pig is above block and falling
if (self.y < block.y && self.vy > 0) {
// Check horizontal overlap
if (self.x + self.radius > blockLeft && self.x - self.radius < blockRight) {
// Check if pig's bottom is entering block's top
if (self.y + self.radius > blockTop && self.y - self.radius < blockBottom) {
// Place pig on top of block
self.y = blockTop - self.radius;
if (Math.abs(self.vy) > 2) {
self.vy = -self.vy * self.bounce;
self.vx *= 0.8;
// Transfer some force to the block below (crash effect)
block.vx += self.vx * 0.2;
block.vy += self.vy * 0.2;
} else {
self.vy = 0;
self.vx *= 0.8;
if (Math.abs(self.vx) < 1) {
self.stopped = true;
}
}
}
}
}
}
// Floor collision
if (self.y + self.radius > groundY) {
self.y = groundY - self.radius;
if (Math.abs(self.vy) > 2) {
self.vy = -self.vy * self.bounce;
self.vx *= 0.8;
} else {
self.vy = 0;
self.vx *= 0.8;
if (Math.abs(self.vx) < 1) {
self.stopped = true;
}
}
}
// Wall collision
if (self.x - self.radius < 0) {
self.x = self.radius;
self.vx = -self.vx * self.bounce;
}
if (self.x + self.radius > 2048) {
self.x = 2048 - self.radius;
self.vx = -self.vx * self.bounce;
}
}
// Update lastX and lastY after movement
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// --- Add Background Image ---
// --- Asset Initialization (shapes) ---
var background = LK.getAsset('block', {
anchorX: 0,
anchorY: 0
});
background.width = 2048;
background.height = 2732;
background.tint = 0x87ceeb; // Sky blue tint for now
background.x = 0;
background.y = 0;
game.addChild(background);
// --- Add Sun to the Sky ---
var sun = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
sun.width = 320;
sun.height = 320;
sun.tint = 0xfff700; // bright yellow
sun.x = 350;
sun.y = 350;
game.addChild(sun);
// --- Add Trees for Scenery ---
var treeTrunkColor = 0x8b5a2b;
var treeLeafColor = 0x228b22;
var treeCount = 4;
var treeSpacing = 2048 / (treeCount + 1);
for (var i = 0; i < treeCount; i++) {
// Trunk
var trunk = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 1
});
trunk.width = 40;
trunk.height = 220;
trunk.tint = treeTrunkColor;
trunk.x = treeSpacing * (i + 1);
trunk.y = groundY + 10;
game.addChild(trunk);
// Leaves (ellipse)
var leaves = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 1
});
leaves.width = 180;
leaves.height = 160;
leaves.tint = treeLeafColor;
leaves.x = trunk.x;
leaves.y = trunk.y - trunk.height + 10;
game.addChild(leaves);
}
// --- Game Variables ---
var birds = [];
var pigs = [];
var blocks = [];
var currentBird = null;
var birdIndex = 0;
var isDragging = false;
var dragStart = {
x: 0,
y: 0
};
var dragEnd = {
x: 0,
y: 0
};
var slingshotX = 180;
var slingshotY = 2000; // Raised slingshot higher (was 2450)
var slingshotBandLength = 180;
var maxBirds = 5;
var groundY = 2650;
var level = 1;
var launching = false;
var score = 0;
// --- GUI ---
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var birdsLeftTxt = new Text2('', {
size: 80,
fill: 0xFFFFFF
});
birdsLeftTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(birdsLeftTxt);
// --- Draw Ground ---
var ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0
});
ground.x = 0;
ground.y = groundY;
game.addChild(ground);
// --- Draw Little Mountain below the slingshot ---
var mountainWidth = 400;
var mountainHeight = 120;
var mountain = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 1
});
mountain.width = mountainWidth;
mountain.height = mountainHeight;
mountain.x = slingshotX;
mountain.y = groundY + 10; // Slightly below ground for overlap
mountain.tint = 0x8b7765; // Optional: make it look more like a mountain
game.addChild(mountain);
// --- Draw Slingshot ---
var slingshot = LK.getAsset('slingshot', {
anchorX: 0.5,
anchorY: 1
});
slingshot.x = slingshotX;
slingshot.y = slingshotY + 90;
game.addChild(slingshot);
// --- Level Setup ---
function setupLevel() {
// Add a golden star collectible above the house for bonus points
if (typeof star === "undefined") {
star = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 0.5
});
star.width = 80;
star.height = 80;
star.tint = 0xffd700; // gold color
star.isStar = true;
}
star.x = 1500;
star.y = groundY - 600;
star.collected = false;
star.visible = true;
game.addChild(star);
// Clear previous
for (var i = 0; i < birds.length; i++) birds[i].destroy();
for (var i = 0; i < pigs.length; i++) pigs[i].destroy();
for (var i = 0; i < blocks.length; i++) blocks[i].destroy();
birds = [];
pigs = [];
blocks = [];
birdIndex = 0;
score = 0;
LK.setScore(0);
scoreTxt.setText('0');
launching = false;
// Place birds
for (var i = 0; i < maxBirds; i++) {
var bird = new Bird();
bird.x = slingshotX - 120 - i * 80;
bird.y = slingshotY + 40;
birds.push(bird);
game.addChild(bird);
}
currentBird = birds[birdIndex];
// Place blocks to form a house shape
var houseCenterX = 1500;
var houseBottomY = groundY - 100;
var wallW = 180;
var wallH = 40;
var wallHeight = 4; // number of vertical wall blocks
var roofW = 180;
var roofH = 40;
// --- House base: two vertical walls (left/right), 4 blocks high ---
for (var i = 0; i < wallHeight; i++) {
// Left wall
var leftWall = new Block();
leftWall.x = houseCenterX - wallW / 2 + wallH / 2;
leftWall.y = houseBottomY - i * wallW;
leftWall.rotation = Math.PI / 2;
blocks.push(leftWall);
game.addChild(leftWall);
// Right wall
var rightWall = new Block();
rightWall.x = houseCenterX + wallW / 2 - wallH / 2;
rightWall.y = houseBottomY - i * wallW;
rightWall.rotation = Math.PI / 2;
blocks.push(rightWall);
game.addChild(rightWall);
}
// --- House base: bottom block (floor) ---
var houseFloor = new Block();
houseFloor.x = houseCenterX;
houseFloor.y = houseBottomY + wallH / 2;
blocks.push(houseFloor);
game.addChild(houseFloor);
// --- House base: top block (ceiling) ---
var houseCeiling = new Block();
houseCeiling.x = houseCenterX;
houseCeiling.y = houseBottomY - (wallHeight - 1) * wallW - wallH / 2;
blocks.push(houseCeiling);
game.addChild(houseCeiling);
// --- House roof: two diagonal blocks (left/right) ---
var roofLeft = new Block();
roofLeft.x = houseCenterX - wallW / 2 + wallH / 2 + 30;
roofLeft.y = houseCeiling.y - wallW / 2 + 10;
roofLeft.rotation = Math.PI / 4; // 45 degrees
blocks.push(roofLeft);
game.addChild(roofLeft);
var roofRight = new Block();
roofRight.x = houseCenterX + wallW / 2 - wallH / 2 - 30;
roofRight.y = houseCeiling.y - wallW / 2 + 10;
roofRight.rotation = -Math.PI / 4; // -45 degrees
blocks.push(roofRight);
game.addChild(roofRight);
// --- House roof: top horizontal block (roof ridge) ---
var roofRidge = new Block();
roofRidge.x = houseCenterX;
roofRidge.y = houseCeiling.y - wallW / 2 - roofH / 2 + 10;
blocks.push(roofRidge);
game.addChild(roofRidge);
// Place pig on the house ceiling
var pig1 = new Pig();
pig1.x = houseCeiling.x;
pig1.y = houseCeiling.y - houseCeiling.height / 2 - pig1.radius - 50;
pigs.push(pig1);
game.addChild(pig1);
// (Second pig inside the house removed)
// --- Ensure blocks and pigs are not hanging in the air ---
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
// Check if block is supported by ground or another block
var supported = false;
// Supported by ground
if (Math.abs(block.y + block.height / 2 - groundY) < 2) {
supported = true;
}
// Supported by another block below
for (var j = 0; j < blocks.length; j++) {
if (i === j) continue;
var other = blocks[j];
var blockBottom = block.y + block.height / 2;
var otherTop = other.y - other.height / 2;
var horizontalOverlap = block.x + block.width / 2 > other.x - other.width / 2 && block.x - block.width / 2 < other.x + other.width / 2;
if (horizontalOverlap && Math.abs(blockBottom - otherTop) < 2) {
supported = true;
break;
}
}
block.stopped = !supported ? false : block.stopped;
}
for (var i = 0; i < pigs.length; i++) {
var pig = pigs[i];
// Check if pig is supported by ground or a block
var supported = false;
// Supported by ground
if (Math.abs(pig.y + pig.radius - groundY) < 2) {
supported = true;
}
// Supported by a block below
for (var j = 0; j < blocks.length; j++) {
var block = blocks[j];
var pigBottom = pig.y + pig.radius;
var blockTop = block.y - block.height / 2;
var horizontalOverlap = pig.x + pig.radius > block.x - block.width / 2 && pig.x - pig.radius < block.x + block.width / 2;
if (horizontalOverlap && Math.abs(pigBottom - blockTop) < 2) {
supported = true;
break;
}
}
pig.stopped = !supported ? false : pig.stopped;
}
updateBirdsLeft();
// Reset star collectible if it exists
if (typeof star !== "undefined") {
star.collected = false;
star.visible = true;
}
}
// --- Update birds left text ---
function updateBirdsLeft() {
birdsLeftTxt.setText('Birds: ' + (maxBirds - birdIndex));
}
// --- Drag and Launch Logic ---
function getDistance(x1, y1, x2, y2) {
var dx = x2 - x1;
var dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
function getAngle(x1, y1, x2, y2) {
return Math.atan2(y2 - y1, x2 - x1);
}
// Only allow drag if not launched
game.down = function (x, y, obj) {
if (launching) return;
if (!currentBird || currentBird.launched) return;
// Only allow drag if touch is near the bird
var dist = getDistance(x, y, currentBird.x, currentBird.y);
if (dist < currentBird.radius * 1.5) {
isDragging = true;
dragStart.x = x;
dragStart.y = y;
}
};
game.move = function (x, y, obj) {
if (isDragging && currentBird && !currentBird.launched) {
// Limit drag distance
var dx = x - slingshotX;
var dy = y - slingshotY;
var dist = getDistance(x, y, slingshotX, slingshotY);
if (dist > slingshotBandLength) {
var angle = getAngle(slingshotX, slingshotY, x, y);
dx = Math.cos(angle) * slingshotBandLength;
dy = Math.sin(angle) * slingshotBandLength;
}
currentBird.x = slingshotX + dx;
currentBird.y = slingshotY + dy;
dragEnd.x = currentBird.x;
dragEnd.y = currentBird.y;
}
};
game.up = function (x, y, obj) {
if (isDragging && currentBird && !currentBird.launched) {
isDragging = false;
// Calculate launch velocity
var dx = slingshotX - currentBird.x;
var dy = slingshotY - currentBird.y;
currentBird.vx = dx * 0.18;
currentBird.vy = dy * 0.18;
currentBird.launched = true;
launching = true;
// Animate slingshot back
tween(currentBird, {
x: currentBird.x,
y: currentBird.y
}, {
duration: 80
});
}
};
// --- Main Game Loop ---
game.update = function () {
// Update all birds
for (var i = 0; i < birds.length; i++) {
birds[i].update();
}
// Update all pigs
for (var i = 0; i < pigs.length; i++) {
pigs[i].update();
}
// Update all blocks
for (var i = 0; i < blocks.length; i++) {
blocks[i].update();
}
// --- Check support for each block: if not supported, set stopped = false so it falls ---
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
// Check if block is supported by ground or another block
var supported = false;
// Supported by ground
if (Math.abs(block.y + block.height / 2 - groundY) < 2) {
supported = true;
}
// Supported by another block below
for (var j = 0; j < blocks.length; j++) {
if (i === j) continue;
var other = blocks[j];
var blockBottom = block.y + block.height / 2;
var otherTop = other.y - other.height / 2;
var horizontalOverlap = block.x + block.width / 2 > other.x - other.width / 2 && block.x - block.width / 2 < other.x + other.width / 2;
if (horizontalOverlap && Math.abs(blockBottom - otherTop) < 2) {
supported = true;
break;
}
}
// If not supported, set stopped = false so it will fall
if (!supported) {
block.stopped = false;
}
}
// Bird-block collision
for (var i = 0; i < birds.length; i++) {
var bird = birds[i];
if (!bird.launched) continue;
if (bird.stopped) continue;
// Collide with blocks
for (var j = 0; j < blocks.length; j++) {
var block = blocks[j];
// Remove block.stopped check so birds can always touch wood
if (block.intersectsCircle(bird)) {
// Calculate collision angle and force
var angle = getAngle(bird.x, bird.y, block.x, block.y);
var force = Math.sqrt(bird.vx * bird.vx + bird.vy * bird.vy) * 0.5;
// Apply force to block
block.vx += Math.cos(angle) * force * 0.7;
block.vy += Math.sin(angle) * force * 0.7;
// --- Ragdoll: add angular velocity to block based on bird impact ---
var impact = force * 0.2;
if (typeof block.angularVelocity === "undefined") block.angularVelocity = 0;
block.angularVelocity += impact * (Math.random() - 0.5);
// Apply bounce to bird
bird.vx = -bird.vx * 0.4;
bird.vy = -bird.vy * 0.4;
// Move bird out of block to prevent sticking
var overlap = bird.radius + Math.max(block.width, block.height) / 2 - getDistance(bird.x, bird.y, block.x, block.y);
if (overlap > 0) {
bird.x += Math.cos(angle) * overlap * 0.5;
bird.y += Math.sin(angle) * overlap * 0.5;
}
// Optionally, mark block as not stopped to allow it to move if hit hard
block.stopped = false;
// Birds deal damage to woods on collision
var birdDamage = Math.min(40, Math.max(5, Math.floor(force * 2)));
block.health -= birdDamage;
if (block.health <= 0) {
block.destroy();
blocks.splice(j, 1);
j--; // Adjust index since we removed a block
continue;
}
}
}
}
// Bird-pig collision
for (var i = 0; i < birds.length; i++) {
var bird = birds[i];
if (!bird.launched) continue;
if (bird.stopped) continue;
// Bird-star collision
if (typeof star !== "undefined" && star.visible && !star.collected && bird.intersectsCircle({
x: star.x,
y: star.y,
radius: star.width / 2
})) {
star.collected = true;
star.visible = false;
LK.setScore(LK.getScore() + 5000);
scoreTxt.setText(LK.getScore());
}
for (var j = 0; j < pigs.length; j++) {
var pig = pigs[j];
if (!pig.alive) continue;
if (bird.intersectsCircle(pig)) {
pig.alive = false;
pig.visible = false;
LK.setScore(LK.getScore() + 1000);
scoreTxt.setText(LK.getScore());
}
}
}
// Block-pig collision (falling blocks can kill pigs)
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
if (block.stopped) continue;
for (var j = 0; j < pigs.length; j++) {
var pig = pigs[j];
if (!pig.alive) continue;
// Treat pig as circle, block as rectangle
if (block.intersectsCircle(pig)) {
// Only kill pig if block is moving fast enough (e.g. falling hard)
var blockSpeed = Math.sqrt(block.vx * block.vx + block.vy * block.vy);
if (blockSpeed > 8) {
pig.alive = false;
pig.visible = false;
LK.setScore(LK.getScore() + 1000);
scoreTxt.setText(LK.getScore());
}
}
}
}
// --- Check support for each pig: if not supported, set stopped = false so it falls ---
for (var i = 0; i < pigs.length; i++) {
var pig = pigs[i];
if (!pig.alive) continue;
var supported = false;
// Supported by ground
if (Math.abs(pig.y + pig.radius - groundY) < 2) {
supported = true;
}
// Supported by a block below
for (var j = 0; j < blocks.length; j++) {
var block = blocks[j];
var pigBottom = pig.y + pig.radius;
var blockTop = block.y - block.height / 2;
var horizontalOverlap = pig.x + pig.radius > block.x - block.width / 2 && pig.x - pig.radius < block.x + block.width / 2;
if (horizontalOverlap && Math.abs(pigBottom - blockTop) < 2) {
supported = true;
break;
}
}
if (!supported) {
pig.stopped = false;
}
}
// Remove dead pigs
var allPigsDead = true;
for (var i = 0; i < pigs.length; i++) {
if (pigs[i].alive) {
allPigsDead = false;
break;
}
}
// If all pigs dead, win (only if there was at least one pig)
if (pigs.length > 0 && allPigsDead) {
LK.showYouWin();
return;
}
// If current bird stopped, move to next bird
if (currentBird && currentBird.launched && currentBird.stopped && !isDragging && launching) {
birdIndex++;
updateBirdsLeft();
if (birdIndex < birds.length) {
currentBird = birds[birdIndex];
// Move next bird to slingshot
tween(currentBird, {
x: slingshotX,
y: slingshotY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
launching = false;
}
});
} else {
// Out of birds, game over
LK.showGameOver();
return;
}
launching = false;
}
};
// --- Start Game ---
setupLevel(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
// Attach a red ellipse as the main bird body
var birdBody = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
birdBody.width = 90;
birdBody.height = 90;
// Add a white ellipse for the belly
var belly = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
belly.width = birdBody.width * 0.7;
belly.height = birdBody.height * 0.5;
belly.x = 0;
belly.y = birdBody.height * 0.18;
belly.tint = 0xffffff;
// Add a black ellipse for the left eye
var leftEye = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
leftEye.width = birdBody.width * 0.18;
leftEye.height = birdBody.height * 0.18;
leftEye.x = -birdBody.width * 0.18;
leftEye.y = -birdBody.height * 0.18;
leftEye.tint = 0x000000;
// Add a black ellipse for the right eye
var rightEye = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
rightEye.width = birdBody.width * 0.18;
rightEye.height = birdBody.height * 0.18;
rightEye.x = birdBody.width * 0.08;
rightEye.y = -birdBody.height * 0.18;
rightEye.tint = 0x000000;
// Add a yellow triangle for the beak
var beak = self.attachAsset('beakTriangle', {
anchorX: 0.1,
anchorY: 0.5
});
beak.width = birdBody.width * 0.48;
beak.height = birdBody.height * 0.36;
beak.x = birdBody.width * 0.56;
beak.y = 0;
beak.rotation = Math.PI / 8;
// Physics properties
self.vx = 0;
self.vy = 0;
self.launched = false;
self.radius = birdBody.width / 2;
self.gravity = 0.35;
self.friction = 0.998;
self.bounce = 0.5;
self.stopped = false;
// Called every tick
self.update = function () {
// Track lastX and lastY for physics events
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
if (self.launched && !self.stopped) {
// Standard projectile physics, no tracking or homing
self.vy += self.gravity;
self.x += self.vx;
self.y += self.vy;
// Friction
self.vx *= self.friction;
self.vy *= self.friction;
// Floor collision
if (self.y + self.radius > groundY) {
self.y = groundY - self.radius;
if (Math.abs(self.vy) > 2) {
self.vy = -self.vy * self.bounce;
self.vx *= 0.8;
self.stopped = false; // Don't stop if still bouncing
} else {
self.vy = 0;
self.vx *= 0.8;
// Only stop if both vx and vy are very small and bird is on ground
if (Math.abs(self.vx) < 1 && Math.abs(self.vy) < 0.5) {
self.stopped = true;
} else {
self.stopped = false;
}
}
}
// Wall collision
if (self.x - self.radius < 0) {
self.x = self.radius;
self.vx = -self.vx * self.bounce;
}
if (self.x + self.radius > 2048) {
self.x = 2048 - self.radius;
self.vx = -self.vx * self.bounce;
}
}
// Update lastX and lastY after movement
if (typeof self.x === "number" && !isNaN(self.x)) {
self.lastX = self.x;
} else if (typeof self.x === "string" && !isNaN(parseFloat(self.x))) {
self.lastX = parseFloat(self.x);
} else if (typeof self.lastX === "number" && !isNaN(self.lastX)) {
// keep previous
} else {
self.lastX = 0;
}
if (typeof self.y === "number" && !isNaN(self.y)) {
self.lastY = self.y;
} else if (typeof self.y === "string" && !isNaN(parseFloat(self.y))) {
self.lastY = parseFloat(self.y);
} else if (typeof self.lastY === "number" && !isNaN(self.lastY)) {
// keep previous
} else {
self.lastY = 0;
}
};
// Simple circle collision
self.intersectsCircle = function (other) {
var dx = self.x - other.x;
var dy = self.y - other.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < self.radius + other.radius;
};
return self;
});
// Block class (rectangular, can be horizontal or vertical)
var Block = Container.expand(function () {
var self = Container.call(this);
// Attach a brown box as the block
var blockAsset = self.attachAsset('block', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = blockAsset.width;
self.height = blockAsset.height;
self.vx = 0;
self.vy = 0;
self.gravity = 0.7;
self.friction = 0.98;
self.bounce = 0.3;
self.stopped = false;
// Add health property for wood blocks
self.health = 100;
// Called every tick
self.update = function () {
// Track lastX and lastY for physics events
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
if (!self.stopped) {
// Apply gravity to wood blocks
self.vy += self.gravity;
self.x += self.vx;
self.y += self.vy;
self.vx *= self.friction;
self.vy *= self.friction;
// --- Ragdoll effect: angular physics ---
if (typeof self.angularVelocity === "undefined") self.angularVelocity = 0;
if (typeof self.angle === "undefined") self.angle = self.rotation || 0;
// Apply angular velocity to rotation
self.angle += self.angularVelocity;
self.rotation = self.angle;
// Dampen angular velocity (simulate friction)
self.angularVelocity *= 0.97;
// Block-on-block collision (stand or crash)
for (var i = 0; i < blocks.length; i++) {
var other = blocks[i];
if (other === self) continue;
// Axis-aligned bounding box collision
var selfLeft = self.x - self.width / 2;
var selfRight = self.x + self.width / 2;
var selfBottom = self.y + self.height / 2;
var selfTop = self.y - self.height / 2;
var otherLeft = other.x - other.width / 2;
var otherRight = other.x + other.width / 2;
var otherTop = other.y - other.height / 2;
var otherBottom = other.y + other.height / 2;
// Check if horizontally overlapping and self is falling onto other
if (selfRight > otherLeft && selfLeft < otherRight && selfBottom > otherTop && selfTop < otherTop && self.vy > 0 && self.y < other.y) {
// Land on top of the other block
self.y = otherTop - self.height / 2;
// If falling fast, bounce/crash
if (Math.abs(self.vy) > 2) {
self.vy = -self.vy * self.bounce;
self.vx *= 0.8;
// Transfer some force to the block below (crash effect)
other.vx += self.vx * 0.2;
other.vy += self.vy * 0.2;
// --- Ragdoll: add angular velocity based on impact ---
var impact = self.vx * 0.1 + self.vy * 0.1;
if (typeof self.angularVelocity === "undefined") self.angularVelocity = 0;
self.angularVelocity += impact * (Math.random() - 0.5) * 0.2;
if (typeof other.angularVelocity === "undefined") other.angularVelocity = 0;
other.angularVelocity += impact * (Math.random() - 0.5) * 0.2;
// Reduce health for both blocks based on impact
var damage = Math.min(30, Math.max(5, Math.floor(Math.abs(self.vy) * 2)));
self.health -= damage;
other.health -= Math.floor(damage / 2);
// Destroy self if health <= 0
if (self.health <= 0) {
self.destroy();
return;
}
// Destroy other if health <= 0
if (other.health <= 0) {
other.destroy();
}
// Bird-block collision damage is handled in game.update
} else {
self.vy = 0;
self.vx *= 0.8;
if (Math.abs(self.vx) < 1) {
self.stopped = true;
}
}
}
// Also allow side-to-side collision (prevent overlap horizontally)
if (selfBottom > otherTop && selfTop < otherBottom) {
// Check if self is moving right into other's left side
if (selfRight > otherLeft && selfLeft < otherLeft && self.vx > 0) {
self.x = otherLeft - self.width / 2;
self.vx = -self.vx * self.bounce;
// Transfer some force to the other block (right hit)
other.vx += self.vx * 0.2;
// --- Ragdoll: add angular velocity based on side impact ---
var impact = self.vx * 0.1;
if (typeof self.angularVelocity === "undefined") self.angularVelocity = 0;
self.angularVelocity += impact * 0.1;
if (typeof other.angularVelocity === "undefined") other.angularVelocity = 0;
other.angularVelocity -= impact * 0.1;
}
// Check if self is moving left into other's right side
if (selfLeft < otherRight && selfRight > otherRight && self.vx < 0) {
self.x = otherRight + self.width / 2;
self.vx = -self.vx * self.bounce;
// Transfer some force to the other block (left hit)
other.vx += self.vx * 0.2;
// --- Ragdoll: add angular velocity based on side impact ---
var impact = self.vx * 0.1;
if (typeof self.angularVelocity === "undefined") self.angularVelocity = 0;
self.angularVelocity -= impact * 0.1;
if (typeof other.angularVelocity === "undefined") other.angularVelocity = 0;
other.angularVelocity += impact * 0.1;
}
}
}
// Floor collision
if (self.y + self.height / 2 > groundY) {
self.y = groundY - self.height / 2;
if (Math.abs(self.vy) > 2) {
self.vy = -self.vy * self.bounce;
self.vx *= 0.8;
} else {
self.vy = 0;
self.vx *= 0.8;
if (Math.abs(self.vx) < 1) {
self.stopped = true;
}
}
}
// Wall collision
if (self.x - self.width / 2 < 0) {
self.x = self.width / 2;
self.vx = -self.vx * self.bounce;
}
if (self.x + self.width / 2 > 2048) {
self.x = 2048 - self.width / 2;
self.vx = -self.vx * self.bounce;
}
}
// Update lastX and lastY after movement
self.lastX = self.x;
self.lastY = self.y;
};
// Simple rectangle collision with circle (bird)
self.intersectsCircle = function (circle) {
var cx = circle.x;
var cy = circle.y;
var rx = self.x;
var ry = self.y;
var hw = self.width / 2;
var hh = self.height / 2;
// Closest point on rectangle to circle center
var closestX = Math.max(rx - hw, Math.min(cx, rx + hw));
var closestY = Math.max(ry - hh, Math.min(cy, ry + hh));
var dx = cx - closestX;
var dy = cy - closestY;
return dx * dx + dy * dy < circle.radius * circle.radius;
};
return self;
});
// Pig class
var Pig = Container.expand(function () {
var self = Container.call(this);
// Attach a green ellipse as the pig
var pigAsset = self.attachAsset('pig', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = pigAsset.width / 2;
self.alive = true;
// Physics properties for pigs
self.vx = 0;
self.vy = 0;
self.gravity = 0.7;
self.friction = 0.98;
self.bounce = 0.3;
self.stopped = false;
// Called every tick
self.update = function () {
if (!self.alive) return;
// Track lastX and lastY for physics events
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
if (!self.stopped) {
self.vy += self.gravity;
self.x += self.vx;
self.y += self.vy;
self.vx *= self.friction;
self.vy *= self.friction;
// Pig-on-block collision: prevent pig from going inside the wood blocks
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
// Axis-aligned bounding box for block
var blockLeft = block.x - block.width / 2;
var blockRight = block.x + block.width / 2;
var blockTop = block.y - block.height / 2;
var blockBottom = block.y + block.height / 2;
// Only check if pig is above block and falling
if (self.y < block.y && self.vy > 0) {
// Check horizontal overlap
if (self.x + self.radius > blockLeft && self.x - self.radius < blockRight) {
// Check if pig's bottom is entering block's top
if (self.y + self.radius > blockTop && self.y - self.radius < blockBottom) {
// Place pig on top of block
self.y = blockTop - self.radius;
if (Math.abs(self.vy) > 2) {
self.vy = -self.vy * self.bounce;
self.vx *= 0.8;
// Transfer some force to the block below (crash effect)
block.vx += self.vx * 0.2;
block.vy += self.vy * 0.2;
} else {
self.vy = 0;
self.vx *= 0.8;
if (Math.abs(self.vx) < 1) {
self.stopped = true;
}
}
}
}
}
}
// Floor collision
if (self.y + self.radius > groundY) {
self.y = groundY - self.radius;
if (Math.abs(self.vy) > 2) {
self.vy = -self.vy * self.bounce;
self.vx *= 0.8;
} else {
self.vy = 0;
self.vx *= 0.8;
if (Math.abs(self.vx) < 1) {
self.stopped = true;
}
}
}
// Wall collision
if (self.x - self.radius < 0) {
self.x = self.radius;
self.vx = -self.vx * self.bounce;
}
if (self.x + self.radius > 2048) {
self.x = 2048 - self.radius;
self.vx = -self.vx * self.bounce;
}
}
// Update lastX and lastY after movement
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// --- Add Background Image ---
// --- Asset Initialization (shapes) ---
var background = LK.getAsset('block', {
anchorX: 0,
anchorY: 0
});
background.width = 2048;
background.height = 2732;
background.tint = 0x87ceeb; // Sky blue tint for now
background.x = 0;
background.y = 0;
game.addChild(background);
// --- Add Sun to the Sky ---
var sun = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
sun.width = 320;
sun.height = 320;
sun.tint = 0xfff700; // bright yellow
sun.x = 350;
sun.y = 350;
game.addChild(sun);
// --- Add Trees for Scenery ---
var treeTrunkColor = 0x8b5a2b;
var treeLeafColor = 0x228b22;
var treeCount = 4;
var treeSpacing = 2048 / (treeCount + 1);
for (var i = 0; i < treeCount; i++) {
// Trunk
var trunk = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 1
});
trunk.width = 40;
trunk.height = 220;
trunk.tint = treeTrunkColor;
trunk.x = treeSpacing * (i + 1);
trunk.y = groundY + 10;
game.addChild(trunk);
// Leaves (ellipse)
var leaves = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 1
});
leaves.width = 180;
leaves.height = 160;
leaves.tint = treeLeafColor;
leaves.x = trunk.x;
leaves.y = trunk.y - trunk.height + 10;
game.addChild(leaves);
}
// --- Game Variables ---
var birds = [];
var pigs = [];
var blocks = [];
var currentBird = null;
var birdIndex = 0;
var isDragging = false;
var dragStart = {
x: 0,
y: 0
};
var dragEnd = {
x: 0,
y: 0
};
var slingshotX = 180;
var slingshotY = 2000; // Raised slingshot higher (was 2450)
var slingshotBandLength = 180;
var maxBirds = 5;
var groundY = 2650;
var level = 1;
var launching = false;
var score = 0;
// --- GUI ---
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var birdsLeftTxt = new Text2('', {
size: 80,
fill: 0xFFFFFF
});
birdsLeftTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(birdsLeftTxt);
// --- Draw Ground ---
var ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0
});
ground.x = 0;
ground.y = groundY;
game.addChild(ground);
// --- Draw Little Mountain below the slingshot ---
var mountainWidth = 400;
var mountainHeight = 120;
var mountain = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 1
});
mountain.width = mountainWidth;
mountain.height = mountainHeight;
mountain.x = slingshotX;
mountain.y = groundY + 10; // Slightly below ground for overlap
mountain.tint = 0x8b7765; // Optional: make it look more like a mountain
game.addChild(mountain);
// --- Draw Slingshot ---
var slingshot = LK.getAsset('slingshot', {
anchorX: 0.5,
anchorY: 1
});
slingshot.x = slingshotX;
slingshot.y = slingshotY + 90;
game.addChild(slingshot);
// --- Level Setup ---
function setupLevel() {
// Add a golden star collectible above the house for bonus points
if (typeof star === "undefined") {
star = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 0.5
});
star.width = 80;
star.height = 80;
star.tint = 0xffd700; // gold color
star.isStar = true;
}
star.x = 1500;
star.y = groundY - 600;
star.collected = false;
star.visible = true;
game.addChild(star);
// Clear previous
for (var i = 0; i < birds.length; i++) birds[i].destroy();
for (var i = 0; i < pigs.length; i++) pigs[i].destroy();
for (var i = 0; i < blocks.length; i++) blocks[i].destroy();
birds = [];
pigs = [];
blocks = [];
birdIndex = 0;
score = 0;
LK.setScore(0);
scoreTxt.setText('0');
launching = false;
// Place birds
for (var i = 0; i < maxBirds; i++) {
var bird = new Bird();
bird.x = slingshotX - 120 - i * 80;
bird.y = slingshotY + 40;
birds.push(bird);
game.addChild(bird);
}
currentBird = birds[birdIndex];
// Place blocks to form a house shape
var houseCenterX = 1500;
var houseBottomY = groundY - 100;
var wallW = 180;
var wallH = 40;
var wallHeight = 4; // number of vertical wall blocks
var roofW = 180;
var roofH = 40;
// --- House base: two vertical walls (left/right), 4 blocks high ---
for (var i = 0; i < wallHeight; i++) {
// Left wall
var leftWall = new Block();
leftWall.x = houseCenterX - wallW / 2 + wallH / 2;
leftWall.y = houseBottomY - i * wallW;
leftWall.rotation = Math.PI / 2;
blocks.push(leftWall);
game.addChild(leftWall);
// Right wall
var rightWall = new Block();
rightWall.x = houseCenterX + wallW / 2 - wallH / 2;
rightWall.y = houseBottomY - i * wallW;
rightWall.rotation = Math.PI / 2;
blocks.push(rightWall);
game.addChild(rightWall);
}
// --- House base: bottom block (floor) ---
var houseFloor = new Block();
houseFloor.x = houseCenterX;
houseFloor.y = houseBottomY + wallH / 2;
blocks.push(houseFloor);
game.addChild(houseFloor);
// --- House base: top block (ceiling) ---
var houseCeiling = new Block();
houseCeiling.x = houseCenterX;
houseCeiling.y = houseBottomY - (wallHeight - 1) * wallW - wallH / 2;
blocks.push(houseCeiling);
game.addChild(houseCeiling);
// --- House roof: two diagonal blocks (left/right) ---
var roofLeft = new Block();
roofLeft.x = houseCenterX - wallW / 2 + wallH / 2 + 30;
roofLeft.y = houseCeiling.y - wallW / 2 + 10;
roofLeft.rotation = Math.PI / 4; // 45 degrees
blocks.push(roofLeft);
game.addChild(roofLeft);
var roofRight = new Block();
roofRight.x = houseCenterX + wallW / 2 - wallH / 2 - 30;
roofRight.y = houseCeiling.y - wallW / 2 + 10;
roofRight.rotation = -Math.PI / 4; // -45 degrees
blocks.push(roofRight);
game.addChild(roofRight);
// --- House roof: top horizontal block (roof ridge) ---
var roofRidge = new Block();
roofRidge.x = houseCenterX;
roofRidge.y = houseCeiling.y - wallW / 2 - roofH / 2 + 10;
blocks.push(roofRidge);
game.addChild(roofRidge);
// Place pig on the house ceiling
var pig1 = new Pig();
pig1.x = houseCeiling.x;
pig1.y = houseCeiling.y - houseCeiling.height / 2 - pig1.radius - 50;
pigs.push(pig1);
game.addChild(pig1);
// (Second pig inside the house removed)
// --- Ensure blocks and pigs are not hanging in the air ---
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
// Check if block is supported by ground or another block
var supported = false;
// Supported by ground
if (Math.abs(block.y + block.height / 2 - groundY) < 2) {
supported = true;
}
// Supported by another block below
for (var j = 0; j < blocks.length; j++) {
if (i === j) continue;
var other = blocks[j];
var blockBottom = block.y + block.height / 2;
var otherTop = other.y - other.height / 2;
var horizontalOverlap = block.x + block.width / 2 > other.x - other.width / 2 && block.x - block.width / 2 < other.x + other.width / 2;
if (horizontalOverlap && Math.abs(blockBottom - otherTop) < 2) {
supported = true;
break;
}
}
block.stopped = !supported ? false : block.stopped;
}
for (var i = 0; i < pigs.length; i++) {
var pig = pigs[i];
// Check if pig is supported by ground or a block
var supported = false;
// Supported by ground
if (Math.abs(pig.y + pig.radius - groundY) < 2) {
supported = true;
}
// Supported by a block below
for (var j = 0; j < blocks.length; j++) {
var block = blocks[j];
var pigBottom = pig.y + pig.radius;
var blockTop = block.y - block.height / 2;
var horizontalOverlap = pig.x + pig.radius > block.x - block.width / 2 && pig.x - pig.radius < block.x + block.width / 2;
if (horizontalOverlap && Math.abs(pigBottom - blockTop) < 2) {
supported = true;
break;
}
}
pig.stopped = !supported ? false : pig.stopped;
}
updateBirdsLeft();
// Reset star collectible if it exists
if (typeof star !== "undefined") {
star.collected = false;
star.visible = true;
}
}
// --- Update birds left text ---
function updateBirdsLeft() {
birdsLeftTxt.setText('Birds: ' + (maxBirds - birdIndex));
}
// --- Drag and Launch Logic ---
function getDistance(x1, y1, x2, y2) {
var dx = x2 - x1;
var dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
function getAngle(x1, y1, x2, y2) {
return Math.atan2(y2 - y1, x2 - x1);
}
// Only allow drag if not launched
game.down = function (x, y, obj) {
if (launching) return;
if (!currentBird || currentBird.launched) return;
// Only allow drag if touch is near the bird
var dist = getDistance(x, y, currentBird.x, currentBird.y);
if (dist < currentBird.radius * 1.5) {
isDragging = true;
dragStart.x = x;
dragStart.y = y;
}
};
game.move = function (x, y, obj) {
if (isDragging && currentBird && !currentBird.launched) {
// Limit drag distance
var dx = x - slingshotX;
var dy = y - slingshotY;
var dist = getDistance(x, y, slingshotX, slingshotY);
if (dist > slingshotBandLength) {
var angle = getAngle(slingshotX, slingshotY, x, y);
dx = Math.cos(angle) * slingshotBandLength;
dy = Math.sin(angle) * slingshotBandLength;
}
currentBird.x = slingshotX + dx;
currentBird.y = slingshotY + dy;
dragEnd.x = currentBird.x;
dragEnd.y = currentBird.y;
}
};
game.up = function (x, y, obj) {
if (isDragging && currentBird && !currentBird.launched) {
isDragging = false;
// Calculate launch velocity
var dx = slingshotX - currentBird.x;
var dy = slingshotY - currentBird.y;
currentBird.vx = dx * 0.18;
currentBird.vy = dy * 0.18;
currentBird.launched = true;
launching = true;
// Animate slingshot back
tween(currentBird, {
x: currentBird.x,
y: currentBird.y
}, {
duration: 80
});
}
};
// --- Main Game Loop ---
game.update = function () {
// Update all birds
for (var i = 0; i < birds.length; i++) {
birds[i].update();
}
// Update all pigs
for (var i = 0; i < pigs.length; i++) {
pigs[i].update();
}
// Update all blocks
for (var i = 0; i < blocks.length; i++) {
blocks[i].update();
}
// --- Check support for each block: if not supported, set stopped = false so it falls ---
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
// Check if block is supported by ground or another block
var supported = false;
// Supported by ground
if (Math.abs(block.y + block.height / 2 - groundY) < 2) {
supported = true;
}
// Supported by another block below
for (var j = 0; j < blocks.length; j++) {
if (i === j) continue;
var other = blocks[j];
var blockBottom = block.y + block.height / 2;
var otherTop = other.y - other.height / 2;
var horizontalOverlap = block.x + block.width / 2 > other.x - other.width / 2 && block.x - block.width / 2 < other.x + other.width / 2;
if (horizontalOverlap && Math.abs(blockBottom - otherTop) < 2) {
supported = true;
break;
}
}
// If not supported, set stopped = false so it will fall
if (!supported) {
block.stopped = false;
}
}
// Bird-block collision
for (var i = 0; i < birds.length; i++) {
var bird = birds[i];
if (!bird.launched) continue;
if (bird.stopped) continue;
// Collide with blocks
for (var j = 0; j < blocks.length; j++) {
var block = blocks[j];
// Remove block.stopped check so birds can always touch wood
if (block.intersectsCircle(bird)) {
// Calculate collision angle and force
var angle = getAngle(bird.x, bird.y, block.x, block.y);
var force = Math.sqrt(bird.vx * bird.vx + bird.vy * bird.vy) * 0.5;
// Apply force to block
block.vx += Math.cos(angle) * force * 0.7;
block.vy += Math.sin(angle) * force * 0.7;
// --- Ragdoll: add angular velocity to block based on bird impact ---
var impact = force * 0.2;
if (typeof block.angularVelocity === "undefined") block.angularVelocity = 0;
block.angularVelocity += impact * (Math.random() - 0.5);
// Apply bounce to bird
bird.vx = -bird.vx * 0.4;
bird.vy = -bird.vy * 0.4;
// Move bird out of block to prevent sticking
var overlap = bird.radius + Math.max(block.width, block.height) / 2 - getDistance(bird.x, bird.y, block.x, block.y);
if (overlap > 0) {
bird.x += Math.cos(angle) * overlap * 0.5;
bird.y += Math.sin(angle) * overlap * 0.5;
}
// Optionally, mark block as not stopped to allow it to move if hit hard
block.stopped = false;
// Birds deal damage to woods on collision
var birdDamage = Math.min(40, Math.max(5, Math.floor(force * 2)));
block.health -= birdDamage;
if (block.health <= 0) {
block.destroy();
blocks.splice(j, 1);
j--; // Adjust index since we removed a block
continue;
}
}
}
}
// Bird-pig collision
for (var i = 0; i < birds.length; i++) {
var bird = birds[i];
if (!bird.launched) continue;
if (bird.stopped) continue;
// Bird-star collision
if (typeof star !== "undefined" && star.visible && !star.collected && bird.intersectsCircle({
x: star.x,
y: star.y,
radius: star.width / 2
})) {
star.collected = true;
star.visible = false;
LK.setScore(LK.getScore() + 5000);
scoreTxt.setText(LK.getScore());
}
for (var j = 0; j < pigs.length; j++) {
var pig = pigs[j];
if (!pig.alive) continue;
if (bird.intersectsCircle(pig)) {
pig.alive = false;
pig.visible = false;
LK.setScore(LK.getScore() + 1000);
scoreTxt.setText(LK.getScore());
}
}
}
// Block-pig collision (falling blocks can kill pigs)
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
if (block.stopped) continue;
for (var j = 0; j < pigs.length; j++) {
var pig = pigs[j];
if (!pig.alive) continue;
// Treat pig as circle, block as rectangle
if (block.intersectsCircle(pig)) {
// Only kill pig if block is moving fast enough (e.g. falling hard)
var blockSpeed = Math.sqrt(block.vx * block.vx + block.vy * block.vy);
if (blockSpeed > 8) {
pig.alive = false;
pig.visible = false;
LK.setScore(LK.getScore() + 1000);
scoreTxt.setText(LK.getScore());
}
}
}
}
// --- Check support for each pig: if not supported, set stopped = false so it falls ---
for (var i = 0; i < pigs.length; i++) {
var pig = pigs[i];
if (!pig.alive) continue;
var supported = false;
// Supported by ground
if (Math.abs(pig.y + pig.radius - groundY) < 2) {
supported = true;
}
// Supported by a block below
for (var j = 0; j < blocks.length; j++) {
var block = blocks[j];
var pigBottom = pig.y + pig.radius;
var blockTop = block.y - block.height / 2;
var horizontalOverlap = pig.x + pig.radius > block.x - block.width / 2 && pig.x - pig.radius < block.x + block.width / 2;
if (horizontalOverlap && Math.abs(pigBottom - blockTop) < 2) {
supported = true;
break;
}
}
if (!supported) {
pig.stopped = false;
}
}
// Remove dead pigs
var allPigsDead = true;
for (var i = 0; i < pigs.length; i++) {
if (pigs[i].alive) {
allPigsDead = false;
break;
}
}
// If all pigs dead, win (only if there was at least one pig)
if (pigs.length > 0 && allPigsDead) {
LK.showYouWin();
return;
}
// If current bird stopped, move to next bird
if (currentBird && currentBird.launched && currentBird.stopped && !isDragging && launching) {
birdIndex++;
updateBirdsLeft();
if (birdIndex < birds.length) {
currentBird = birds[birdIndex];
// Move next bird to slingshot
tween(currentBird, {
x: slingshotX,
y: slingshotY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
launching = false;
}
});
} else {
// Out of birds, game over
LK.showGameOver();
return;
}
launching = false;
}
};
// --- Start Game ---
setupLevel();