/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
coinCount: 0,
leaderboard: []
});
/****
* Classes
****/
var Bus = Container.expand(function () {
var self = Container.call(this);
var busSprite = self.attachAsset('bus', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = busSprite.width;
self.height = busSprite.height;
self.speed = 1; // All buses have the same base speed
self.lane = 0; // 0-3 for the four road lanes
self.update = function () {
self.lastX = self.x;
// Move vertically from top to bottom
self.y += gameSpeed * self.speed;
if (self.y > 2732 + self.height) {
self.resetPosition();
}
};
self.resetPosition = function () {
// Reset to top of screen
self.y = -self.height - Math.random() * 300;
// Randomly assign to a lane
self.lane = Math.floor(Math.random() * 4);
self.x = roadPositions[self.lane];
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinSprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = coinSprite.width;
self.height = coinSprite.height;
self.lane = 1;
// Coin animation (rotate)
LK.setInterval(function () {
tween(coinSprite, {
rotation: coinSprite.rotation + Math.PI * 2
}, {
duration: 1000
});
}, 1000);
self.update = function () {
self.y += gameSpeed;
if (self.y > 2732 + self.height) {
self.destroy();
}
};
return self;
});
var CoinParticle = Container.expand(function () {
var self = Container.call(this);
var coinSprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.startX = 0;
self.startY = 0;
self.targetX = 0;
self.targetY = 0;
self.init = function (startX, startY, targetX, targetY) {
self.x = startX;
self.y = startY;
self.startX = startX;
self.startY = startY;
self.targetX = targetX;
self.targetY = targetY;
// Add rotation to make coin spin while flying
var rotateInterval = LK.setInterval(function () {
coinSprite.rotation += 0.2;
}, 16);
// Create coin flight path animation with arc
var controlPointX = startX + (targetX - startX) * 0.5;
var controlPointY = startY - 200; // Arc upward
var steps = 20; // Number of steps for the arc
var stepDuration = 800 / steps; // Duration for each step
// Animate along bezier curve
function animateStep(i) {
if (i >= steps) {
// Animation complete
LK.clearInterval(rotateInterval);
// Flash the coin counter when coin arrives
tween(coinTxt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(coinTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
self.destroy();
return;
}
var t = i / steps;
// Quadratic bezier curve calculation
var newX = (1 - t) * (1 - t) * startX + 2 * (1 - t) * t * controlPointX + t * t * targetX;
var newY = (1 - t) * (1 - t) * startY + 2 * (1 - t) * t * controlPointY + t * t * targetY;
tween(self, {
x: newX,
y: newY,
scaleX: 0.8 - 0.3 * t,
scaleY: 0.8 - 0.3 * t
}, {
duration: stepDuration,
onFinish: function onFinish() {
animateStep(i + 1);
}
});
}
// Start the animation
animateStep(0);
};
return self;
});
var Magnet = Container.expand(function () {
var self = Container.call(this);
var magnetSprite = self.attachAsset('miknatis', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Simplified Magnet class for visual reference only
return self;
});
var MiniBus = Container.expand(function () {
var self = Container.call(this);
// Replace bus sprite with jumpboard
var busSprite = self.attachAsset('jumpboard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1
});
self.width = busSprite.width * 1.5;
self.height = busSprite.height;
self.speed = 1.2; // Slightly faster than regular buses
self.lane = 0; // 0-3 for the four road lanes
// No need for green tint as we're using the jumping board asset now
self.update = function () {
self.lastX = self.x;
// Move vertically from top to bottom
self.y += gameSpeed * self.speed;
if (self.y > 2732 + self.height) {
self.resetPosition();
}
};
self.resetPosition = function () {
// Reset to top of screen with more randomization
self.y = -self.height - Math.random() * 1500; // Further increased random variance
// Randomly assign to a lane
self.lane = Math.floor(Math.random() * 4);
self.x = roadPositions[self.lane];
// Check for other jumping boards in this lane and ensure spacing
var jumpingBoardsInLane = [];
for (var i = 0; i < buses.length; i++) {
if (buses[i] instanceof MiniBus && buses[i] !== self && buses[i].lane === self.lane) {
jumpingBoardsInLane.push(buses[i]);
}
}
// Ensure minimum spacing with other jumping boards in the same lane - increased spacing requirement
for (var i = 0; i < jumpingBoardsInLane.length; i++) {
var otherBoard = jumpingBoardsInLane[i];
if (otherBoard.y < 0 && Math.abs(self.y - otherBoard.y) < self.height * 20) {
// Greatly increased minimum distance
// If too close to another jumping board, move much further up
self.y = otherBoard.y - (self.height * 20 + Math.random() * 800); // Increased spacing
}
}
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
// Create container for obstacle parts
var obstacleContainer = new Container();
self.addChild(obstacleContainer);
// Create red obstacle (full obstacle is dangerous)
var redPart = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff0000,
// Make obstacle red
scaleY: 1.0
});
self.redPart = redPart;
self.width = redPart.width;
self.height = redPart.height;
self.lane = 1;
self.type = "normal"; // normal, low, high
self.canSlideUnder = true; // All obstacles can be slid under
self.setType = function (newType) {
self.type = newType;
// Keep consistent size regardless of type
if (newType === "low") {
// Just adjust position, not size
self.y += self.height * 0.2;
} else if (newType === "high") {
// Just adjust position, not size
self.y -= self.height * 0.2;
}
};
// Check if player is colliding with red part
self.isCollidingWithRedPart = function (player) {
var redPartBounds = {
top: self.y - redPart.height * 0.5,
bottom: self.y + redPart.height * 0.5,
left: self.x - redPart.width * 0.5,
right: self.x + redPart.width * 0.5
};
var playerBounds = {
top: player.y - player.height * 0.5,
bottom: player.y + player.height * 0.5,
left: player.x - player.width * 0.5,
right: player.x + player.width * 0.5
};
return !(redPartBounds.right < playerBounds.left || redPartBounds.left > playerBounds.right || redPartBounds.bottom < playerBounds.top || redPartBounds.top > playerBounds.bottom);
};
self.update = function () {
self.y += gameSpeed;
if (self.y > 2732 + self.height) {
self.destroy();
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = playerSprite.width;
self.height = playerSprite.height;
self.lane = 1; // 0=left, 1=center, 2=right
self.isJumping = false;
self.isSliding = false;
// Apply golden skin if purchased
if (storage.purchases && storage.purchases["Golden Player"] === true) {
playerSprite.tint = 0xFFD700; // Gold color
}
// No lifesaver/shield functionality
// Add activateShield method
self.activateShield = function () {
// Create shield effect around player
var shieldActive = true;
// Visual indicator for shield
var shieldEffect = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00FFFF,
scaleX: 2,
scaleY: 2,
alpha: 0.5
});
self.addChild(shieldEffect);
// Create pulsing animation for shield
function pulseShield() {
tween(shieldEffect, {
scaleX: 2.3,
scaleY: 2.3,
alpha: 0.3
}, {
duration: 500,
onFinish: function onFinish() {
tween(shieldEffect, {
scaleX: 2,
scaleY: 2,
alpha: 0.5
}, {
duration: 500,
onFinish: pulseShield
});
}
});
}
pulseShield();
// Shield lasts for 10 seconds
LK.setTimeout(function () {
shieldActive = false;
// Fade out shield effect
tween(shieldEffect, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
shieldEffect.destroy();
}
});
}, 10000);
};
self.jump = function () {
if (!self.isJumping) {
self.isJumping = true;
var startY = self.y;
LK.getSound('jump').play();
tween(self, {
y: startY - 300
}, {
duration: 550,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: startY
}, {
duration: 550,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
}
};
self.slide = function () {
if (!self.isSliding && !self.isJumping) {
self.isSliding = true;
var originalHeight = playerSprite.height;
tween(playerSprite, {
scaleY: 0.5
}, {
duration: 250,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(playerSprite, {
scaleY: 1
}, {
duration: 250,
onFinish: function onFinish() {
self.isSliding = false;
}
});
}, 600);
}
});
}
};
self.moveLane = function (targetLane) {
if (targetLane >= 0 && targetLane <= 3) {
self.lane = targetLane;
tween(self, {
x: roadPositions[targetLane]
}, {
duration: 100,
easing: tween.easeOut
});
}
};
self.update = function () {
// Player logic updated in game update
};
return self;
});
var PlayerParticle = Container.expand(function () {
var self = Container.call(this);
var particleSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2 + Math.random() * 0.2,
scaleY: 0.2 + Math.random() * 0.2
});
self.vx = 0;
self.vy = 0;
self.gravity = 0.2;
self.friction = 0.98;
self.lifespan = 1000;
self.birthTime = Date.now();
self.update = function () {
// Apply physics
self.vy += self.gravity;
self.vx *= self.friction;
self.vy *= self.friction;
self.x += self.vx;
self.y += self.vy;
// Fade out based on lifespan
var age = Date.now() - self.birthTime;
var lifePercent = 1 - age / self.lifespan;
if (lifePercent <= 0) {
self.destroy();
return;
}
particleSprite.alpha = lifePercent;
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerupSprite = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = powerupSprite.width;
self.height = powerupSprite.height;
self.lane = 1;
self.type = "shield"; // shield, magnet, slowdown
// Pulsate animation
function pulsate() {
tween(powerupSprite, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
onFinish: function onFinish() {
tween(powerupSprite, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
onFinish: pulsate
});
}
});
}
pulsate();
self.setType = function (newType) {
self.type = newType;
if (newType === "shield") {
powerupSprite.tint = 0x00FFFF; // Blue color
// Position shield powerups higher on the screen
self.y -= 400; // Increased from 200 to 400 to position higher
} else if (newType === "slowdown") {
powerupSprite.tint = 0x00FF00;
}
};
self.update = function () {
self.y += gameSpeed;
if (self.y > 2732 + self.height) {
self.destroy();
}
};
return self;
});
var RoadLine = Container.expand(function () {
var self = Container.call(this);
var lineSprite = self.attachAsset('road_line', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = lineSprite.width;
self.height = lineSprite.height;
self.lane = 0; // 0-3 for the four road lanes
self.update = function () {
// Road lines remain stationary
};
self.resetPosition = function () {
// No reset needed as lines are now stationary
};
return self;
});
var Shop = Container.expand(function () {
var self = Container.call(this);
// Create shop background - smaller for bottom left positioning
var shopBg = self.attachAsset('ground', {
anchorX: 0.5,
anchorY: 0.5,
width: 1000,
height: 1200,
tint: 0x222222
});
// Shop title - smaller for mobile
var titleTxt = new Text2('SHOP', {
size: 60,
fill: 0xFFD700
});
titleTxt.anchor.set(0.5, 0);
titleTxt.y = -500;
self.addChild(titleTxt);
// Coin counter at top
var shopCoinTxt = new Text2('Coins: 0', {
size: 40,
fill: 0xFFD700
});
shopCoinTxt.anchor.set(0.5, 0);
shopCoinTxt.y = titleTxt.y + titleTxt.height + 10;
self.addChild(shopCoinTxt);
// Close button - positioned at top right of shop
var closeButton = new Container();
var closeBg = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60,
tint: 0xAA0000
});
closeButton.addChild(closeBg);
var closeTxt = new Text2('X', {
size: 40,
fill: 0xFFFFFF
});
closeTxt.anchor.set(0.5, 0.5);
closeButton.addChild(closeTxt);
closeButton.x = 450;
closeButton.y = -500;
self.addChild(closeButton);
// Create shop items
var items = [];
var itemContainer = new Container();
self.addChild(itemContainer);
self.show = function () {
// Update coin display
shopCoinTxt.setText("Coins: " + coinCount);
// Position shop to appear at bottom left
self.x = 500;
self.y = 2732 - 600;
// Make visible
self.visible = true;
// Initialize items if not already done
if (items.length === 0) {
self.createItems();
}
// Update purchase state of all items
for (var i = 0; i < items.length; i++) {
items[i].updatePurchaseState();
}
};
self.hide = function () {
self.visible = false;
};
self.createItems = function () {
// Clear existing items
itemContainer.removeChildren();
items = [];
// Create shop items with compact grid layout
var item1 = new ShopItem();
item1.setItem("Double Coins", "Doubles all coin collections", 500, 'coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
item1.x = -300;
item1.y = -300;
itemContainer.addChild(item1);
items.push(item1);
var item2 = new ShopItem();
item2.setItem("Extra Life", "Get a second chance when you crash", 1000, 'powerup', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00FFFF
});
item2.x = 0;
item2.y = -300;
itemContainer.addChild(item2);
items.push(item2);
var item3 = new ShopItem();
item3.setItem("Speed Boost", "Start with higher speed", 750, 'bus', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
item3.x = 300;
item3.y = -300;
itemContainer.addChild(item3);
items.push(item3);
var item4 = new ShopItem();
item4.setItem("Golden Player", "Shiny gold player skin", 2000, 'player', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFD700
});
item4.x = -150;
item4.y = 0;
itemContainer.addChild(item4);
items.push(item4);
var item5 = new ShopItem();
item5.setItem("Rainbow Roads", "Colorful road patterns", 1500, 'road', {
anchorX: 0.5,
anchorY: 0.5,
height: 300,
tint: 0xFF00FF
});
item5.x = 150;
item5.y = 0;
itemContainer.addChild(item5);
items.push(item5);
};
// Close button event handler
closeButton.down = function () {
self.hide();
};
// Initialize as hidden
self.visible = false;
return self;
});
var ShopItem = Container.expand(function () {
var self = Container.call(this);
self.itemName = "";
self.itemDescription = "";
self.itemCost = 0;
self.itemAsset = null;
self.purchased = false;
// Text elements - smaller sizes for mobile
var nameTxt = new Text2('', {
size: 36,
fill: 0xFFFFFF
});
nameTxt.anchor.set(0.5, 0);
self.addChild(nameTxt);
var costTxt = new Text2('', {
size: 28,
fill: 0xFFD700 // Gold color
});
costTxt.anchor.set(0.5, 0);
self.addChild(costTxt);
var descriptionTxt = new Text2('', {
size: 22,
fill: 0xCCCCCC
});
descriptionTxt.anchor.set(0.5, 0);
self.addChild(descriptionTxt);
var buyButton = new Container();
var buttonBg = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 60,
tint: 0x00AA00
});
buyButton.addChild(buttonBg);
var buttonTxt = new Text2('BUY', {
size: 30,
fill: 0xFFFFFF
});
buttonTxt.anchor.set(0.5, 0.5);
buyButton.addChild(buttonTxt);
self.addChild(buyButton);
self.setItem = function (name, description, cost, assetId, assetOptions) {
self.itemName = name;
self.itemDescription = description;
self.itemCost = cost;
// Update text elements
nameTxt.setText(name);
costTxt.setText(cost + " coins");
descriptionTxt.setText(description);
// Position text elements - more compact spacing for mobile
nameTxt.y = 0;
costTxt.y = nameTxt.height + 2;
descriptionTxt.y = costTxt.y + costTxt.height + 2;
// Add item asset if provided - make smaller
if (assetId) {
if (self.itemAsset) {
self.itemAsset.destroy();
}
self.itemAsset = self.attachAsset(assetId, {});
self.itemAsset.y = descriptionTxt.y + descriptionTxt.height + 25;
self.itemAsset.x = 0;
if (self.itemAsset.scale) {
self.itemAsset.scale.x = 0.5;
self.itemAsset.scale.y = 0.5;
}
}
// Position buy button - more compact spacing
buyButton.y = self.itemAsset ? self.itemAsset.y + 40 : descriptionTxt.y + descriptionTxt.height + 20;
buyButton.x = 0;
// Check if already purchased
self.updatePurchaseState();
};
self.updatePurchaseState = function () {
// Check storage to see if this item is already purchased
var purchases = storage.purchases || {};
self.purchased = purchases[self.itemName] === true;
if (self.purchased) {
buttonTxt.setText("OWNED");
buttonBg.tint = 0x555555;
} else {
buttonTxt.setText("BUY");
buttonBg.tint = 0x00AA00;
}
};
// Event handlers
buyButton.down = function (x, y, obj) {
if (self.purchased) return;
if (coinCount >= self.itemCost) {
// Purchase successful
coinCount -= self.itemCost;
storage.coinCount = coinCount;
// Record purchase
var purchases = storage.purchases || {};
purchases[self.itemName] = true;
storage.purchases = purchases;
// Update button state
self.purchased = true;
buttonTxt.setText("OWNED");
buttonBg.tint = 0x555555;
// Apply item effect
if (self.itemName === "Double Coins") {
storage.coinMultiplier = 2;
} else if (self.itemName === "Extra Life") {
storage.extraLife = true;
} else if (self.itemName === "Speed Boost") {
storage.speedBoost = true;
}
// Update coin display
updateUI();
shopCoinTxt.setText("Coins: " + coinCount);
} else {
// Not enough coins
tween(costTxt, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(costTxt, {
scaleX: 1,
scaleY: 1,
tint: 0xFFD700
}, {
duration: 200
});
}
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x777777 // Gray background
});
/****
* Game Code
****/
// Game variables
var player;
var obstacles = [];
var coins = [];
var buses = [];
var roadLines = [];
var ground;
var gameSpeed = 5;
var initialGameSpeed = 5;
var maxGameSpeed = 15;
var speedIncreaseInterval;
var gameStarted = false;
var distanceTraveled = 0;
var score = 0;
var coinValue = 10;
var highScore = storage.highScore || 0;
var shop;
var shopButton;
var shopCoinTxt;
// Define the 4 road positions (vertical lanes)
var roadWidth = 2048 / 4;
var roadPositions = [roadWidth * 0.5, roadWidth * 1.5, roadWidth * 2.5, roadWidth * 3.5];
// Lane positions for player, obstacles and collectibles
var laneX = [roadPositions[0], roadPositions[1], roadPositions[2], roadPositions[3]];
// UI elements
var coinCount = storage.coinCount || 0;
var coinTxt = new Text2('COINS: 0', {
size: 60,
fill: 0xFFD700 // Gold color for coins
});
coinTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(coinTxt);
coinTxt.x = -coinTxt.width - 20;
coinTxt.y = 20;
// Leaderboard display
var leaderboardTitle = new Text2('TOP COIN EARNERS', {
size: 50,
fill: 0xFFD700
});
leaderboardTitle.anchor.set(0, 0);
LK.gui.topLeft.addChild(leaderboardTitle);
leaderboardTitle.x = 120; // Leave space for platform menu icon
leaderboardTitle.y = 20;
// Create text elements for each leaderboard entry
var leaderboardEntries = [];
for (var i = 0; i < 3; i++) {
var entryText = new Text2(i + 1 + '. ---: 0', {
size: 40,
fill: 0xFFFFFF
});
entryText.anchor.set(0, 0);
LK.gui.topLeft.addChild(entryText);
entryText.x = 120; // Leave space for platform menu icon
entryText.y = 80 + i * 50;
leaderboardEntries.push(entryText);
}
// Initialize leaderboard
updateLeaderboard();
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 = 90; // Move down to make room for coin counter
var distanceTxt = new Text2('DISTANCE: 0m', {
size: 60,
fill: 0xFFFFFF
});
distanceTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(distanceTxt);
distanceTxt.x = -distanceTxt.width - 20;
distanceTxt.y = 160; // Adjusted to make room for coin and score
var highScoreTxt = new Text2('BEST: ' + highScore, {
size: 50,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(highScoreTxt);
highScoreTxt.x = -highScoreTxt.width - 20;
highScoreTxt.y = 230; // Adjusted to make room for coin and score
var startTxt = new Text2('SWIPE TO START', {
size: 100,
fill: 0xFFFFFF
});
startTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startTxt);
// Game initialization functions
function initializeGame() {
// Create gray ground/background
game.setBackgroundColor(0x777777);
ground = game.addChild(LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 0.5
}));
ground.x = 2048 / 2;
ground.y = 2732 / 2;
// Initialize shop
if (!shop) {
shop = new Shop();
game.addChild(shop);
}
// Create shop button
if (!shopButton) {
shopButton = new Container();
var shopButtonBg = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 150,
tint: 0xFFD700 // Gold color to make it more visible
});
shopButton.addChild(shopButtonBg);
var shopButtonTxt = new Text2('SHOP', {
size: 50,
fill: 0x000000
});
shopButtonTxt.anchor.set(0.5, 0.5);
shopButton.addChild(shopButtonTxt);
// Position at bottom left with some spacing from the edge
shopButton.x = 120;
shopButton.y = 2732 - 120;
game.addChild(shopButton);
// Add event handler to open shop
shopButton.down = function () {
if (!gameStarted) {
shop.show();
}
};
}
// Create 4 vertical roads with no gaps between them
var roadWidth = 2048 / 4; // Divide screen width into 4 equal parts
// Check if Rainbow Roads skin is purchased
var rainbowRoads = storage.purchases && storage.purchases["Rainbow Roads"] === true;
var roadColors = rainbowRoads ? [0xFF0000, 0x00FF00, 0x0000FF, 0xFF00FF] : [0x333333, 0x333333, 0x333333, 0x333333];
for (var i = 0; i < 4; i++) {
var road = game.addChild(LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
width: roadWidth,
// Set width to exactly 1/4 of screen
height: 2732,
// Full screen height
tint: roadColors[i]
}));
road.x = roadWidth * i + roadWidth / 2; // Position roads side by side with no gaps
road.y = 2732 / 2;
}
// Create road lines as vertical marks down the roads
var roadWidth = 2048 / 4; // Width of each road
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 15; j++) {
var roadLine = new RoadLine();
roadLine.lane = i;
roadLine.x = roadWidth * i + roadWidth / 2; // Center in each road
roadLine.y = j * 200; // Distribute lines vertically
roadLines.push(roadLine);
game.addChild(roadLine);
}
}
// Create player
player = game.addChild(new Player());
player.lane = 1; // Start in second lane
player.x = roadPositions[player.lane]; // Position player horizontally on the road
player.y = 2732 - 500; // Position player higher on the screen
// Create regular buses
for (var i = 0; i < 6; i++) {
var bus = new Bus();
bus.lane = Math.floor(Math.random() * 4);
bus.x = roadPositions[bus.lane];
// Position buses with enough space between them vertically
var offset = i * (bus.height + 300);
bus.y = -offset - bus.height;
// Make sure buses in the same lane don't overlap
for (var j = 0; j < buses.length; j++) {
if (bus.lane === buses[j].lane && Math.abs(bus.y - buses[j].y) < bus.height + buses[j].height) {
// Move this bus further up to avoid overlap
bus.y = buses[j].y - (bus.height + 300);
}
}
buses.push(bus);
game.addChild(bus);
}
// Create jumping boards (previously mini buses)
var miniBuses = [];
for (var i = 0; i < 4; i++) {
var miniBus = new MiniBus();
miniBus.lane = Math.floor(Math.random() * 4);
miniBus.x = roadPositions[miniBus.lane];
// Position jumping boards with significantly more space between them
var offset = i * (miniBus.height + 800); // Increased vertical spacing between boards from 400 to 800
miniBus.y = -offset - miniBus.height - 500;
// Make sure jumping boards don't overlap with regular buses
for (var j = 0; j < buses.length; j++) {
if (miniBus.lane === buses[j].lane && Math.abs(miniBus.y - buses[j].y) < miniBus.height + buses[j].height) {
miniBus.y = buses[j].y - (miniBus.height + 600); // Increased separation from 400 to 600
}
}
// Check spacing with other jumping boards that were already created
for (var j = 0; j < miniBuses.length; j++) {
if (miniBus.lane === miniBuses[j].lane && Math.abs(miniBus.y - miniBuses[j].y) < miniBus.height * 6) {
// If too close to another jumping board in the same lane, move it further up
miniBus.y = miniBuses[j].y - miniBus.height * 6;
}
}
miniBuses.push(miniBus);
buses.push(miniBus); // Add to main buses array for updates
game.addChild(miniBus);
}
// Initialize variables
obstacles = []; // Keep empty as we don't use obstacles at all
coins = [];
gameSpeed = initialGameSpeed;
distanceTraveled = 0;
score = 0;
// Don't reset coinCount here to preserve coins between game sessions
updateUI();
// No reset coins button - removed as requested
}
function startGame() {
gameStarted = true;
LK.setScore(0);
// Load persistent coin count from storage
coinCount = storage.coinCount || coinCount;
startTxt.visible = false;
// Hide shop button during gameplay
shopButton.visible = false;
shopButton.alpha = 0; // Ensure it's fully invisible
// Apply Speed Boost powerup if purchased
if (storage.speedBoost === true) {
gameSpeed = initialGameSpeed + 2;
}
// Start increasing game speed over time
speedIncreaseInterval = LK.setInterval(function () {
if (gameSpeed < maxGameSpeed) {
gameSpeed += 0.5;
// Update all buses to have the same speed when game speed changes
for (var i = 0; i < buses.length; i++) {
buses[i].speed = 1; // Keep all buses at same speed when the game speeds up
}
}
}, 10000);
// Start spawning only coins
spawnCoins();
// Play background music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
// Enable all game sounds (preload them by playing at volume 0 then stopping)
LK.getSound('coin_collect').play({
volume: 0
});
LK.getSound('crash').play({
volume: 0
});
LK.getSound('jump').play({
volume: 0
});
LK.getSound('powerup_collect').play({
volume: 0
});
LK.setTimeout(function () {
LK.getSound('coin_collect').stop();
LK.getSound('crash').stop();
LK.getSound('jump').stop();
LK.getSound('powerup_collect').stop();
}, 100);
}
function updateUI() {
coinTxt.setText('COINS: ' + coinCount);
coinTxt.x = -coinTxt.width - 20;
scoreTxt.setText('SCORE: ' + score);
scoreTxt.x = -scoreTxt.width - 20;
var distance = Math.floor(distanceTraveled / 10);
distanceTxt.setText('DISTANCE: ' + distance + 'm');
distanceTxt.x = -distanceTxt.width - 20;
highScoreTxt.setText('BEST: ' + highScore);
highScoreTxt.x = -highScoreTxt.width - 20;
// Save coin count to storage when UI is updated to persist between sessions
storage.coinCount = coinCount;
}
function updateLeaderboard() {
// Get leaderboard from storage or create empty array if none exists
var leaderboard = storage.leaderboard || [];
// Update entries in UI
for (var i = 0; i < 3; i++) {
if (i < leaderboard.length) {
var entry = leaderboard[i];
var parts = entry.split(":");
var playerName = parts[0];
var score = parts[1];
leaderboardEntries[i].setText(i + 1 + '. ' + playerName + ': ' + score);
} else {
leaderboardEntries[i].setText(i + 1 + '. ---: 0');
}
}
}
function addToLeaderboard(newScore) {
// Get leaderboard from storage or create empty array if none exists
var leaderboard = storage.leaderboard || [];
// Generate a player name (could be replaced with actual player names in a real game)
var playerName = "Player" + Math.floor(Math.random() * 100);
// Add new entry with coinCount instead of score
leaderboard.push(playerName + ":" + coinCount);
// Sort leaderboard by coins (descending) - parse coins from string format
leaderboard.sort(function (a, b) {
var coinsA = parseInt(a.split(":")[1]);
var coinsB = parseInt(b.split(":")[1]);
return coinsB - coinsA;
});
// Keep only top entries
if (leaderboard.length > 10) {
leaderboard = leaderboard.slice(0, 10);
}
// Save back to storage
storage.leaderboard = leaderboard;
// Update UI
updateLeaderboard();
}
// Object spawning functions
function spawnObstacles() {
// Function is empty because we don't want to spawn any obstacles
// Only jumping boards (MiniBus objects) will be used as obstacles
}
function spawnCoins() {
var coinTimer = LK.setInterval(function () {
if (!gameStarted) return;
// Random coin pattern
var pattern = Math.floor(Math.random() * 4);
if (pattern === 0) {
// Single coin
var lane = Math.floor(Math.random() * 4);
spawnCoin(lane);
} else if (pattern === 1) {
// Vertical line of coins
var lane = Math.floor(Math.random() * 4);
for (var i = 0; i < 3; i++) {
var coin = new Coin();
coin.lane = lane;
coin.x = roadPositions[lane];
coin.y = -coin.height - i * 120;
coins.push(coin);
game.addChild(coin);
}
} else if (pattern === 2) {
// Horizontal line of coins
for (var i = 0; i < 4; i++) {
spawnCoin(i);
}
} else {
// Diagonal line of coins
for (var i = 0; i < 4; i++) {
var coin = new Coin();
coin.lane = i;
coin.x = roadPositions[i];
coin.y = -coin.height - i * 120;
coins.push(coin);
game.addChild(coin);
}
}
}, 3000);
}
function spawnCoin(lane) {
var coin = new Coin();
coin.lane = lane;
coin.x = roadPositions[lane];
coin.y = -coin.height;
// Check if there are buses in this lane near spawn point with increased range
var busInLane = false;
for (var i = 0; i < buses.length; i++) {
if (buses[i].lane === lane && buses[i].y < 0 && buses[i].y > -500) {
// Increased range check
busInLane = true;
break;
}
}
// Only spawn coins if there's no bus in this lane
if (!busInLane) {
// Increased chance to spawn additional coins if in lane 1 (correct lane)
if (lane === 1 && Math.random() < 0.9) {
// Increased probability from 0.7 to 0.9
// Add extra coins in a vertical line in the correct lane
for (var i = 1; i < 6; i++) {
// Check for buses before spawning each extra coin
var extraCoinY = -coin.height - i * 100;
var extraCoinBusCollision = false;
for (var j = 0; j < buses.length; j++) {
if (buses[j].lane === lane && Math.abs(buses[j].y - extraCoinY) < buses[j].height / 2 + coin.height / 2) {
extraCoinBusCollision = true;
break;
}
}
if (!extraCoinBusCollision) {
var extraCoin = new Coin();
extraCoin.lane = lane;
extraCoin.x = roadPositions[lane];
extraCoin.y = extraCoinY; // Reduced spacing for more coins
coins.push(extraCoin);
game.addChild(extraCoin);
}
}
}
coins.push(coin);
game.addChild(coin);
}
}
// Function removed as powerups are no longer used
// Function to reset all coins in the game (only visual coins, not the count)
function resetCoins() {
// Remove all existing coins on screen
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].destroy();
}
coins = [];
// Ensure we're keeping the coin count persistent
storage.coinCount = coinCount;
updateUI();
}
// Game mechanics functions
function checkCollisions() {
// No obstacle collisions to check since we're not spawning obstacles anymore
// Only jumping boards (MiniBus objects) are used as obstacles
// Check bus collisions
for (var i = buses.length - 1; i >= 0; i--) {
var bus = buses[i];
// Check for bus-to-bus collisions
for (var j = 0; j < buses.length; j++) {
if (i !== j && bus.lane === buses[j].lane && Math.abs(bus.y - buses[j].y) < (bus.height + buses[j].height) * 0.8) {
// Prevent overlap by repositioning this bus
bus.y = buses[j].y - (bus.height + buses[j].height);
if (bus.y > 2732 + bus.height) {
bus.resetPosition();
}
}
}
// Check for bus-to-obstacle collisions
for (var j = 0; j < obstacles.length; j++) {
if (bus.lane === obstacles[j].lane && Math.abs(bus.y - obstacles[j].y) < (bus.height + obstacles[j].height) * 0.8) {
// Prevent overlap by repositioning this bus
bus.y = obstacles[j].y - (bus.height + obstacles[j].height);
if (bus.y > 2732 + bus.height) {
bus.resetPosition();
}
}
}
// Ensure at least one free lane is available at all times
// First count how many lanes have buses in the player's vicinity
var busesNearPlayer = [0, 0, 0, 0]; // Count of buses in each lane
for (var j = 0; j < buses.length; j++) {
var nearBus = buses[j];
// Only count buses that are close to the player vertically
if (Math.abs(nearBus.y - player.y) < 700) {
// Increased detection range
busesNearPlayer[nearBus.lane]++;
}
}
// If all lanes have buses near the player, move one bus to ensure a free lane
var totalBlockedLanes = (busesNearPlayer[0] > 0 ? 1 : 0) + (busesNearPlayer[1] > 0 ? 1 : 0) + (busesNearPlayer[2] > 0 ? 1 : 0) + (busesNearPlayer[3] > 0 ? 1 : 0);
if (totalBlockedLanes >= 3) {
// More aggressive - clear if 3+ lanes are blocked
// Find a lane that's different from player's current lane to free up
var laneToFree = (player.lane + 2) % 4; // Pick lane opposite to player
// Make sure this lane is completely clear by moving ALL buses in this lane
for (var j = 0; j < buses.length; j++) {
if (buses[j].lane === laneToFree && Math.abs(buses[j].y - player.y) < 1000) {
// Increased range
buses[j].resetPosition(); // Move this bus away
// Add additional space to ensure safety
buses[j].y -= Math.random() * 800;
}
}
}
// Check player collision with bus
if (player.lane === bus.lane && Math.abs(player.y - bus.y) < (player.height + bus.height) / 2) {
// Check if it's a jumping board that can be jumped over
if (bus instanceof MiniBus && player.isJumping) {
continue; // Player successfully jumped over the jumping board
}
// Check if player can jump over the jumping board
if (bus instanceof MiniBus) {
// Track jumping boards approaching player
if (bus.y < player.y && bus.y > player.y - 400) {
// No highlighting needed
}
}
// Flash warning when player is about to collide with a bus
if (!player.warningShown && bus.y < player.y && bus.y > player.y - 300) {
player.warningShown = true;
// Flash the player to warn of incoming danger
var flashCount = 0;
var warningInterval = LK.setInterval(function () {
player.alpha = player.alpha === 1 ? 0.5 : 1;
flashCount++;
if (flashCount >= 6) {
LK.clearInterval(warningInterval);
player.alpha = 1;
// Reset warning after a delay
LK.setTimeout(function () {
player.warningShown = false;
}, 1000);
}
}, 100);
}
// Collision occurred
createPlayerParticles();
gameOver();
break;
}
}
// Check coin collections
for (var i = coins.length - 1; i >= 0; i--) {
var coin = coins[i];
// Track the last position of the coin relative to player
if (coin.lastRelativeY === undefined) {
coin.lastRelativeY = coin.y - player.y;
}
var currentRelativeY = coin.y - player.y;
// Regular collection - Only collect when coin passes directly over player
if (player.lane === coin.lane && coin.lastRelativeY < 0 && currentRelativeY >= 0 && Math.abs(player.x - coin.x) < (player.width + coin.width) / 2) {
// Coin just passed over the player (crossed from above to below)
collectCoin(coin, i);
}
// Update last relative position
coin.lastRelativeY = currentRelativeY;
}
// Powerup collection logic removed
}
function collectCoin(coin, index) {
LK.getSound('coin_collect').play();
// Apply coin multiplier if purchased
var multiplier = storage.coinMultiplier || 1;
score += coinValue * multiplier;
coinCount += multiplier;
LK.setScore(score);
// Update shop coin counter if shop is visible
if (shop && shop.visible) {
shopCoinTxt.setText("Coins: " + coinCount);
}
// Get the global position of the coin
var coinPos = {
x: coin.x,
y: coin.y
};
// Get the global position of the coin counter
var counterPos = {
x: LK.gui.topRight.x - coinTxt.width / 2,
y: LK.gui.topRight.y + coinTxt.height / 2
};
// Adjust to make sure coins fly exactly to the counter
counterPos.x -= 200;
counterPos.y += 20;
// Create flying coin animation with rotation
var coinParticle = new CoinParticle();
game.addChild(coinParticle);
coinParticle.init(coinPos.x, coinPos.y, counterPos.x, counterPos.y);
// Make sure the coin exists before trying to remove it
if (index >= 0 && index < coins.length && coins[index]) {
coins.splice(index, 1);
coin.destroy();
} else if (coin) {
// If index is invalid but we have a coin reference, just destroy it
// and remove it from the coins array if found
coin.destroy();
var coinIndex = coins.indexOf(coin);
if (coinIndex >= 0) {
coins.splice(coinIndex, 1);
}
}
updateUI();
}
// Function removed as powerups are no longer used
// Function removed as powerups are no longer used
function createPlayerParticles() {
// Create particle explosion effect at player position
var particleCount = 20;
var particles = [];
for (var i = 0; i < particleCount; i++) {
var particle = new PlayerParticle();
game.addChild(particle);
particle.x = player.x;
particle.y = player.y;
// Random direction
var angle = Math.random() * Math.PI * 2;
var speed = 2 + Math.random() * 5;
particle.vx = Math.cos(angle) * speed;
particle.vy = Math.sin(angle) * speed;
// Random rotation and scale for variety
particle.rotation = Math.random() * Math.PI * 2;
// Set random lifespan
particle.lifespan = 800 + Math.random() * 1200;
particle.birthTime = Date.now();
// Add to particles array for tracking
particles.push(particle);
}
// Hide the original player
player.visible = false;
}
function gameOver() {
// Check if player has Extra Life powerup
if (storage.extraLife === true) {
// Use the extra life
storage.extraLife = false;
// Make player flash to show immunity
player.alpha = 0.5;
// Keep player in current lane
var currentLane = player.lane;
// Create temporary immunity
var immuneTimer = LK.setInterval(function () {
player.alpha = player.alpha === 0.5 ? 1 : 0.5;
}, 100);
// End immunity after 2 seconds
LK.setTimeout(function () {
LK.clearInterval(immuneTimer);
player.alpha = 1;
}, 2000);
return; // Don't end the game
}
LK.getSound('crash').play();
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
// Save final coin count to ensure it persists after game over
storage.coinCount = coinCount;
// Update leaderboard with current coins instead of score
addToLeaderboard(coinCount);
// Clear intervals
LK.clearInterval(speedIncreaseInterval);
// Make player visible again if it was hidden
player.visible = true;
// Create a character growth animation before game over
// Start growing from the player's current position
var currentX = player.x;
var currentY = player.y;
// Make shop button visible again after game over
shopButton.visible = true;
// Use tween to grow player's color to fill the screen
tween(player, {
scaleX: 25,
// Make even larger to ensure full screen coverage
scaleY: 25,
// Make even larger to ensure full screen coverage
alpha: 0.95,
// More visible
rotation: Math.PI * 2 // Full rotation for effect
}, {
duration: 2000,
// Longer duration for more dramatic effect
easing: tween.elasticOut,
onFinish: function onFinish() {
// Now show game over after animation completes
LK.showGameOver();
// Ensure shop button is prominently visible
shopButton.tint = 0xFFFFFF; // Reset tint
shopButton.alpha = 1; // Full opacity
}
});
}
// Helper function to find the safest lane
function findSafeLane() {
// Count objects in each lane near the player
var laneDanger = [0, 0, 0, 0];
// Check buses in each lane with a wider vertical range
for (var i = 0; i < buses.length; i++) {
if (Math.abs(buses[i].y - player.y) < 600) {
// Increased detection range
laneDanger[buses[i].lane] += 2;
}
}
// Find the lane with minimum danger
var minDanger = 999;
var safestLane = player.lane;
for (var i = 0; i < 4; i++) {
if (laneDanger[i] < minDanger) {
minDanger = laneDanger[i];
safestLane = i;
}
}
// ALWAYS ensure at least one lane is completely safe
// If all lanes have danger, ensure at least one lane is safe
var allLanesHaveDanger = true;
for (var i = 0; i < 4; i++) {
if (laneDanger[i] === 0) {
allLanesHaveDanger = false;
safestLane = i; // Immediately use first safe lane found
break;
}
}
// If all lanes have danger, force clear out a lane
if (allLanesHaveDanger) {
// Choose a lane different from player's current lane to free up
var laneToFree = (player.lane + 2) % 4;
// Clear ALL buses from this lane that are near the player
for (var i = 0; i < buses.length; i++) {
if (buses[i].lane === laneToFree && Math.abs(buses[i].y - player.y) < 800) {
// Move bus way above the screen to ensure it's far away
buses[i].y = -buses[i].height - Math.random() * 3000; // Increased distance
}
}
// Set this as safest lane
safestLane = laneToFree;
// Add visual indicator for the safe lane (removed as requested)
}
return safestLane;
}
// Touch/swipe controls
var startY, startX;
var MIN_SWIPE_DISTANCE = 30;
game.down = function (x, y) {
startX = x;
startY = y;
};
game.up = function (x, y) {
if (!startX || !startY) return;
var deltaX = x - startX;
var deltaY = y - startY;
var absDeltaX = Math.abs(deltaX);
var absDeltaY = Math.abs(deltaY);
// Start game on any swipe if not started
if (!gameStarted) {
startGame();
return;
}
// Lower the minimum swipe distance to make it more sensitive
var MIN_SWIPE_DISTANCE = 30;
// Detect swipe direction
if (absDeltaX > MIN_SWIPE_DISTANCE || absDeltaY > MIN_SWIPE_DISTANCE) {
// Horizontal swipe detection (left/right) - always move only to adjacent lane
if (absDeltaX > absDeltaY) {
if (deltaX > 0) {
// Swipe right - move to adjacent right lane if possible
var newLane = Math.min(player.lane + 1, 3);
// Check if killTweensOf exists before calling it
if (tween.killTweensOf) {
tween.killTweensOf(player); // Kill any existing movement tweens
}
// Shorter duration for faster response
tween(player, {
x: roadPositions[newLane]
}, {
duration: 100,
// Faster movement
easing: tween.easeOut
});
player.lane = newLane; // Update player lane
} else {
// Swipe left - move to adjacent left lane if possible
var newLane = Math.max(player.lane - 1, 0);
// Check if killTweensOf exists before calling it
if (tween.killTweensOf) {
tween.killTweensOf(player);
}
// Shorter duration for faster response
tween(player, {
x: roadPositions[newLane]
}, {
duration: 100,
// Faster movement
easing: tween.easeOut
});
player.lane = newLane; // Update player lane
}
}
// Vertical swipe detection (up/down)
else {
if (deltaY < 0) {
// Swipe up (jump)
player.jump();
} else {
// Swipe down (slide)
player.slide();
}
}
}
};
// Initialize the game
initializeGame();
// Game update loop
game.update = function () {
if (!gameStarted) {
// Don't do any movement when game hasn't started
return;
}
// Update distance traveled
distanceTraveled += gameSpeed;
updateUI();
// Check for collisions
checkCollisions();
// No obstacles to update since we don't spawn them anymore
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i]) coins[i].update();
}
// Powerups removed from game
// Ensure jumping boards (MiniBus objects) aren't too close to each other
var jumpingBoards = buses.filter(function (bus) {
return bus instanceof MiniBus;
});
// Check each jumping board against others in the same lane
for (var i = 0; i < jumpingBoards.length; i++) {
for (var j = i + 1; j < jumpingBoards.length; j++) {
if (jumpingBoards[i].lane === jumpingBoards[j].lane) {
var distance = Math.abs(jumpingBoards[i].y - jumpingBoards[j].y);
// If boards are too close, reposition the one that's higher up
if (distance < jumpingBoards[i].height * 10) {
// Increased from 5x to 10x minimum spacing
var higherBoard = jumpingBoards[i].y < jumpingBoards[j].y ? jumpingBoards[i] : jumpingBoards[j];
// Reset position of the higher board to ensure adequate spacing
higherBoard.resetPosition();
// Move it even further up to ensure separation
higherBoard.y -= higherBoard.height * 10 + Math.random() * 400; // Increased spacing
}
}
}
}
for (var i = buses.length - 1; i >= 0; i--) {
if (buses[i]) buses[i].update();
}
// Road lines are now stationary
// Update all PlayerParticle instances that might be in the scene
var particles = game.children.filter(function (child) {
return child instanceof PlayerParticle;
});
for (var i = 0; i < particles.length; i++) {
particles[i].update();
}
// Magnet functionality removed
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
coinCount: 0,
leaderboard: []
});
/****
* Classes
****/
var Bus = Container.expand(function () {
var self = Container.call(this);
var busSprite = self.attachAsset('bus', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = busSprite.width;
self.height = busSprite.height;
self.speed = 1; // All buses have the same base speed
self.lane = 0; // 0-3 for the four road lanes
self.update = function () {
self.lastX = self.x;
// Move vertically from top to bottom
self.y += gameSpeed * self.speed;
if (self.y > 2732 + self.height) {
self.resetPosition();
}
};
self.resetPosition = function () {
// Reset to top of screen
self.y = -self.height - Math.random() * 300;
// Randomly assign to a lane
self.lane = Math.floor(Math.random() * 4);
self.x = roadPositions[self.lane];
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinSprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = coinSprite.width;
self.height = coinSprite.height;
self.lane = 1;
// Coin animation (rotate)
LK.setInterval(function () {
tween(coinSprite, {
rotation: coinSprite.rotation + Math.PI * 2
}, {
duration: 1000
});
}, 1000);
self.update = function () {
self.y += gameSpeed;
if (self.y > 2732 + self.height) {
self.destroy();
}
};
return self;
});
var CoinParticle = Container.expand(function () {
var self = Container.call(this);
var coinSprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.startX = 0;
self.startY = 0;
self.targetX = 0;
self.targetY = 0;
self.init = function (startX, startY, targetX, targetY) {
self.x = startX;
self.y = startY;
self.startX = startX;
self.startY = startY;
self.targetX = targetX;
self.targetY = targetY;
// Add rotation to make coin spin while flying
var rotateInterval = LK.setInterval(function () {
coinSprite.rotation += 0.2;
}, 16);
// Create coin flight path animation with arc
var controlPointX = startX + (targetX - startX) * 0.5;
var controlPointY = startY - 200; // Arc upward
var steps = 20; // Number of steps for the arc
var stepDuration = 800 / steps; // Duration for each step
// Animate along bezier curve
function animateStep(i) {
if (i >= steps) {
// Animation complete
LK.clearInterval(rotateInterval);
// Flash the coin counter when coin arrives
tween(coinTxt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(coinTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
self.destroy();
return;
}
var t = i / steps;
// Quadratic bezier curve calculation
var newX = (1 - t) * (1 - t) * startX + 2 * (1 - t) * t * controlPointX + t * t * targetX;
var newY = (1 - t) * (1 - t) * startY + 2 * (1 - t) * t * controlPointY + t * t * targetY;
tween(self, {
x: newX,
y: newY,
scaleX: 0.8 - 0.3 * t,
scaleY: 0.8 - 0.3 * t
}, {
duration: stepDuration,
onFinish: function onFinish() {
animateStep(i + 1);
}
});
}
// Start the animation
animateStep(0);
};
return self;
});
var Magnet = Container.expand(function () {
var self = Container.call(this);
var magnetSprite = self.attachAsset('miknatis', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Simplified Magnet class for visual reference only
return self;
});
var MiniBus = Container.expand(function () {
var self = Container.call(this);
// Replace bus sprite with jumpboard
var busSprite = self.attachAsset('jumpboard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1
});
self.width = busSprite.width * 1.5;
self.height = busSprite.height;
self.speed = 1.2; // Slightly faster than regular buses
self.lane = 0; // 0-3 for the four road lanes
// No need for green tint as we're using the jumping board asset now
self.update = function () {
self.lastX = self.x;
// Move vertically from top to bottom
self.y += gameSpeed * self.speed;
if (self.y > 2732 + self.height) {
self.resetPosition();
}
};
self.resetPosition = function () {
// Reset to top of screen with more randomization
self.y = -self.height - Math.random() * 1500; // Further increased random variance
// Randomly assign to a lane
self.lane = Math.floor(Math.random() * 4);
self.x = roadPositions[self.lane];
// Check for other jumping boards in this lane and ensure spacing
var jumpingBoardsInLane = [];
for (var i = 0; i < buses.length; i++) {
if (buses[i] instanceof MiniBus && buses[i] !== self && buses[i].lane === self.lane) {
jumpingBoardsInLane.push(buses[i]);
}
}
// Ensure minimum spacing with other jumping boards in the same lane - increased spacing requirement
for (var i = 0; i < jumpingBoardsInLane.length; i++) {
var otherBoard = jumpingBoardsInLane[i];
if (otherBoard.y < 0 && Math.abs(self.y - otherBoard.y) < self.height * 20) {
// Greatly increased minimum distance
// If too close to another jumping board, move much further up
self.y = otherBoard.y - (self.height * 20 + Math.random() * 800); // Increased spacing
}
}
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
// Create container for obstacle parts
var obstacleContainer = new Container();
self.addChild(obstacleContainer);
// Create red obstacle (full obstacle is dangerous)
var redPart = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff0000,
// Make obstacle red
scaleY: 1.0
});
self.redPart = redPart;
self.width = redPart.width;
self.height = redPart.height;
self.lane = 1;
self.type = "normal"; // normal, low, high
self.canSlideUnder = true; // All obstacles can be slid under
self.setType = function (newType) {
self.type = newType;
// Keep consistent size regardless of type
if (newType === "low") {
// Just adjust position, not size
self.y += self.height * 0.2;
} else if (newType === "high") {
// Just adjust position, not size
self.y -= self.height * 0.2;
}
};
// Check if player is colliding with red part
self.isCollidingWithRedPart = function (player) {
var redPartBounds = {
top: self.y - redPart.height * 0.5,
bottom: self.y + redPart.height * 0.5,
left: self.x - redPart.width * 0.5,
right: self.x + redPart.width * 0.5
};
var playerBounds = {
top: player.y - player.height * 0.5,
bottom: player.y + player.height * 0.5,
left: player.x - player.width * 0.5,
right: player.x + player.width * 0.5
};
return !(redPartBounds.right < playerBounds.left || redPartBounds.left > playerBounds.right || redPartBounds.bottom < playerBounds.top || redPartBounds.top > playerBounds.bottom);
};
self.update = function () {
self.y += gameSpeed;
if (self.y > 2732 + self.height) {
self.destroy();
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = playerSprite.width;
self.height = playerSprite.height;
self.lane = 1; // 0=left, 1=center, 2=right
self.isJumping = false;
self.isSliding = false;
// Apply golden skin if purchased
if (storage.purchases && storage.purchases["Golden Player"] === true) {
playerSprite.tint = 0xFFD700; // Gold color
}
// No lifesaver/shield functionality
// Add activateShield method
self.activateShield = function () {
// Create shield effect around player
var shieldActive = true;
// Visual indicator for shield
var shieldEffect = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00FFFF,
scaleX: 2,
scaleY: 2,
alpha: 0.5
});
self.addChild(shieldEffect);
// Create pulsing animation for shield
function pulseShield() {
tween(shieldEffect, {
scaleX: 2.3,
scaleY: 2.3,
alpha: 0.3
}, {
duration: 500,
onFinish: function onFinish() {
tween(shieldEffect, {
scaleX: 2,
scaleY: 2,
alpha: 0.5
}, {
duration: 500,
onFinish: pulseShield
});
}
});
}
pulseShield();
// Shield lasts for 10 seconds
LK.setTimeout(function () {
shieldActive = false;
// Fade out shield effect
tween(shieldEffect, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
shieldEffect.destroy();
}
});
}, 10000);
};
self.jump = function () {
if (!self.isJumping) {
self.isJumping = true;
var startY = self.y;
LK.getSound('jump').play();
tween(self, {
y: startY - 300
}, {
duration: 550,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: startY
}, {
duration: 550,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
}
};
self.slide = function () {
if (!self.isSliding && !self.isJumping) {
self.isSliding = true;
var originalHeight = playerSprite.height;
tween(playerSprite, {
scaleY: 0.5
}, {
duration: 250,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(playerSprite, {
scaleY: 1
}, {
duration: 250,
onFinish: function onFinish() {
self.isSliding = false;
}
});
}, 600);
}
});
}
};
self.moveLane = function (targetLane) {
if (targetLane >= 0 && targetLane <= 3) {
self.lane = targetLane;
tween(self, {
x: roadPositions[targetLane]
}, {
duration: 100,
easing: tween.easeOut
});
}
};
self.update = function () {
// Player logic updated in game update
};
return self;
});
var PlayerParticle = Container.expand(function () {
var self = Container.call(this);
var particleSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2 + Math.random() * 0.2,
scaleY: 0.2 + Math.random() * 0.2
});
self.vx = 0;
self.vy = 0;
self.gravity = 0.2;
self.friction = 0.98;
self.lifespan = 1000;
self.birthTime = Date.now();
self.update = function () {
// Apply physics
self.vy += self.gravity;
self.vx *= self.friction;
self.vy *= self.friction;
self.x += self.vx;
self.y += self.vy;
// Fade out based on lifespan
var age = Date.now() - self.birthTime;
var lifePercent = 1 - age / self.lifespan;
if (lifePercent <= 0) {
self.destroy();
return;
}
particleSprite.alpha = lifePercent;
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerupSprite = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = powerupSprite.width;
self.height = powerupSprite.height;
self.lane = 1;
self.type = "shield"; // shield, magnet, slowdown
// Pulsate animation
function pulsate() {
tween(powerupSprite, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
onFinish: function onFinish() {
tween(powerupSprite, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
onFinish: pulsate
});
}
});
}
pulsate();
self.setType = function (newType) {
self.type = newType;
if (newType === "shield") {
powerupSprite.tint = 0x00FFFF; // Blue color
// Position shield powerups higher on the screen
self.y -= 400; // Increased from 200 to 400 to position higher
} else if (newType === "slowdown") {
powerupSprite.tint = 0x00FF00;
}
};
self.update = function () {
self.y += gameSpeed;
if (self.y > 2732 + self.height) {
self.destroy();
}
};
return self;
});
var RoadLine = Container.expand(function () {
var self = Container.call(this);
var lineSprite = self.attachAsset('road_line', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = lineSprite.width;
self.height = lineSprite.height;
self.lane = 0; // 0-3 for the four road lanes
self.update = function () {
// Road lines remain stationary
};
self.resetPosition = function () {
// No reset needed as lines are now stationary
};
return self;
});
var Shop = Container.expand(function () {
var self = Container.call(this);
// Create shop background - smaller for bottom left positioning
var shopBg = self.attachAsset('ground', {
anchorX: 0.5,
anchorY: 0.5,
width: 1000,
height: 1200,
tint: 0x222222
});
// Shop title - smaller for mobile
var titleTxt = new Text2('SHOP', {
size: 60,
fill: 0xFFD700
});
titleTxt.anchor.set(0.5, 0);
titleTxt.y = -500;
self.addChild(titleTxt);
// Coin counter at top
var shopCoinTxt = new Text2('Coins: 0', {
size: 40,
fill: 0xFFD700
});
shopCoinTxt.anchor.set(0.5, 0);
shopCoinTxt.y = titleTxt.y + titleTxt.height + 10;
self.addChild(shopCoinTxt);
// Close button - positioned at top right of shop
var closeButton = new Container();
var closeBg = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60,
tint: 0xAA0000
});
closeButton.addChild(closeBg);
var closeTxt = new Text2('X', {
size: 40,
fill: 0xFFFFFF
});
closeTxt.anchor.set(0.5, 0.5);
closeButton.addChild(closeTxt);
closeButton.x = 450;
closeButton.y = -500;
self.addChild(closeButton);
// Create shop items
var items = [];
var itemContainer = new Container();
self.addChild(itemContainer);
self.show = function () {
// Update coin display
shopCoinTxt.setText("Coins: " + coinCount);
// Position shop to appear at bottom left
self.x = 500;
self.y = 2732 - 600;
// Make visible
self.visible = true;
// Initialize items if not already done
if (items.length === 0) {
self.createItems();
}
// Update purchase state of all items
for (var i = 0; i < items.length; i++) {
items[i].updatePurchaseState();
}
};
self.hide = function () {
self.visible = false;
};
self.createItems = function () {
// Clear existing items
itemContainer.removeChildren();
items = [];
// Create shop items with compact grid layout
var item1 = new ShopItem();
item1.setItem("Double Coins", "Doubles all coin collections", 500, 'coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
item1.x = -300;
item1.y = -300;
itemContainer.addChild(item1);
items.push(item1);
var item2 = new ShopItem();
item2.setItem("Extra Life", "Get a second chance when you crash", 1000, 'powerup', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00FFFF
});
item2.x = 0;
item2.y = -300;
itemContainer.addChild(item2);
items.push(item2);
var item3 = new ShopItem();
item3.setItem("Speed Boost", "Start with higher speed", 750, 'bus', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
item3.x = 300;
item3.y = -300;
itemContainer.addChild(item3);
items.push(item3);
var item4 = new ShopItem();
item4.setItem("Golden Player", "Shiny gold player skin", 2000, 'player', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFD700
});
item4.x = -150;
item4.y = 0;
itemContainer.addChild(item4);
items.push(item4);
var item5 = new ShopItem();
item5.setItem("Rainbow Roads", "Colorful road patterns", 1500, 'road', {
anchorX: 0.5,
anchorY: 0.5,
height: 300,
tint: 0xFF00FF
});
item5.x = 150;
item5.y = 0;
itemContainer.addChild(item5);
items.push(item5);
};
// Close button event handler
closeButton.down = function () {
self.hide();
};
// Initialize as hidden
self.visible = false;
return self;
});
var ShopItem = Container.expand(function () {
var self = Container.call(this);
self.itemName = "";
self.itemDescription = "";
self.itemCost = 0;
self.itemAsset = null;
self.purchased = false;
// Text elements - smaller sizes for mobile
var nameTxt = new Text2('', {
size: 36,
fill: 0xFFFFFF
});
nameTxt.anchor.set(0.5, 0);
self.addChild(nameTxt);
var costTxt = new Text2('', {
size: 28,
fill: 0xFFD700 // Gold color
});
costTxt.anchor.set(0.5, 0);
self.addChild(costTxt);
var descriptionTxt = new Text2('', {
size: 22,
fill: 0xCCCCCC
});
descriptionTxt.anchor.set(0.5, 0);
self.addChild(descriptionTxt);
var buyButton = new Container();
var buttonBg = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 60,
tint: 0x00AA00
});
buyButton.addChild(buttonBg);
var buttonTxt = new Text2('BUY', {
size: 30,
fill: 0xFFFFFF
});
buttonTxt.anchor.set(0.5, 0.5);
buyButton.addChild(buttonTxt);
self.addChild(buyButton);
self.setItem = function (name, description, cost, assetId, assetOptions) {
self.itemName = name;
self.itemDescription = description;
self.itemCost = cost;
// Update text elements
nameTxt.setText(name);
costTxt.setText(cost + " coins");
descriptionTxt.setText(description);
// Position text elements - more compact spacing for mobile
nameTxt.y = 0;
costTxt.y = nameTxt.height + 2;
descriptionTxt.y = costTxt.y + costTxt.height + 2;
// Add item asset if provided - make smaller
if (assetId) {
if (self.itemAsset) {
self.itemAsset.destroy();
}
self.itemAsset = self.attachAsset(assetId, {});
self.itemAsset.y = descriptionTxt.y + descriptionTxt.height + 25;
self.itemAsset.x = 0;
if (self.itemAsset.scale) {
self.itemAsset.scale.x = 0.5;
self.itemAsset.scale.y = 0.5;
}
}
// Position buy button - more compact spacing
buyButton.y = self.itemAsset ? self.itemAsset.y + 40 : descriptionTxt.y + descriptionTxt.height + 20;
buyButton.x = 0;
// Check if already purchased
self.updatePurchaseState();
};
self.updatePurchaseState = function () {
// Check storage to see if this item is already purchased
var purchases = storage.purchases || {};
self.purchased = purchases[self.itemName] === true;
if (self.purchased) {
buttonTxt.setText("OWNED");
buttonBg.tint = 0x555555;
} else {
buttonTxt.setText("BUY");
buttonBg.tint = 0x00AA00;
}
};
// Event handlers
buyButton.down = function (x, y, obj) {
if (self.purchased) return;
if (coinCount >= self.itemCost) {
// Purchase successful
coinCount -= self.itemCost;
storage.coinCount = coinCount;
// Record purchase
var purchases = storage.purchases || {};
purchases[self.itemName] = true;
storage.purchases = purchases;
// Update button state
self.purchased = true;
buttonTxt.setText("OWNED");
buttonBg.tint = 0x555555;
// Apply item effect
if (self.itemName === "Double Coins") {
storage.coinMultiplier = 2;
} else if (self.itemName === "Extra Life") {
storage.extraLife = true;
} else if (self.itemName === "Speed Boost") {
storage.speedBoost = true;
}
// Update coin display
updateUI();
shopCoinTxt.setText("Coins: " + coinCount);
} else {
// Not enough coins
tween(costTxt, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(costTxt, {
scaleX: 1,
scaleY: 1,
tint: 0xFFD700
}, {
duration: 200
});
}
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x777777 // Gray background
});
/****
* Game Code
****/
// Game variables
var player;
var obstacles = [];
var coins = [];
var buses = [];
var roadLines = [];
var ground;
var gameSpeed = 5;
var initialGameSpeed = 5;
var maxGameSpeed = 15;
var speedIncreaseInterval;
var gameStarted = false;
var distanceTraveled = 0;
var score = 0;
var coinValue = 10;
var highScore = storage.highScore || 0;
var shop;
var shopButton;
var shopCoinTxt;
// Define the 4 road positions (vertical lanes)
var roadWidth = 2048 / 4;
var roadPositions = [roadWidth * 0.5, roadWidth * 1.5, roadWidth * 2.5, roadWidth * 3.5];
// Lane positions for player, obstacles and collectibles
var laneX = [roadPositions[0], roadPositions[1], roadPositions[2], roadPositions[3]];
// UI elements
var coinCount = storage.coinCount || 0;
var coinTxt = new Text2('COINS: 0', {
size: 60,
fill: 0xFFD700 // Gold color for coins
});
coinTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(coinTxt);
coinTxt.x = -coinTxt.width - 20;
coinTxt.y = 20;
// Leaderboard display
var leaderboardTitle = new Text2('TOP COIN EARNERS', {
size: 50,
fill: 0xFFD700
});
leaderboardTitle.anchor.set(0, 0);
LK.gui.topLeft.addChild(leaderboardTitle);
leaderboardTitle.x = 120; // Leave space for platform menu icon
leaderboardTitle.y = 20;
// Create text elements for each leaderboard entry
var leaderboardEntries = [];
for (var i = 0; i < 3; i++) {
var entryText = new Text2(i + 1 + '. ---: 0', {
size: 40,
fill: 0xFFFFFF
});
entryText.anchor.set(0, 0);
LK.gui.topLeft.addChild(entryText);
entryText.x = 120; // Leave space for platform menu icon
entryText.y = 80 + i * 50;
leaderboardEntries.push(entryText);
}
// Initialize leaderboard
updateLeaderboard();
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 = 90; // Move down to make room for coin counter
var distanceTxt = new Text2('DISTANCE: 0m', {
size: 60,
fill: 0xFFFFFF
});
distanceTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(distanceTxt);
distanceTxt.x = -distanceTxt.width - 20;
distanceTxt.y = 160; // Adjusted to make room for coin and score
var highScoreTxt = new Text2('BEST: ' + highScore, {
size: 50,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(highScoreTxt);
highScoreTxt.x = -highScoreTxt.width - 20;
highScoreTxt.y = 230; // Adjusted to make room for coin and score
var startTxt = new Text2('SWIPE TO START', {
size: 100,
fill: 0xFFFFFF
});
startTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startTxt);
// Game initialization functions
function initializeGame() {
// Create gray ground/background
game.setBackgroundColor(0x777777);
ground = game.addChild(LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 0.5
}));
ground.x = 2048 / 2;
ground.y = 2732 / 2;
// Initialize shop
if (!shop) {
shop = new Shop();
game.addChild(shop);
}
// Create shop button
if (!shopButton) {
shopButton = new Container();
var shopButtonBg = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 150,
tint: 0xFFD700 // Gold color to make it more visible
});
shopButton.addChild(shopButtonBg);
var shopButtonTxt = new Text2('SHOP', {
size: 50,
fill: 0x000000
});
shopButtonTxt.anchor.set(0.5, 0.5);
shopButton.addChild(shopButtonTxt);
// Position at bottom left with some spacing from the edge
shopButton.x = 120;
shopButton.y = 2732 - 120;
game.addChild(shopButton);
// Add event handler to open shop
shopButton.down = function () {
if (!gameStarted) {
shop.show();
}
};
}
// Create 4 vertical roads with no gaps between them
var roadWidth = 2048 / 4; // Divide screen width into 4 equal parts
// Check if Rainbow Roads skin is purchased
var rainbowRoads = storage.purchases && storage.purchases["Rainbow Roads"] === true;
var roadColors = rainbowRoads ? [0xFF0000, 0x00FF00, 0x0000FF, 0xFF00FF] : [0x333333, 0x333333, 0x333333, 0x333333];
for (var i = 0; i < 4; i++) {
var road = game.addChild(LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
width: roadWidth,
// Set width to exactly 1/4 of screen
height: 2732,
// Full screen height
tint: roadColors[i]
}));
road.x = roadWidth * i + roadWidth / 2; // Position roads side by side with no gaps
road.y = 2732 / 2;
}
// Create road lines as vertical marks down the roads
var roadWidth = 2048 / 4; // Width of each road
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 15; j++) {
var roadLine = new RoadLine();
roadLine.lane = i;
roadLine.x = roadWidth * i + roadWidth / 2; // Center in each road
roadLine.y = j * 200; // Distribute lines vertically
roadLines.push(roadLine);
game.addChild(roadLine);
}
}
// Create player
player = game.addChild(new Player());
player.lane = 1; // Start in second lane
player.x = roadPositions[player.lane]; // Position player horizontally on the road
player.y = 2732 - 500; // Position player higher on the screen
// Create regular buses
for (var i = 0; i < 6; i++) {
var bus = new Bus();
bus.lane = Math.floor(Math.random() * 4);
bus.x = roadPositions[bus.lane];
// Position buses with enough space between them vertically
var offset = i * (bus.height + 300);
bus.y = -offset - bus.height;
// Make sure buses in the same lane don't overlap
for (var j = 0; j < buses.length; j++) {
if (bus.lane === buses[j].lane && Math.abs(bus.y - buses[j].y) < bus.height + buses[j].height) {
// Move this bus further up to avoid overlap
bus.y = buses[j].y - (bus.height + 300);
}
}
buses.push(bus);
game.addChild(bus);
}
// Create jumping boards (previously mini buses)
var miniBuses = [];
for (var i = 0; i < 4; i++) {
var miniBus = new MiniBus();
miniBus.lane = Math.floor(Math.random() * 4);
miniBus.x = roadPositions[miniBus.lane];
// Position jumping boards with significantly more space between them
var offset = i * (miniBus.height + 800); // Increased vertical spacing between boards from 400 to 800
miniBus.y = -offset - miniBus.height - 500;
// Make sure jumping boards don't overlap with regular buses
for (var j = 0; j < buses.length; j++) {
if (miniBus.lane === buses[j].lane && Math.abs(miniBus.y - buses[j].y) < miniBus.height + buses[j].height) {
miniBus.y = buses[j].y - (miniBus.height + 600); // Increased separation from 400 to 600
}
}
// Check spacing with other jumping boards that were already created
for (var j = 0; j < miniBuses.length; j++) {
if (miniBus.lane === miniBuses[j].lane && Math.abs(miniBus.y - miniBuses[j].y) < miniBus.height * 6) {
// If too close to another jumping board in the same lane, move it further up
miniBus.y = miniBuses[j].y - miniBus.height * 6;
}
}
miniBuses.push(miniBus);
buses.push(miniBus); // Add to main buses array for updates
game.addChild(miniBus);
}
// Initialize variables
obstacles = []; // Keep empty as we don't use obstacles at all
coins = [];
gameSpeed = initialGameSpeed;
distanceTraveled = 0;
score = 0;
// Don't reset coinCount here to preserve coins between game sessions
updateUI();
// No reset coins button - removed as requested
}
function startGame() {
gameStarted = true;
LK.setScore(0);
// Load persistent coin count from storage
coinCount = storage.coinCount || coinCount;
startTxt.visible = false;
// Hide shop button during gameplay
shopButton.visible = false;
shopButton.alpha = 0; // Ensure it's fully invisible
// Apply Speed Boost powerup if purchased
if (storage.speedBoost === true) {
gameSpeed = initialGameSpeed + 2;
}
// Start increasing game speed over time
speedIncreaseInterval = LK.setInterval(function () {
if (gameSpeed < maxGameSpeed) {
gameSpeed += 0.5;
// Update all buses to have the same speed when game speed changes
for (var i = 0; i < buses.length; i++) {
buses[i].speed = 1; // Keep all buses at same speed when the game speeds up
}
}
}, 10000);
// Start spawning only coins
spawnCoins();
// Play background music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
// Enable all game sounds (preload them by playing at volume 0 then stopping)
LK.getSound('coin_collect').play({
volume: 0
});
LK.getSound('crash').play({
volume: 0
});
LK.getSound('jump').play({
volume: 0
});
LK.getSound('powerup_collect').play({
volume: 0
});
LK.setTimeout(function () {
LK.getSound('coin_collect').stop();
LK.getSound('crash').stop();
LK.getSound('jump').stop();
LK.getSound('powerup_collect').stop();
}, 100);
}
function updateUI() {
coinTxt.setText('COINS: ' + coinCount);
coinTxt.x = -coinTxt.width - 20;
scoreTxt.setText('SCORE: ' + score);
scoreTxt.x = -scoreTxt.width - 20;
var distance = Math.floor(distanceTraveled / 10);
distanceTxt.setText('DISTANCE: ' + distance + 'm');
distanceTxt.x = -distanceTxt.width - 20;
highScoreTxt.setText('BEST: ' + highScore);
highScoreTxt.x = -highScoreTxt.width - 20;
// Save coin count to storage when UI is updated to persist between sessions
storage.coinCount = coinCount;
}
function updateLeaderboard() {
// Get leaderboard from storage or create empty array if none exists
var leaderboard = storage.leaderboard || [];
// Update entries in UI
for (var i = 0; i < 3; i++) {
if (i < leaderboard.length) {
var entry = leaderboard[i];
var parts = entry.split(":");
var playerName = parts[0];
var score = parts[1];
leaderboardEntries[i].setText(i + 1 + '. ' + playerName + ': ' + score);
} else {
leaderboardEntries[i].setText(i + 1 + '. ---: 0');
}
}
}
function addToLeaderboard(newScore) {
// Get leaderboard from storage or create empty array if none exists
var leaderboard = storage.leaderboard || [];
// Generate a player name (could be replaced with actual player names in a real game)
var playerName = "Player" + Math.floor(Math.random() * 100);
// Add new entry with coinCount instead of score
leaderboard.push(playerName + ":" + coinCount);
// Sort leaderboard by coins (descending) - parse coins from string format
leaderboard.sort(function (a, b) {
var coinsA = parseInt(a.split(":")[1]);
var coinsB = parseInt(b.split(":")[1]);
return coinsB - coinsA;
});
// Keep only top entries
if (leaderboard.length > 10) {
leaderboard = leaderboard.slice(0, 10);
}
// Save back to storage
storage.leaderboard = leaderboard;
// Update UI
updateLeaderboard();
}
// Object spawning functions
function spawnObstacles() {
// Function is empty because we don't want to spawn any obstacles
// Only jumping boards (MiniBus objects) will be used as obstacles
}
function spawnCoins() {
var coinTimer = LK.setInterval(function () {
if (!gameStarted) return;
// Random coin pattern
var pattern = Math.floor(Math.random() * 4);
if (pattern === 0) {
// Single coin
var lane = Math.floor(Math.random() * 4);
spawnCoin(lane);
} else if (pattern === 1) {
// Vertical line of coins
var lane = Math.floor(Math.random() * 4);
for (var i = 0; i < 3; i++) {
var coin = new Coin();
coin.lane = lane;
coin.x = roadPositions[lane];
coin.y = -coin.height - i * 120;
coins.push(coin);
game.addChild(coin);
}
} else if (pattern === 2) {
// Horizontal line of coins
for (var i = 0; i < 4; i++) {
spawnCoin(i);
}
} else {
// Diagonal line of coins
for (var i = 0; i < 4; i++) {
var coin = new Coin();
coin.lane = i;
coin.x = roadPositions[i];
coin.y = -coin.height - i * 120;
coins.push(coin);
game.addChild(coin);
}
}
}, 3000);
}
function spawnCoin(lane) {
var coin = new Coin();
coin.lane = lane;
coin.x = roadPositions[lane];
coin.y = -coin.height;
// Check if there are buses in this lane near spawn point with increased range
var busInLane = false;
for (var i = 0; i < buses.length; i++) {
if (buses[i].lane === lane && buses[i].y < 0 && buses[i].y > -500) {
// Increased range check
busInLane = true;
break;
}
}
// Only spawn coins if there's no bus in this lane
if (!busInLane) {
// Increased chance to spawn additional coins if in lane 1 (correct lane)
if (lane === 1 && Math.random() < 0.9) {
// Increased probability from 0.7 to 0.9
// Add extra coins in a vertical line in the correct lane
for (var i = 1; i < 6; i++) {
// Check for buses before spawning each extra coin
var extraCoinY = -coin.height - i * 100;
var extraCoinBusCollision = false;
for (var j = 0; j < buses.length; j++) {
if (buses[j].lane === lane && Math.abs(buses[j].y - extraCoinY) < buses[j].height / 2 + coin.height / 2) {
extraCoinBusCollision = true;
break;
}
}
if (!extraCoinBusCollision) {
var extraCoin = new Coin();
extraCoin.lane = lane;
extraCoin.x = roadPositions[lane];
extraCoin.y = extraCoinY; // Reduced spacing for more coins
coins.push(extraCoin);
game.addChild(extraCoin);
}
}
}
coins.push(coin);
game.addChild(coin);
}
}
// Function removed as powerups are no longer used
// Function to reset all coins in the game (only visual coins, not the count)
function resetCoins() {
// Remove all existing coins on screen
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].destroy();
}
coins = [];
// Ensure we're keeping the coin count persistent
storage.coinCount = coinCount;
updateUI();
}
// Game mechanics functions
function checkCollisions() {
// No obstacle collisions to check since we're not spawning obstacles anymore
// Only jumping boards (MiniBus objects) are used as obstacles
// Check bus collisions
for (var i = buses.length - 1; i >= 0; i--) {
var bus = buses[i];
// Check for bus-to-bus collisions
for (var j = 0; j < buses.length; j++) {
if (i !== j && bus.lane === buses[j].lane && Math.abs(bus.y - buses[j].y) < (bus.height + buses[j].height) * 0.8) {
// Prevent overlap by repositioning this bus
bus.y = buses[j].y - (bus.height + buses[j].height);
if (bus.y > 2732 + bus.height) {
bus.resetPosition();
}
}
}
// Check for bus-to-obstacle collisions
for (var j = 0; j < obstacles.length; j++) {
if (bus.lane === obstacles[j].lane && Math.abs(bus.y - obstacles[j].y) < (bus.height + obstacles[j].height) * 0.8) {
// Prevent overlap by repositioning this bus
bus.y = obstacles[j].y - (bus.height + obstacles[j].height);
if (bus.y > 2732 + bus.height) {
bus.resetPosition();
}
}
}
// Ensure at least one free lane is available at all times
// First count how many lanes have buses in the player's vicinity
var busesNearPlayer = [0, 0, 0, 0]; // Count of buses in each lane
for (var j = 0; j < buses.length; j++) {
var nearBus = buses[j];
// Only count buses that are close to the player vertically
if (Math.abs(nearBus.y - player.y) < 700) {
// Increased detection range
busesNearPlayer[nearBus.lane]++;
}
}
// If all lanes have buses near the player, move one bus to ensure a free lane
var totalBlockedLanes = (busesNearPlayer[0] > 0 ? 1 : 0) + (busesNearPlayer[1] > 0 ? 1 : 0) + (busesNearPlayer[2] > 0 ? 1 : 0) + (busesNearPlayer[3] > 0 ? 1 : 0);
if (totalBlockedLanes >= 3) {
// More aggressive - clear if 3+ lanes are blocked
// Find a lane that's different from player's current lane to free up
var laneToFree = (player.lane + 2) % 4; // Pick lane opposite to player
// Make sure this lane is completely clear by moving ALL buses in this lane
for (var j = 0; j < buses.length; j++) {
if (buses[j].lane === laneToFree && Math.abs(buses[j].y - player.y) < 1000) {
// Increased range
buses[j].resetPosition(); // Move this bus away
// Add additional space to ensure safety
buses[j].y -= Math.random() * 800;
}
}
}
// Check player collision with bus
if (player.lane === bus.lane && Math.abs(player.y - bus.y) < (player.height + bus.height) / 2) {
// Check if it's a jumping board that can be jumped over
if (bus instanceof MiniBus && player.isJumping) {
continue; // Player successfully jumped over the jumping board
}
// Check if player can jump over the jumping board
if (bus instanceof MiniBus) {
// Track jumping boards approaching player
if (bus.y < player.y && bus.y > player.y - 400) {
// No highlighting needed
}
}
// Flash warning when player is about to collide with a bus
if (!player.warningShown && bus.y < player.y && bus.y > player.y - 300) {
player.warningShown = true;
// Flash the player to warn of incoming danger
var flashCount = 0;
var warningInterval = LK.setInterval(function () {
player.alpha = player.alpha === 1 ? 0.5 : 1;
flashCount++;
if (flashCount >= 6) {
LK.clearInterval(warningInterval);
player.alpha = 1;
// Reset warning after a delay
LK.setTimeout(function () {
player.warningShown = false;
}, 1000);
}
}, 100);
}
// Collision occurred
createPlayerParticles();
gameOver();
break;
}
}
// Check coin collections
for (var i = coins.length - 1; i >= 0; i--) {
var coin = coins[i];
// Track the last position of the coin relative to player
if (coin.lastRelativeY === undefined) {
coin.lastRelativeY = coin.y - player.y;
}
var currentRelativeY = coin.y - player.y;
// Regular collection - Only collect when coin passes directly over player
if (player.lane === coin.lane && coin.lastRelativeY < 0 && currentRelativeY >= 0 && Math.abs(player.x - coin.x) < (player.width + coin.width) / 2) {
// Coin just passed over the player (crossed from above to below)
collectCoin(coin, i);
}
// Update last relative position
coin.lastRelativeY = currentRelativeY;
}
// Powerup collection logic removed
}
function collectCoin(coin, index) {
LK.getSound('coin_collect').play();
// Apply coin multiplier if purchased
var multiplier = storage.coinMultiplier || 1;
score += coinValue * multiplier;
coinCount += multiplier;
LK.setScore(score);
// Update shop coin counter if shop is visible
if (shop && shop.visible) {
shopCoinTxt.setText("Coins: " + coinCount);
}
// Get the global position of the coin
var coinPos = {
x: coin.x,
y: coin.y
};
// Get the global position of the coin counter
var counterPos = {
x: LK.gui.topRight.x - coinTxt.width / 2,
y: LK.gui.topRight.y + coinTxt.height / 2
};
// Adjust to make sure coins fly exactly to the counter
counterPos.x -= 200;
counterPos.y += 20;
// Create flying coin animation with rotation
var coinParticle = new CoinParticle();
game.addChild(coinParticle);
coinParticle.init(coinPos.x, coinPos.y, counterPos.x, counterPos.y);
// Make sure the coin exists before trying to remove it
if (index >= 0 && index < coins.length && coins[index]) {
coins.splice(index, 1);
coin.destroy();
} else if (coin) {
// If index is invalid but we have a coin reference, just destroy it
// and remove it from the coins array if found
coin.destroy();
var coinIndex = coins.indexOf(coin);
if (coinIndex >= 0) {
coins.splice(coinIndex, 1);
}
}
updateUI();
}
// Function removed as powerups are no longer used
// Function removed as powerups are no longer used
function createPlayerParticles() {
// Create particle explosion effect at player position
var particleCount = 20;
var particles = [];
for (var i = 0; i < particleCount; i++) {
var particle = new PlayerParticle();
game.addChild(particle);
particle.x = player.x;
particle.y = player.y;
// Random direction
var angle = Math.random() * Math.PI * 2;
var speed = 2 + Math.random() * 5;
particle.vx = Math.cos(angle) * speed;
particle.vy = Math.sin(angle) * speed;
// Random rotation and scale for variety
particle.rotation = Math.random() * Math.PI * 2;
// Set random lifespan
particle.lifespan = 800 + Math.random() * 1200;
particle.birthTime = Date.now();
// Add to particles array for tracking
particles.push(particle);
}
// Hide the original player
player.visible = false;
}
function gameOver() {
// Check if player has Extra Life powerup
if (storage.extraLife === true) {
// Use the extra life
storage.extraLife = false;
// Make player flash to show immunity
player.alpha = 0.5;
// Keep player in current lane
var currentLane = player.lane;
// Create temporary immunity
var immuneTimer = LK.setInterval(function () {
player.alpha = player.alpha === 0.5 ? 1 : 0.5;
}, 100);
// End immunity after 2 seconds
LK.setTimeout(function () {
LK.clearInterval(immuneTimer);
player.alpha = 1;
}, 2000);
return; // Don't end the game
}
LK.getSound('crash').play();
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
// Save final coin count to ensure it persists after game over
storage.coinCount = coinCount;
// Update leaderboard with current coins instead of score
addToLeaderboard(coinCount);
// Clear intervals
LK.clearInterval(speedIncreaseInterval);
// Make player visible again if it was hidden
player.visible = true;
// Create a character growth animation before game over
// Start growing from the player's current position
var currentX = player.x;
var currentY = player.y;
// Make shop button visible again after game over
shopButton.visible = true;
// Use tween to grow player's color to fill the screen
tween(player, {
scaleX: 25,
// Make even larger to ensure full screen coverage
scaleY: 25,
// Make even larger to ensure full screen coverage
alpha: 0.95,
// More visible
rotation: Math.PI * 2 // Full rotation for effect
}, {
duration: 2000,
// Longer duration for more dramatic effect
easing: tween.elasticOut,
onFinish: function onFinish() {
// Now show game over after animation completes
LK.showGameOver();
// Ensure shop button is prominently visible
shopButton.tint = 0xFFFFFF; // Reset tint
shopButton.alpha = 1; // Full opacity
}
});
}
// Helper function to find the safest lane
function findSafeLane() {
// Count objects in each lane near the player
var laneDanger = [0, 0, 0, 0];
// Check buses in each lane with a wider vertical range
for (var i = 0; i < buses.length; i++) {
if (Math.abs(buses[i].y - player.y) < 600) {
// Increased detection range
laneDanger[buses[i].lane] += 2;
}
}
// Find the lane with minimum danger
var minDanger = 999;
var safestLane = player.lane;
for (var i = 0; i < 4; i++) {
if (laneDanger[i] < minDanger) {
minDanger = laneDanger[i];
safestLane = i;
}
}
// ALWAYS ensure at least one lane is completely safe
// If all lanes have danger, ensure at least one lane is safe
var allLanesHaveDanger = true;
for (var i = 0; i < 4; i++) {
if (laneDanger[i] === 0) {
allLanesHaveDanger = false;
safestLane = i; // Immediately use first safe lane found
break;
}
}
// If all lanes have danger, force clear out a lane
if (allLanesHaveDanger) {
// Choose a lane different from player's current lane to free up
var laneToFree = (player.lane + 2) % 4;
// Clear ALL buses from this lane that are near the player
for (var i = 0; i < buses.length; i++) {
if (buses[i].lane === laneToFree && Math.abs(buses[i].y - player.y) < 800) {
// Move bus way above the screen to ensure it's far away
buses[i].y = -buses[i].height - Math.random() * 3000; // Increased distance
}
}
// Set this as safest lane
safestLane = laneToFree;
// Add visual indicator for the safe lane (removed as requested)
}
return safestLane;
}
// Touch/swipe controls
var startY, startX;
var MIN_SWIPE_DISTANCE = 30;
game.down = function (x, y) {
startX = x;
startY = y;
};
game.up = function (x, y) {
if (!startX || !startY) return;
var deltaX = x - startX;
var deltaY = y - startY;
var absDeltaX = Math.abs(deltaX);
var absDeltaY = Math.abs(deltaY);
// Start game on any swipe if not started
if (!gameStarted) {
startGame();
return;
}
// Lower the minimum swipe distance to make it more sensitive
var MIN_SWIPE_DISTANCE = 30;
// Detect swipe direction
if (absDeltaX > MIN_SWIPE_DISTANCE || absDeltaY > MIN_SWIPE_DISTANCE) {
// Horizontal swipe detection (left/right) - always move only to adjacent lane
if (absDeltaX > absDeltaY) {
if (deltaX > 0) {
// Swipe right - move to adjacent right lane if possible
var newLane = Math.min(player.lane + 1, 3);
// Check if killTweensOf exists before calling it
if (tween.killTweensOf) {
tween.killTweensOf(player); // Kill any existing movement tweens
}
// Shorter duration for faster response
tween(player, {
x: roadPositions[newLane]
}, {
duration: 100,
// Faster movement
easing: tween.easeOut
});
player.lane = newLane; // Update player lane
} else {
// Swipe left - move to adjacent left lane if possible
var newLane = Math.max(player.lane - 1, 0);
// Check if killTweensOf exists before calling it
if (tween.killTweensOf) {
tween.killTweensOf(player);
}
// Shorter duration for faster response
tween(player, {
x: roadPositions[newLane]
}, {
duration: 100,
// Faster movement
easing: tween.easeOut
});
player.lane = newLane; // Update player lane
}
}
// Vertical swipe detection (up/down)
else {
if (deltaY < 0) {
// Swipe up (jump)
player.jump();
} else {
// Swipe down (slide)
player.slide();
}
}
}
};
// Initialize the game
initializeGame();
// Game update loop
game.update = function () {
if (!gameStarted) {
// Don't do any movement when game hasn't started
return;
}
// Update distance traveled
distanceTraveled += gameSpeed;
updateUI();
// Check for collisions
checkCollisions();
// No obstacles to update since we don't spawn them anymore
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i]) coins[i].update();
}
// Powerups removed from game
// Ensure jumping boards (MiniBus objects) aren't too close to each other
var jumpingBoards = buses.filter(function (bus) {
return bus instanceof MiniBus;
});
// Check each jumping board against others in the same lane
for (var i = 0; i < jumpingBoards.length; i++) {
for (var j = i + 1; j < jumpingBoards.length; j++) {
if (jumpingBoards[i].lane === jumpingBoards[j].lane) {
var distance = Math.abs(jumpingBoards[i].y - jumpingBoards[j].y);
// If boards are too close, reposition the one that's higher up
if (distance < jumpingBoards[i].height * 10) {
// Increased from 5x to 10x minimum spacing
var higherBoard = jumpingBoards[i].y < jumpingBoards[j].y ? jumpingBoards[i] : jumpingBoards[j];
// Reset position of the higher board to ensure adequate spacing
higherBoard.resetPosition();
// Move it even further up to ensure separation
higherBoard.y -= higherBoard.height * 10 + Math.random() * 400; // Increased spacing
}
}
}
}
for (var i = buses.length - 1; i >= 0; i--) {
if (buses[i]) buses[i].update();
}
// Road lines are now stationary
// Update all PlayerParticle instances that might be in the scene
var particles = game.children.filter(function (child) {
return child instanceof PlayerParticle;
});
for (var i = 0; i < particles.length; i++) {
particles[i].update();
}
// Magnet functionality removed
};