/**** * 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();