/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdGraphic = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocity = 0;
self.gravity = 0.5;
self.jumpForce = -20;
self.flying = false;
self.onWire = true;
self.currentNest = null;
self.isJumpAllowed = true;
self.jump = function () {
// Only jump if allowed (when on a nest)
if (!self.isJumpAllowed) return;
// All jumps handled the same way - no special first jump anymore
if (self.onWire) {
self.onWire = false; // Bird leaves wire
}
// Normal jump physics for all jumps
self.velocity = self.jumpForce;
self.flying = true;
// Mark game as started on first jump
if (!game.gameStarted) {
game.gameStarted = true;
// Store safe landing height to prevent immediate game over
game.safeJumpHeight = self.y + 300; // Room for falling before game over
// Start continuous scrolling
game.isScrolling = true;
}
// Disable jumping until landing on a nest again
self.isJumpAllowed = false;
// Create stars effect
self.createStars();
// Play flying sound
LK.getSound('fly').play();
};
self.createStars = function () {
for (var i = 0; i < 5; i++) {
var star = LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x + (Math.random() * 60 - 30),
y: self.y + (Math.random() * 60 - 30),
alpha: 1
});
game.addChild(star);
// Animate the star
tween(star, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2,
x: star.x + (Math.random() * 40 - 20),
y: star.y + (Math.random() * 40 - 20)
}, {
duration: 500,
onFinish: function onFinish() {
game.removeChild(star);
}
});
}
};
self.update = function () {
if (!self.onWire) {
// Always apply gravity when bird is flying (both first jump and subsequent jumps)
if (self.flying) {
self.velocity += self.gravity;
self.y += self.velocity;
// Check if falling too fast
if (self.velocity > 20) {
self.velocity = 20;
}
}
} else if (self.currentNest) {
// Bird follows the nest
self.x = self.currentNest.x;
if (self.currentNest.y !== undefined) {
self.y = self.currentNest.y - 30;
}
} else if (self.onWire) {
// Safety check: if bird is somehow on wire but has no nest
// Find closest nest or place in the center of screen
// Don't immediately set flying to true to prevent immediate fall
if (self.currentNest === null) {
// Try to find the nearest nest
var nearestNest = null;
var minDistance = Infinity;
for (var i = 0; i < nests.length; i++) {
var distance = Math.abs(nests[i].x - self.x);
if (distance < minDistance) {
minDistance = distance;
nearestNest = nests[i];
}
}
if (nearestNest) {
self.currentNest = nearestNest;
nearestNest.occupied = true;
} else {
self.onWire = false;
self.flying = true;
self.velocity = 0;
}
}
}
};
self.down = function (x, y, obj) {
self.jump();
};
return self;
});
var Nest = Container.expand(function () {
var self = Container.call(this);
var nestGraphic = self.attachAsset('nest', {
anchorX: 0.5,
anchorY: 0.5
});
self.occupied = false;
self.lastX = undefined;
self.speed = 4; // Speed for horizontal movement
self.direction = 1; // Direction for horizontal movement
// Randomize initial direction
self.randomizeDirection = function () {
self.direction = Math.random() > 0.5 ? 1 : -1;
};
// Update method to handle movement and collisions
self.update = function () {
// Track last position for reference
if (self.lastX === undefined) self.lastX = self.x;
// Move nest horizontally
self.x += self.speed * self.direction;
// Check for wall collisions and reverse direction
var wallWidth = 30; // Width of the wall assets
var nestWidth = 100; // Width of nest
if (self.x <= wallWidth + nestWidth / 2) {
self.direction = 1; // Move right when hitting left wall
self.x = wallWidth + nestWidth / 2;
} else if (self.x >= 2048 - wallWidth - nestWidth / 2) {
self.direction = -1; // Move left when hitting right wall
self.x = 2048 - wallWidth - nestWidth / 2;
}
// Update last position
self.lastX = self.x;
};
// Initialize random direction
self.randomizeDirection();
return self;
});
var PowerLine = Container.expand(function () {
var self = Container.call(this);
var lineGraphic = self.attachAsset('powerLine', {
anchorX: 0,
anchorY: 0.5
});
// Power lines are static - no update method needed
return self;
});
var Star = Container.expand(function () {
var self = Container.call(this);
var starGraphic = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
var score = 0;
var highScore = storage.highScore || 0;
var powerLines = [];
var nests = [];
var bird;
var cameraY = 0;
var gameOver = false;
var lineSpacing = 300;
var maxLines = 10;
var nextLineY = 2500;
var ascentCount = 0;
var lastNestLevel = null;
var removedBottomLines = 0;
// Initialize score display
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
scoreTxt.x = -scoreTxt.width - 20;
scoreTxt.y = 20;
// Initialize high score display
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 40,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(highScoreTxt);
highScoreTxt.x = -highScoreTxt.width - 20;
highScoreTxt.y = 100;
// Create background
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0
});
game.addChild(background);
// Initialize bird
function initBird() {
bird = new Bird();
bird.x = 2048 / 2;
bird.y = 2500;
game.addChild(bird);
// Bird will be positioned on a nest in initGame
}
// Create a power line with nests
function createPowerLine(y) {
var powerLine = new PowerLine();
powerLine.y = y;
powerLine.initialY = y; // Store initial Y position
// Set power line to span the whole width, respecting wall boundaries
powerLine.x = 30; // Starting from left wall position
game.addChild(powerLine);
powerLines.push(powerLine);
// Calculate nest properties once - more efficient
var nestCount = Math.floor(Math.random() * 3) + 1;
// Ensure at least one nest on the bottommost power line
if (y === 2500) {
nestCount = Math.max(nestCount, 2); // Guarantee at least two nests on bottom line for better gameplay
}
var nestWidth = 100;
var wallWidth = 30; // Width of the wall assets
var availableWidth = 2048 - nestWidth - wallWidth * 2; // Account for walls
// Pre-calculate nest positions for better performance
var nestPositions = [];
for (var i = 0; i < nestCount; i++) {
// If this is the bottom line, ensure at least one nest is near the center
var nestX = y === 2500 && i === 0 ? 2048 / 2 // Center the first nest on bottom line
: wallWidth + nestWidth / 2 + Math.random() * (availableWidth - nestWidth);
nestPositions.push({
x: nestX,
y: y - 5
});
}
// Add nests in a small timeout to prevent UI blocking
LK.setTimeout(function () {
// Place nests randomly along the power line
for (var i = 0; i < nestCount; i++) {
var nest = new Nest();
// Random position along the power line
nest.x = nestPositions[i].x;
nest.y = nestPositions[i].y; // Slightly above the line
nest.initialY = y - 5; // Store initial Y position
game.addChild(nest);
nests.push(nest);
}
}, 0); // Even a 0ms timeout helps by deferring to next event loop
return powerLine;
}
// Initialize power lines
function initPowerLines() {
// Clear existing lines and nests - optimize by checking length first
if (powerLines.length > 0) {
for (var i = 0; i < powerLines.length; i++) {
game.removeChild(powerLines[i]);
}
}
if (nests.length > 0) {
for (var i = 0; i < nests.length; i++) {
game.removeChild(nests[i]);
}
}
powerLines = [];
nests = [];
// Create initial power lines - create in batches for better performance
nextLineY = 2500;
// Create lines in smaller batches to prevent blocking UI
var linesBatch = 3;
var currentLine = 0;
function createLineBatch() {
var batchEnd = Math.min(currentLine + linesBatch, maxLines);
for (var i = currentLine; i < batchEnd; i++) {
createPowerLine(nextLineY);
nextLineY -= lineSpacing;
}
currentLine = batchEnd;
if (currentLine < maxLines) {
LK.setTimeout(createLineBatch, 1);
}
}
createLineBatch();
}
// Initialize the game
function initGame() {
score = 0;
gameOver = false;
game.gameStarted = false; // Flag to track if game has started (first jump)
game.isFirstJump = false; // No longer needed as we use tween for first jump
game.birdHighestPosition = null; // Track bird's highest (lowest y value) position
game.safeJumpHeight = null; // Will be set on first jump
ascentCount = 0;
lastNestLevel = null;
removedBottomLines = 0;
updateScore(0);
// Set canvas size at the beginning only
cameraY = 0; // Reset camera position
game.cameraOffsetY = 0; // Smoothed camera offset for rendering
game.cameraTargetY = 0; // Target camera position
game.cameraSmoothness = 0.1; // How quickly camera follows (0-1)
game.scrollSpeed = 1; // Base scroll speed
game.jumpCount = 0; // Count of successful jumps
game.isScrolling = false; // Whether continuous scrolling is active
// Defer heavy initialization with a small timeout to allow the UI to render first
LK.setTimeout(function () {
initPowerLines();
initBird();
// Create walls at screen edges
createWalls();
// Give time for nests to be created, then place bird
LK.setTimeout(function () {
// Find a nest on the bottommost line and place bird on it
var bottomNest = null;
var bottomY = 0;
// First identify the bottommost line by finding the nest with largest Y value
for (var i = 0; i < nests.length; i++) {
if (nests[i].y > bottomY) {
bottomY = nests[i].y;
bottomNest = nests[i];
}
}
// If we found a bottom nest, place bird on it
if (bottomNest) {
bird.currentNest = bottomNest;
bird.x = bottomNest.x;
bird.y = bottomNest.y - 30;
bird.isJumpAllowed = true;
bird.onWire = true;
bottomNest.occupied = true;
} else {
// Ensure bird is properly placed on the bottommost power line if no nest was found
var bottomLine = null;
var maxY = -Infinity;
for (var i = 0; i < powerLines.length; i++) {
if (powerLines[i].initialY > maxY) {
maxY = powerLines[i].initialY;
bottomLine = powerLines[i];
}
}
if (bottomLine) {
// Create a nest on the bottom line
var nest = new Nest();
nest.x = 2048 / 2; // Center of screen
nest.y = bottomLine.y - 5; // Slightly above the line
nest.initialY = bottomLine.initialY - 5;
game.addChild(nest);
nests.push(nest);
// Place bird on this nest
bird.currentNest = nest;
bird.x = nest.x;
bird.y = nest.y - 30;
bird.isJumpAllowed = true;
bird.onWire = true;
nest.occupied = true;
}
}
// Play background music
LK.playMusic('gameMusic');
}, 100); // A bit more delay to ensure nests are created
}, 50); // Short delay to let the UI render first
}
// Update score display
function updateScore(newScore) {
score = newScore;
scoreTxt.setText('Score: ' + score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
}
// Check if bird is on a nest
function checkNestCollision() {
if (bird.velocity > 0) {
// Only check when bird is falling
for (var i = 0; i < nests.length; i++) {
var nest = nests[i];
if (!nest.occupied && bird.intersects(nest)) {
bird.velocity = 0;
bird.onWire = true;
bird.currentNest = nest;
nest.occupied = true;
// Re-enable jumping now that bird has landed
bird.isJumpAllowed = true;
// Update score based on height
var newScore = Math.floor((2732 - nest.y) / 100);
updateScore(newScore);
// Check for successful ascent to a higher wire
var currentLevel = Math.floor(nest.initialY / lineSpacing);
if (lastNestLevel !== null && currentLevel < lastNestLevel) {
ascentCount++;
// Increment jump count and increase difficulty every 10 jumps
game.jumpCount++;
if (game.jumpCount % 10 === 0) {
// Increase scroll speed
game.scrollSpeed += 0.2;
// Flash the screen to indicate difficulty increase
LK.effects.flashScreen(0x00ff00, 300);
}
// Every 3 successful ascents, remove the bottommost wire
if (ascentCount % 3 === 0) {
removeBottomWire();
}
}
lastNestLevel = currentLevel;
// Play landing sound
LK.getSound('land').play();
return true;
}
}
}
return false;
}
// Move camera based on continuous scrolling after first jump
function updateCamera() {
// Only handle bird position on wire
if (bird.onWire && bird.currentNest) {
bird.y = bird.currentNest.y - 30;
}
// Implement continuous scrolling after the game has started
if (game.isScrolling) {
// Continuously increase camera target position based on scrollSpeed
game.cameraTargetY += game.scrollSpeed;
// Smoothly move the camera using linear interpolation (lerp)
game.cameraOffsetY += (game.cameraTargetY - game.cameraOffsetY) * game.cameraSmoothness;
// Apply the camera offset to all game objects that need to move with the camera
for (var i = 0; i < powerLines.length; i++) {
powerLines[i].y = powerLines[i].initialY + game.cameraOffsetY;
}
for (var i = 0; i < nests.length; i++) {
nests[i].y = nests[i].initialY + game.cameraOffsetY;
}
// Update background and walls position if they exist
var children = game.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.initialY !== undefined && child !== bird) {
child.y = child.initialY + game.cameraOffsetY;
}
}
// Update cameraY for game logic (line generation, etc.)
cameraY = game.cameraOffsetY;
}
}
// Generate new lines if needed
function checkGenerateLines() {
if (nextLineY > cameraY - 2732) {
createPowerLine(nextLineY);
nextLineY -= lineSpacing;
}
}
// Create walls at screen edges
function createWalls() {
// Prepare wall properties before asset loading
var wallProps = {
anchorX: 0,
anchorY: 0
};
// Using setTimeout to create a small delay before accessing assets
LK.setTimeout(function () {
var leftWall = LK.getAsset('wall', {
anchorX: 0,
anchorY: 0
});
leftWall.x = 0;
leftWall.y = 0;
leftWall.initialY = 0; // Store initial Y position
game.addChild(leftWall);
var rightWall = LK.getAsset('wall', {
anchorX: 0,
anchorY: 0
});
rightWall.x = 2048 - rightWall.width;
rightWall.y = 0;
rightWall.initialY = 0; // Store initial Y position
game.addChild(rightWall);
}, 5); // Small delay to prevent blocking UI
}
// Function to remove the bottommost wire and its nests
function removeBottomWire() {
// Find the bottommost wire (highest y value)
var bottomLineIndex = -1;
var bottomY = -Infinity;
for (var i = 0; i < powerLines.length; i++) {
if (powerLines[i].initialY > bottomY) {
bottomY = powerLines[i].initialY;
bottomLineIndex = i;
}
}
if (bottomLineIndex >= 0) {
// Remove the wire
game.removeChild(powerLines[bottomLineIndex]);
var removedLine = powerLines.splice(bottomLineIndex, 1)[0];
// Remove all nests on this wire
for (var i = nests.length - 1; i >= 0; i--) {
// Check if the nest is on the removed wire (approximately same initialY)
if (Math.abs(nests[i].initialY - (removedLine.initialY - 5)) < 10) {
// If bird is on this nest, it will fall
if (bird.currentNest === nests[i]) {
bird.currentNest = null;
bird.onWire = false;
bird.isJumpAllowed = false;
bird.velocity = 1; // Start falling
}
game.removeChild(nests[i]);
nests.splice(i, 1);
}
}
removedBottomLines++;
}
}
// Remove off-screen power lines and nests
function cleanupObjects() {
for (var i = powerLines.length - 1; i >= 0; i--) {
if (powerLines[i].y > cameraY + 3000) {
game.removeChild(powerLines[i]);
powerLines.splice(i, 1);
}
}
for (var i = nests.length - 1; i >= 0; i--) {
if (nests[i].y > cameraY + 3000) {
game.removeChild(nests[i]);
nests.splice(i, 1);
}
}
}
// Handle game over
function handleGameOver() {
// Don't check for game over if game hasn't started yet
if (!game.gameStarted) return;
// Game over condition - bird falls off bottom of screen
// With continuous scrolling, we need to check if bird is too far below the camera view
var screenBottom = 2732;
var visibleBottom = screenBottom + game.cameraOffsetY;
var isInGracePeriod = game.gameStarted && LK.ticks < 120;
if (!gameOver && !isInGracePeriod && bird.y > visibleBottom) {
gameOver = true;
LK.getSound('fall').play();
LK.showGameOver();
}
}
// Main game loop
game.update = function () {
if (gameOver) return;
// Update bird if it exists
if (bird) {
// Double-check bird is defined before calling update
if (typeof bird.update === 'function') {
bird.update();
}
}
// Power lines are static, no need to update them
// Update nests
for (var i = 0; i < nests.length; i++) {
nests[i].update();
}
// Check if bird landed on a nest and bird exists
if (bird) {
checkNestCollision();
// Update camera position
updateCamera();
}
// Generate new lines if needed
checkGenerateLines();
// Remove off-screen objects
cleanupObjects();
// Check game over condition
if (bird) {
handleGameOver();
}
};
// Bird jump on screen tap/click
game.down = function (x, y, obj) {
if (!gameOver && bird && typeof bird.jump === 'function') {
bird.jump();
}
};
// Show loading indicator
var loadingText = new Text2('Loading...', {
size: 80,
fill: 0xFFFFFF
});
loadingText.anchor.set(0.5, 0.5);
loadingText.x = 2048 / 2;
loadingText.y = 2732 / 2;
game.addChild(loadingText);
// Create a preloader to load game assets efficiently
function startGameWithPreloader() {
// Use tween to animate loading text while assets are prepared
tween(loadingText, {
alpha: 0.5
}, {
duration: 500,
easing: tween.easeInOutQuad,
loop: true,
yoyo: true
});
// Defer game initialization to prevent startup lag
LK.setTimeout(function () {
// Remove loading indicator
tween(loadingText, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
game.removeChild(loadingText);
// Initialize game
initGame();
}
});
}, 200); // Give enough time for engine to initialize
}
// Start game with preloader
startGameWithPreloader(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdGraphic = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocity = 0;
self.gravity = 0.5;
self.jumpForce = -20;
self.flying = false;
self.onWire = true;
self.currentNest = null;
self.isJumpAllowed = true;
self.jump = function () {
// Only jump if allowed (when on a nest)
if (!self.isJumpAllowed) return;
// All jumps handled the same way - no special first jump anymore
if (self.onWire) {
self.onWire = false; // Bird leaves wire
}
// Normal jump physics for all jumps
self.velocity = self.jumpForce;
self.flying = true;
// Mark game as started on first jump
if (!game.gameStarted) {
game.gameStarted = true;
// Store safe landing height to prevent immediate game over
game.safeJumpHeight = self.y + 300; // Room for falling before game over
// Start continuous scrolling
game.isScrolling = true;
}
// Disable jumping until landing on a nest again
self.isJumpAllowed = false;
// Create stars effect
self.createStars();
// Play flying sound
LK.getSound('fly').play();
};
self.createStars = function () {
for (var i = 0; i < 5; i++) {
var star = LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x + (Math.random() * 60 - 30),
y: self.y + (Math.random() * 60 - 30),
alpha: 1
});
game.addChild(star);
// Animate the star
tween(star, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2,
x: star.x + (Math.random() * 40 - 20),
y: star.y + (Math.random() * 40 - 20)
}, {
duration: 500,
onFinish: function onFinish() {
game.removeChild(star);
}
});
}
};
self.update = function () {
if (!self.onWire) {
// Always apply gravity when bird is flying (both first jump and subsequent jumps)
if (self.flying) {
self.velocity += self.gravity;
self.y += self.velocity;
// Check if falling too fast
if (self.velocity > 20) {
self.velocity = 20;
}
}
} else if (self.currentNest) {
// Bird follows the nest
self.x = self.currentNest.x;
if (self.currentNest.y !== undefined) {
self.y = self.currentNest.y - 30;
}
} else if (self.onWire) {
// Safety check: if bird is somehow on wire but has no nest
// Find closest nest or place in the center of screen
// Don't immediately set flying to true to prevent immediate fall
if (self.currentNest === null) {
// Try to find the nearest nest
var nearestNest = null;
var minDistance = Infinity;
for (var i = 0; i < nests.length; i++) {
var distance = Math.abs(nests[i].x - self.x);
if (distance < minDistance) {
minDistance = distance;
nearestNest = nests[i];
}
}
if (nearestNest) {
self.currentNest = nearestNest;
nearestNest.occupied = true;
} else {
self.onWire = false;
self.flying = true;
self.velocity = 0;
}
}
}
};
self.down = function (x, y, obj) {
self.jump();
};
return self;
});
var Nest = Container.expand(function () {
var self = Container.call(this);
var nestGraphic = self.attachAsset('nest', {
anchorX: 0.5,
anchorY: 0.5
});
self.occupied = false;
self.lastX = undefined;
self.speed = 4; // Speed for horizontal movement
self.direction = 1; // Direction for horizontal movement
// Randomize initial direction
self.randomizeDirection = function () {
self.direction = Math.random() > 0.5 ? 1 : -1;
};
// Update method to handle movement and collisions
self.update = function () {
// Track last position for reference
if (self.lastX === undefined) self.lastX = self.x;
// Move nest horizontally
self.x += self.speed * self.direction;
// Check for wall collisions and reverse direction
var wallWidth = 30; // Width of the wall assets
var nestWidth = 100; // Width of nest
if (self.x <= wallWidth + nestWidth / 2) {
self.direction = 1; // Move right when hitting left wall
self.x = wallWidth + nestWidth / 2;
} else if (self.x >= 2048 - wallWidth - nestWidth / 2) {
self.direction = -1; // Move left when hitting right wall
self.x = 2048 - wallWidth - nestWidth / 2;
}
// Update last position
self.lastX = self.x;
};
// Initialize random direction
self.randomizeDirection();
return self;
});
var PowerLine = Container.expand(function () {
var self = Container.call(this);
var lineGraphic = self.attachAsset('powerLine', {
anchorX: 0,
anchorY: 0.5
});
// Power lines are static - no update method needed
return self;
});
var Star = Container.expand(function () {
var self = Container.call(this);
var starGraphic = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
var score = 0;
var highScore = storage.highScore || 0;
var powerLines = [];
var nests = [];
var bird;
var cameraY = 0;
var gameOver = false;
var lineSpacing = 300;
var maxLines = 10;
var nextLineY = 2500;
var ascentCount = 0;
var lastNestLevel = null;
var removedBottomLines = 0;
// Initialize score display
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
scoreTxt.x = -scoreTxt.width - 20;
scoreTxt.y = 20;
// Initialize high score display
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 40,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(highScoreTxt);
highScoreTxt.x = -highScoreTxt.width - 20;
highScoreTxt.y = 100;
// Create background
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0
});
game.addChild(background);
// Initialize bird
function initBird() {
bird = new Bird();
bird.x = 2048 / 2;
bird.y = 2500;
game.addChild(bird);
// Bird will be positioned on a nest in initGame
}
// Create a power line with nests
function createPowerLine(y) {
var powerLine = new PowerLine();
powerLine.y = y;
powerLine.initialY = y; // Store initial Y position
// Set power line to span the whole width, respecting wall boundaries
powerLine.x = 30; // Starting from left wall position
game.addChild(powerLine);
powerLines.push(powerLine);
// Calculate nest properties once - more efficient
var nestCount = Math.floor(Math.random() * 3) + 1;
// Ensure at least one nest on the bottommost power line
if (y === 2500) {
nestCount = Math.max(nestCount, 2); // Guarantee at least two nests on bottom line for better gameplay
}
var nestWidth = 100;
var wallWidth = 30; // Width of the wall assets
var availableWidth = 2048 - nestWidth - wallWidth * 2; // Account for walls
// Pre-calculate nest positions for better performance
var nestPositions = [];
for (var i = 0; i < nestCount; i++) {
// If this is the bottom line, ensure at least one nest is near the center
var nestX = y === 2500 && i === 0 ? 2048 / 2 // Center the first nest on bottom line
: wallWidth + nestWidth / 2 + Math.random() * (availableWidth - nestWidth);
nestPositions.push({
x: nestX,
y: y - 5
});
}
// Add nests in a small timeout to prevent UI blocking
LK.setTimeout(function () {
// Place nests randomly along the power line
for (var i = 0; i < nestCount; i++) {
var nest = new Nest();
// Random position along the power line
nest.x = nestPositions[i].x;
nest.y = nestPositions[i].y; // Slightly above the line
nest.initialY = y - 5; // Store initial Y position
game.addChild(nest);
nests.push(nest);
}
}, 0); // Even a 0ms timeout helps by deferring to next event loop
return powerLine;
}
// Initialize power lines
function initPowerLines() {
// Clear existing lines and nests - optimize by checking length first
if (powerLines.length > 0) {
for (var i = 0; i < powerLines.length; i++) {
game.removeChild(powerLines[i]);
}
}
if (nests.length > 0) {
for (var i = 0; i < nests.length; i++) {
game.removeChild(nests[i]);
}
}
powerLines = [];
nests = [];
// Create initial power lines - create in batches for better performance
nextLineY = 2500;
// Create lines in smaller batches to prevent blocking UI
var linesBatch = 3;
var currentLine = 0;
function createLineBatch() {
var batchEnd = Math.min(currentLine + linesBatch, maxLines);
for (var i = currentLine; i < batchEnd; i++) {
createPowerLine(nextLineY);
nextLineY -= lineSpacing;
}
currentLine = batchEnd;
if (currentLine < maxLines) {
LK.setTimeout(createLineBatch, 1);
}
}
createLineBatch();
}
// Initialize the game
function initGame() {
score = 0;
gameOver = false;
game.gameStarted = false; // Flag to track if game has started (first jump)
game.isFirstJump = false; // No longer needed as we use tween for first jump
game.birdHighestPosition = null; // Track bird's highest (lowest y value) position
game.safeJumpHeight = null; // Will be set on first jump
ascentCount = 0;
lastNestLevel = null;
removedBottomLines = 0;
updateScore(0);
// Set canvas size at the beginning only
cameraY = 0; // Reset camera position
game.cameraOffsetY = 0; // Smoothed camera offset for rendering
game.cameraTargetY = 0; // Target camera position
game.cameraSmoothness = 0.1; // How quickly camera follows (0-1)
game.scrollSpeed = 1; // Base scroll speed
game.jumpCount = 0; // Count of successful jumps
game.isScrolling = false; // Whether continuous scrolling is active
// Defer heavy initialization with a small timeout to allow the UI to render first
LK.setTimeout(function () {
initPowerLines();
initBird();
// Create walls at screen edges
createWalls();
// Give time for nests to be created, then place bird
LK.setTimeout(function () {
// Find a nest on the bottommost line and place bird on it
var bottomNest = null;
var bottomY = 0;
// First identify the bottommost line by finding the nest with largest Y value
for (var i = 0; i < nests.length; i++) {
if (nests[i].y > bottomY) {
bottomY = nests[i].y;
bottomNest = nests[i];
}
}
// If we found a bottom nest, place bird on it
if (bottomNest) {
bird.currentNest = bottomNest;
bird.x = bottomNest.x;
bird.y = bottomNest.y - 30;
bird.isJumpAllowed = true;
bird.onWire = true;
bottomNest.occupied = true;
} else {
// Ensure bird is properly placed on the bottommost power line if no nest was found
var bottomLine = null;
var maxY = -Infinity;
for (var i = 0; i < powerLines.length; i++) {
if (powerLines[i].initialY > maxY) {
maxY = powerLines[i].initialY;
bottomLine = powerLines[i];
}
}
if (bottomLine) {
// Create a nest on the bottom line
var nest = new Nest();
nest.x = 2048 / 2; // Center of screen
nest.y = bottomLine.y - 5; // Slightly above the line
nest.initialY = bottomLine.initialY - 5;
game.addChild(nest);
nests.push(nest);
// Place bird on this nest
bird.currentNest = nest;
bird.x = nest.x;
bird.y = nest.y - 30;
bird.isJumpAllowed = true;
bird.onWire = true;
nest.occupied = true;
}
}
// Play background music
LK.playMusic('gameMusic');
}, 100); // A bit more delay to ensure nests are created
}, 50); // Short delay to let the UI render first
}
// Update score display
function updateScore(newScore) {
score = newScore;
scoreTxt.setText('Score: ' + score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
}
// Check if bird is on a nest
function checkNestCollision() {
if (bird.velocity > 0) {
// Only check when bird is falling
for (var i = 0; i < nests.length; i++) {
var nest = nests[i];
if (!nest.occupied && bird.intersects(nest)) {
bird.velocity = 0;
bird.onWire = true;
bird.currentNest = nest;
nest.occupied = true;
// Re-enable jumping now that bird has landed
bird.isJumpAllowed = true;
// Update score based on height
var newScore = Math.floor((2732 - nest.y) / 100);
updateScore(newScore);
// Check for successful ascent to a higher wire
var currentLevel = Math.floor(nest.initialY / lineSpacing);
if (lastNestLevel !== null && currentLevel < lastNestLevel) {
ascentCount++;
// Increment jump count and increase difficulty every 10 jumps
game.jumpCount++;
if (game.jumpCount % 10 === 0) {
// Increase scroll speed
game.scrollSpeed += 0.2;
// Flash the screen to indicate difficulty increase
LK.effects.flashScreen(0x00ff00, 300);
}
// Every 3 successful ascents, remove the bottommost wire
if (ascentCount % 3 === 0) {
removeBottomWire();
}
}
lastNestLevel = currentLevel;
// Play landing sound
LK.getSound('land').play();
return true;
}
}
}
return false;
}
// Move camera based on continuous scrolling after first jump
function updateCamera() {
// Only handle bird position on wire
if (bird.onWire && bird.currentNest) {
bird.y = bird.currentNest.y - 30;
}
// Implement continuous scrolling after the game has started
if (game.isScrolling) {
// Continuously increase camera target position based on scrollSpeed
game.cameraTargetY += game.scrollSpeed;
// Smoothly move the camera using linear interpolation (lerp)
game.cameraOffsetY += (game.cameraTargetY - game.cameraOffsetY) * game.cameraSmoothness;
// Apply the camera offset to all game objects that need to move with the camera
for (var i = 0; i < powerLines.length; i++) {
powerLines[i].y = powerLines[i].initialY + game.cameraOffsetY;
}
for (var i = 0; i < nests.length; i++) {
nests[i].y = nests[i].initialY + game.cameraOffsetY;
}
// Update background and walls position if they exist
var children = game.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.initialY !== undefined && child !== bird) {
child.y = child.initialY + game.cameraOffsetY;
}
}
// Update cameraY for game logic (line generation, etc.)
cameraY = game.cameraOffsetY;
}
}
// Generate new lines if needed
function checkGenerateLines() {
if (nextLineY > cameraY - 2732) {
createPowerLine(nextLineY);
nextLineY -= lineSpacing;
}
}
// Create walls at screen edges
function createWalls() {
// Prepare wall properties before asset loading
var wallProps = {
anchorX: 0,
anchorY: 0
};
// Using setTimeout to create a small delay before accessing assets
LK.setTimeout(function () {
var leftWall = LK.getAsset('wall', {
anchorX: 0,
anchorY: 0
});
leftWall.x = 0;
leftWall.y = 0;
leftWall.initialY = 0; // Store initial Y position
game.addChild(leftWall);
var rightWall = LK.getAsset('wall', {
anchorX: 0,
anchorY: 0
});
rightWall.x = 2048 - rightWall.width;
rightWall.y = 0;
rightWall.initialY = 0; // Store initial Y position
game.addChild(rightWall);
}, 5); // Small delay to prevent blocking UI
}
// Function to remove the bottommost wire and its nests
function removeBottomWire() {
// Find the bottommost wire (highest y value)
var bottomLineIndex = -1;
var bottomY = -Infinity;
for (var i = 0; i < powerLines.length; i++) {
if (powerLines[i].initialY > bottomY) {
bottomY = powerLines[i].initialY;
bottomLineIndex = i;
}
}
if (bottomLineIndex >= 0) {
// Remove the wire
game.removeChild(powerLines[bottomLineIndex]);
var removedLine = powerLines.splice(bottomLineIndex, 1)[0];
// Remove all nests on this wire
for (var i = nests.length - 1; i >= 0; i--) {
// Check if the nest is on the removed wire (approximately same initialY)
if (Math.abs(nests[i].initialY - (removedLine.initialY - 5)) < 10) {
// If bird is on this nest, it will fall
if (bird.currentNest === nests[i]) {
bird.currentNest = null;
bird.onWire = false;
bird.isJumpAllowed = false;
bird.velocity = 1; // Start falling
}
game.removeChild(nests[i]);
nests.splice(i, 1);
}
}
removedBottomLines++;
}
}
// Remove off-screen power lines and nests
function cleanupObjects() {
for (var i = powerLines.length - 1; i >= 0; i--) {
if (powerLines[i].y > cameraY + 3000) {
game.removeChild(powerLines[i]);
powerLines.splice(i, 1);
}
}
for (var i = nests.length - 1; i >= 0; i--) {
if (nests[i].y > cameraY + 3000) {
game.removeChild(nests[i]);
nests.splice(i, 1);
}
}
}
// Handle game over
function handleGameOver() {
// Don't check for game over if game hasn't started yet
if (!game.gameStarted) return;
// Game over condition - bird falls off bottom of screen
// With continuous scrolling, we need to check if bird is too far below the camera view
var screenBottom = 2732;
var visibleBottom = screenBottom + game.cameraOffsetY;
var isInGracePeriod = game.gameStarted && LK.ticks < 120;
if (!gameOver && !isInGracePeriod && bird.y > visibleBottom) {
gameOver = true;
LK.getSound('fall').play();
LK.showGameOver();
}
}
// Main game loop
game.update = function () {
if (gameOver) return;
// Update bird if it exists
if (bird) {
// Double-check bird is defined before calling update
if (typeof bird.update === 'function') {
bird.update();
}
}
// Power lines are static, no need to update them
// Update nests
for (var i = 0; i < nests.length; i++) {
nests[i].update();
}
// Check if bird landed on a nest and bird exists
if (bird) {
checkNestCollision();
// Update camera position
updateCamera();
}
// Generate new lines if needed
checkGenerateLines();
// Remove off-screen objects
cleanupObjects();
// Check game over condition
if (bird) {
handleGameOver();
}
};
// Bird jump on screen tap/click
game.down = function (x, y, obj) {
if (!gameOver && bird && typeof bird.jump === 'function') {
bird.jump();
}
};
// Show loading indicator
var loadingText = new Text2('Loading...', {
size: 80,
fill: 0xFFFFFF
});
loadingText.anchor.set(0.5, 0.5);
loadingText.x = 2048 / 2;
loadingText.y = 2732 / 2;
game.addChild(loadingText);
// Create a preloader to load game assets efficiently
function startGameWithPreloader() {
// Use tween to animate loading text while assets are prepared
tween(loadingText, {
alpha: 0.5
}, {
duration: 500,
easing: tween.easeInOutQuad,
loop: true,
yoyo: true
});
// Defer game initialization to prevent startup lag
LK.setTimeout(function () {
// Remove loading indicator
tween(loadingText, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
game.removeChild(loadingText);
// Initialize game
initGame();
}
});
}, 200); // Give enough time for engine to initialize
}
// Start game with preloader
startGameWithPreloader();