/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Building = Container.expand(function () { var self = Container.call(this); var buildingGraphics = self.attachAsset('building', { anchorX: 0.5, anchorY: 0 }); // Randomize building properties var shade = Math.floor(Math.random() * 40) + 40; buildingGraphics.tint = shade << 16 | shade << 8 | shade; buildingGraphics.height = Math.random() * 600 + 400; buildingGraphics.width = Math.random() * 100 + 100; // Add windows to buildings for a city look var windowCount = Math.floor(buildingGraphics.height / 80); var windowsPerRow = Math.floor(buildingGraphics.width / 40); for (var i = 0; i < windowCount; i++) { for (var j = 0; j < windowsPerRow; j++) { var windowLight = self.addChild(new Container()); var windowAsset = windowLight.attachAsset('building', { anchorX: 0.5, anchorY: 0.5, width: 20, height: 30, tint: 0xFFFF99 }); windowLight.x = j * 40 - buildingGraphics.width / 2 + 25; windowLight.y = i * 80 + 50; // Randomly light windows windowLight.alpha = Math.random() > 0.3 ? 0.8 : 0; } } self.speed = 10; // Default speed self.update = function () { self.y += self.speed; // Reset building when it goes off screen if (self.y > 2732 + buildingGraphics.height) { self.y = -buildingGraphics.height; // Determine road boundaries to keep buildings off the road var road = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].asset && game.children[i].asset.id === 'road') { road = game.children[i]; break; } } // Place building only on sidewalks var roadLeftEdge = road ? (2048 - road.width) / 2 : 424; var roadRightEdge = road ? 2048 - (2048 - road.width) / 2 : 1624; // Determine which side to place the building on based on its current side if (self.x < roadLeftEdge) { // Keep on left side self.x = Math.random() * (roadLeftEdge - buildingGraphics.width / 2); } else { // Keep on right side self.x = roadRightEdge + Math.random() * (roadLeftEdge - buildingGraphics.width / 2); } var shade = Math.floor(Math.random() * 40) + 40; buildingGraphics.tint = shade << 16 | shade << 8 | shade; buildingGraphics.height = Math.random() * 600 + 400; buildingGraphics.width = Math.random() * 100 + 100; // Recreate windows when building resets self.removeChildren(1); // Keep only the main building graphic var windowCount = Math.floor(buildingGraphics.height / 80); var windowsPerRow = Math.floor(buildingGraphics.width / 40); for (var i = 0; i < windowCount; i++) { for (var j = 0; j < windowsPerRow; j++) { var windowLight = self.addChild(new Container()); var windowAsset = windowLight.attachAsset('building', { anchorX: 0.5, anchorY: 0.5, width: 20, height: 30, tint: 0xFFFF99 }); windowLight.x = j * 40 - buildingGraphics.width / 2 + 25; windowLight.y = i * 80 + 50; // Randomly light windows windowLight.alpha = Math.random() > 0.3 ? 0.8 : 0; } } } }; return self; }); var Car = Container.expand(function () { var self = Container.call(this); // Create car body - using just the car asset without additional square var carGraphics = self.attachAsset('car', { anchorX: 0.5, anchorY: 0.5, tint: 0x3366CC // Blue car color }); // Add headlights var leftHeadlight = self.addChild(new Container()); var leftHeadlightAsset = leftHeadlight.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.5, width: 40, height: 40, tint: 0xFFFFAA }); leftHeadlight.x = -60; leftHeadlight.y = -140; var rightHeadlight = self.addChild(new Container()); var rightHeadlightAsset = rightHeadlight.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.5, width: 40, height: 40, tint: 0xFFFFAA }); rightHeadlight.x = 60; rightHeadlight.y = -140; self.jumping = false; self.jumpHeight = 100; self.originalY = 0; self.lastY = 0; // Track last Y position for intersection detection self.jump = function () { if (self.jumping) { return; } self.jumping = true; self.originalY = self.y; // Play jump sound LK.getSound('jump').play(); // Jump animation using tween tween(self, { y: self.y - self.jumpHeight }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Fall back down tween(self, { y: self.originalY }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.jumping = false; } }); } }); }; self.down = function () { self.jump(); }; self.update = function () { self.lastY = self.y; // Create headlight glow effect if (LK.ticks % 5 === 0) { leftHeadlightAsset.alpha = 0.7 + Math.random() * 0.3; rightHeadlightAsset.alpha = 0.7 + Math.random() * 0.3; } }; return self; }); var Line = Container.expand(function () { var self = Container.call(this); // Create road line marking var lineGraphics = self.attachAsset('line', { anchorX: 0.5, anchorY: 0.5 }); // Make line vertical lineGraphics.rotation = Math.PI / 2; // Rotate 90 degrees // Road lines have fixed width to better match car size lineGraphics.width = 400; // Line length (now vertical) lineGraphics.height = 60; // Line thickness (more visible) self.lastY = 0; // Track last Y position for intersection detection self.speed = 15; // Default speed self.active = true; // Whether the line is active for scoring self.scored = false; // Whether this line has been scored already // Add visual effect to make lines more prominent var glowEffect = self.addChild(new Container()); var glowAsset = glowEffect.attachAsset('line', { anchorX: 0.5, anchorY: 0.5, width: lineGraphics.width + 20, height: lineGraphics.height + 20, tint: 0xFFFF00, alpha: 0.3 }); glowAsset.rotation = Math.PI / 2; self.update = function () { self.lastY = self.y; // Store last position for collision detection self.y += self.speed; // Pulse glow effect if (LK.ticks % 10 === 0) { glowAsset.alpha = 0.2 + Math.random() * 0.2; } // Remove when off screen if (self.y > 2732 + lineGraphics.width / 2) { self.active = false; } }; return self; }); var ScoreEffect = Container.expand(function () { var self = Container.call(this); var effectGraphics = self.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); self.show = function (x, y, isPositive) { self.x = x; self.y = y; if (!isPositive) { effectGraphics.tint = 0xFF0000; // Play penalty sound LK.getSound('penalty').play(); } else { effectGraphics.tint = 0x00FF00; // Play point sound LK.getSound('point').play(); } // Animation for the effect tween(self, { alpha: 0, y: y - 100 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { self.alpha = 1; self.visible = false; } }); self.visible = true; }; return self; }); var Store = Container.expand(function () { var self = Container.call(this); // Create store building var storeGraphics = self.attachAsset('building', { anchorX: 0.5, anchorY: 0, width: 200, height: 250, tint: 0xFAE5D3 // Light beige for store }); // Add store sign var storeSign = self.addChild(new Container()); var storeSignAsset = storeSign.attachAsset('building', { anchorX: 0.5, anchorY: 0.5, width: 180, height: 60, tint: 0x3498DB // Blue for store sign }); storeSign.y = 40; // Add store window var storeWindow = self.addChild(new Container()); var storeWindowAsset = storeWindow.attachAsset('building', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 100, tint: 0xD6EAF8 // Light blue for window }); storeWindow.y = 120; // Add store door var storeDoor = self.addChild(new Container()); var storeDoorAsset = storeDoor.attachAsset('building', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 100, tint: 0x784212 // Brown for door }); storeDoor.y = 180; self.speed = 10; // Default speed self.update = function () { self.y += self.speed; // Reset store when it goes off screen if (self.y > 2732 + storeGraphics.height) { self.y = -storeGraphics.height; // Randomize store appearance var storeTypes = [{ building: 0xFAE5D3, sign: 0x3498DB, window: 0xD6EAF8 }, // Blue store { building: 0xFCF3CF, sign: 0xE74C3C, window: 0xFADBD8 }, // Red store { building: 0xD5F5E3, sign: 0x8E44AD, window: 0xEBDEF0 }, // Purple store { building: 0xEAEDED, sign: 0xF39C12, window: 0xFAE5D3 } // Orange store ]; var type = storeTypes[Math.floor(Math.random() * storeTypes.length)]; storeGraphics.tint = type.building; storeSignAsset.tint = type.sign; storeWindowAsset.tint = type.window; // Vary store size slightly var sizeVar = 0.8 + Math.random() * 0.4; storeGraphics.width = 200 * sizeVar; storeGraphics.height = 250 * sizeVar; storeSign.scale.set(sizeVar); storeWindow.scale.set(sizeVar); storeDoor.scale.set(sizeVar); // Adjust positions based on new size storeSign.y = 40 * sizeVar; storeWindow.y = 120 * sizeVar; storeDoor.y = 180 * sizeVar; } }; return self; }); var StreetLight = Container.expand(function () { var self = Container.call(this); // Create pole var pole = self.addChild(new Container()); var poleAsset = pole.attachAsset('building', { anchorX: 0.5, anchorY: 1.0, width: 20, height: 200, tint: 0x555555 // Dark gray for pole }); // Create light var light = self.addChild(new Container()); var lightAsset = light.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.5, width: 40, height: 40, tint: 0xFFFF99 // Yellow light }); light.y = -200; // Light arm var arm = self.addChild(new Container()); var armAsset = arm.attachAsset('building', { anchorX: 0, anchorY: 0.5, width: 40, height: 10, tint: 0x555555 // Dark gray for arm }); arm.y = -190; self.speed = 10; // Default speed self.isLit = Math.random() > 0.2; // Most lights are on self.update = function () { self.y += self.speed; // Make light flicker occasionally if (self.isLit && LK.ticks % 20 === 0 && Math.random() > 0.7) { lightAsset.alpha = 0.6 + Math.random() * 0.4; } // Reset light when it goes off screen if (self.y > 2732 + 200) { self.y = -200; self.isLit = Math.random() > 0.2; lightAsset.alpha = self.isLit ? 1 : 0.2; } }; return self; }); var TapEffect = Container.expand(function () { var self = Container.call(this); var effectGraphics = self.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 300, alpha: 0.6 }); self.show = function (x, y, isPositive) { self.x = x; self.y = y; // Set color based on whether it's a positive or negative tap if (isPositive) { effectGraphics.tint = 0x00FF00; // Green for good taps // Play point sound with lower volume to avoid overwhelming LK.getSound('point').play({ volume: 0.3 }); } else { effectGraphics.tint = 0xFF0000; // Red for bad taps // Play penalty sound with lower volume to avoid overwhelming LK.getSound('penalty').play({ volume: 0.3 }); } // Reset properties before animation self.alpha = 1; effectGraphics.alpha = 0.6; effectGraphics.scale.x = 0.5; effectGraphics.scale.y = 0.5; self.visible = true; // Animate the effect - expand and fade out for a nice ripple effect tween(effectGraphics, { alpha: 0, scaleX: 2.5, scaleY: 2.5 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { self.visible = false; effectGraphics.scale.x = 0.5; effectGraphics.scale.y = 0.5; } }); }; return self; }); var Tree = Container.expand(function () { var self = Container.call(this); // Create tree trunk var trunk = self.addChild(new Container()); var trunkAsset = trunk.attachAsset('building', { anchorX: 0.5, anchorY: 1.0, width: 40, height: 120, tint: 0x8B4513 // Brown color for trunk }); // Create tree foliage (multiple layers for depth) var foliage1 = self.addChild(new Container()); var foliageAsset1 = foliage1.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.8, width: 150, height: 150, tint: 0x228B22 // Forest green }); foliage1.y = -80; var foliage2 = self.addChild(new Container()); var foliageAsset2 = foliage2.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.7, width: 120, height: 120, tint: 0x32CD32 // Lime green for highlight }); foliage2.y = -100; self.speed = 10; // Default speed self.update = function () { self.y += self.speed; // Reset tree when it goes off screen if (self.y > 2732 + 150) { self.y = -150; // Randomize tree appearance when resetting trunkAsset.height = 80 + Math.random() * 80; trunkAsset.width = 30 + Math.random() * 20; var scale = 0.8 + Math.random() * 0.5; foliage1.scale.set(scale); foliage2.scale.set(scale * 0.9); // Slightly vary green shades var greenVariation = Math.floor(Math.random() * 40); foliageAsset1.tint = 34 + greenVariation << 16 | 139 + greenVariation << 8 | 34; foliageAsset2.tint = 50 + greenVariation << 16 | 205 + greenVariation << 8 | 50; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ // Game variables var car; var lines = []; var buildings = []; var trees = []; var stores = []; var streetLights = []; var score = 0; var scoreText; var speedMultiplier = 1; var lineSpawnRate = 60; // Frames between line spawns var difficulty = 1; var isGameActive = true; var scoreEffects = []; var tapEffects = []; var effectIndex = 0; var tapEffectIndex = 0; var lastJumpScore = 0; // Create a sky background game.setBackgroundColor(0x87CEEB); // Light blue sky // Initialize the road var road = game.addChild(LK.getAsset('road', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, width: 1200, // Make the road narrower than the full width tint: 0x444444 // Darker gray for asphalt })); // Add road edges/sidewalks var leftSidewalk = game.addChild(LK.getAsset('building', { anchorX: 0, anchorY: 0.5, width: (2048 - road.width) / 2, height: 2732, x: 0, y: 2732 / 2, tint: 0x999999 // Light gray for sidewalk })); var rightSidewalk = game.addChild(LK.getAsset('building', { anchorX: 1, anchorY: 0.5, width: (2048 - road.width) / 2, height: 2732, x: 2048, y: 2732 / 2, tint: 0x999999 // Light gray for sidewalk })); // Initialize score display scoreText = new Text2('SCORE: 0', { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 5 }); scoreText.anchor.set(0.5, 0); scoreText.y = 50; // Move down slightly from top LK.gui.top.addChild(scoreText); // Add game instructions var instructionsText = new Text2('Tap when no lines\nAvoid tapping on lines\nScore 1000 to win', { size: 80, fill: 0xFFFFFF, stroke: 0x800080, strokeThickness: 10, fontWeight: 'bold', align: 'center' }); instructionsText.anchor.set(0.5, 0); instructionsText.y = 150; LK.gui.top.addChild(instructionsText); // Create buildings for city skyline on both sides of the road (not on the road) for (var i = 0; i < 20; i++) { var building = new Building(); // Position buildings only on the sides, ensuring they stay off the road var roadLeftEdge = (2048 - road.width) / 2; var roadRightEdge = 2048 - roadLeftEdge; if (i % 2 === 0) { // Left side only - ensure buildings stay completely on sidewalk var buildingWidth = building.children[0].width; building.x = Math.random() * (roadLeftEdge - buildingWidth / 2); // Left side only, accounting for building width } else { // Right side only - ensure buildings stay completely on sidewalk var buildingWidth = building.children[0].width; building.x = roadRightEdge + Math.random() * (roadLeftEdge - buildingWidth / 2); // Right side only } building.y = Math.random() * 2732; buildings.push(building); game.addChild(building); } // Add trees along the road var trees = []; for (var i = 0; i < 15; i++) { var tree = new Tree(); // Position trees on the sidewalks if (i % 2 === 0) { // Left side trees - closer to road tree.x = (2048 - road.width) / 2 - 80 - Math.random() * 60; } else { // Right side trees - closer to road tree.x = 2048 - (2048 - road.width) / 2 + 80 + Math.random() * 60; } // Distribute trees along the road tree.y = -150 + i * 350 + Math.random() * 200; trees.push(tree); game.addChild(tree); } // Add stores on the sides var stores = []; for (var i = 0; i < 8; i++) { var store = new Store(); // Position stores on the sidewalks, further from the road than trees if (i % 2 === 0) { // Left side stores store.x = (2048 - road.width) / 4; } else { // Right side stores store.x = 2048 - (2048 - road.width) / 4; } // Distribute stores along the road with some spacing store.y = -300 + i * 650 + Math.random() * 300; stores.push(store); game.addChild(store); } // Add street lights var streetLights = []; for (var i = 0; i < 12; i++) { var light = new StreetLight(); // Position lights on the edge of the road if (i % 2 === 0) { // Left side lights light.x = (2048 - road.width) / 2 - 20; } else { // Right side lights light.x = 2048 - (2048 - road.width) / 2 + 20; } // Distribute lights evenly light.y = -200 + i * 400 + i % 2 * 200; streetLights.push(light); game.addChild(light); } // Create car car = new Car(); car.x = 2048 / 2; car.y = 2732 - 400; game.addChild(car); // Bring car to front to make sure it appears on top of lines game.setChildIndex(car, game.children.length - 1); // Create score effects pool for (var i = 0; i < 5; i++) { var effect = new ScoreEffect(); effect.visible = false; scoreEffects.push(effect); game.addChild(effect); } // Create tap effects pool - increased size to handle multiple effects for (var i = 0; i < 10; i++) { var tapEffect = new TapEffect(); tapEffect.visible = false; tapEffects.push(tapEffect); game.addChild(tapEffect); } // Game click handler game.down = function (x, y, obj) { if (!isGameActive) { return; } // Check if car is already jumping if (!car.jumping) { car.jump(); // Check if car is over a road line var overLine = false; for (var i = 0; i < lines.length; i++) { // Check if the car intersects with any vertical road line if (lines[i].active && car.intersects(lines[i])) { overLine = true; break; } } // Score or penalty based on jump if (overLine) { // Penalty for jumping over a line score = Math.max(0, score - 10); // Show penalty effect var effect = scoreEffects[effectIndex]; effectIndex = (effectIndex + 1) % scoreEffects.length; effect.show(car.x, car.y - 50, false); // Show tap feedback effect (red for penalty) var tapEffect = tapEffects[tapEffectIndex]; tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length; tapEffect.show(x, y, false); // Add multiple red effects for stronger visual feedback for (var j = 0; j < 2; j++) { var extraEffect = tapEffects[tapEffectIndex]; tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length; extraEffect.show(x + (Math.random() * 100 - 50), y + (Math.random() * 100 - 50), false); } // Flash screen red for bad tap LK.effects.flashScreen(0xff0000, 300); } else { // Points for jumping on empty road var pointsGained = 20 * difficulty; score += pointsGained; lastJumpScore = pointsGained; // Show score effect var effect = scoreEffects[effectIndex]; effectIndex = (effectIndex + 1) % scoreEffects.length; effect.show(car.x, car.y - 50, true); // Show tap feedback effect (green for points) var tapEffect = tapEffects[tapEffectIndex]; tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length; tapEffect.show(x, y, true); // Add multiple green effects for stronger visual feedback for (var j = 0; j < 2; j++) { var extraEffect = tapEffects[tapEffectIndex]; tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length; extraEffect.show(x + (Math.random() * 100 - 50), y + (Math.random() * 100 - 50), true); } } // Update score display scoreText.setText(Math.round(score)); LK.setScore(Math.round(score)); } }; // Start background music LK.playMusic('bgmusic', { fade: { start: 0, end: 0.4, duration: 1000 } }); // Game update function game.update = function () { if (!isGameActive) { return; } // Update car car.update(); // Update all lines for (var i = lines.length - 1; i >= 0; i--) { lines[i].update(); // We don't penalize for just passing over a line anymore // Only interactions happen when player taps if (!lines[i].scored && lines[i].lastY < car.y && lines[i].y >= car.y) { lines[i].scored = true; // Mark as scored } // Remove inactive lines if (!lines[i].active) { lines[i].destroy(); lines.splice(i, 1); } } // Update all buildings for (var i = 0; i < buildings.length; i++) { buildings[i].speed = 5 * speedMultiplier; buildings[i].update(); } // Update all trees for (var i = 0; i < trees.length; i++) { trees[i].speed = 7 * speedMultiplier; trees[i].update(); } // Update all stores for (var i = 0; i < stores.length; i++) { stores[i].speed = 5 * speedMultiplier; stores[i].update(); } // Update all street lights for (var i = 0; i < streetLights.length; i++) { streetLights[i].speed = 7 * speedMultiplier; streetLights[i].update(); } // Create new lines at intervals - always come from center of the screen if (LK.ticks % Math.floor(lineSpawnRate / speedMultiplier) === 0) { var line = new Line(); // Position line at the center of the screen line.x = 2048 / 2; // Center X coordinate line.y = -50; line.speed = 15 * speedMultiplier; lines.push(line); game.addChild(line); // Ensure car is always rendered on top of lines game.setChildIndex(car, game.children.length - 1); } // Increase difficulty over time if (LK.ticks % 600 === 0) { // Every 10 seconds difficulty += 0.1; // Speed up game after 500 points, but not too much, and slow down a bit after 800 if (score > 800) { speedMultiplier = 1 + (difficulty - 1) * 1.0; // Reduced speed multiplier after 800 points lineSpawnRate = Math.max(15, 40 - difficulty * 3); // Reduced line spawn rate } else if (score > 500) { speedMultiplier = 1 + (difficulty - 1) * 1.1; // Moderate speed increase after 500 points lineSpawnRate = Math.max(12, 40 - difficulty * 4); // Moderate increase in line spawn rate } else { speedMultiplier = 1 + (difficulty - 1) * 0.5; lineSpawnRate = Math.max(15, 60 - difficulty * 5); } } // Check for game over condition if (score < 0) { isGameActive = false; LK.showGameOver(); } // Check for win condition (1000 points to win) if (score >= 1000) { isGameActive = false; LK.showYouWin(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Building = Container.expand(function () {
var self = Container.call(this);
var buildingGraphics = self.attachAsset('building', {
anchorX: 0.5,
anchorY: 0
});
// Randomize building properties
var shade = Math.floor(Math.random() * 40) + 40;
buildingGraphics.tint = shade << 16 | shade << 8 | shade;
buildingGraphics.height = Math.random() * 600 + 400;
buildingGraphics.width = Math.random() * 100 + 100;
// Add windows to buildings for a city look
var windowCount = Math.floor(buildingGraphics.height / 80);
var windowsPerRow = Math.floor(buildingGraphics.width / 40);
for (var i = 0; i < windowCount; i++) {
for (var j = 0; j < windowsPerRow; j++) {
var windowLight = self.addChild(new Container());
var windowAsset = windowLight.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 30,
tint: 0xFFFF99
});
windowLight.x = j * 40 - buildingGraphics.width / 2 + 25;
windowLight.y = i * 80 + 50;
// Randomly light windows
windowLight.alpha = Math.random() > 0.3 ? 0.8 : 0;
}
}
self.speed = 10; // Default speed
self.update = function () {
self.y += self.speed;
// Reset building when it goes off screen
if (self.y > 2732 + buildingGraphics.height) {
self.y = -buildingGraphics.height;
// Determine road boundaries to keep buildings off the road
var road = null;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].asset && game.children[i].asset.id === 'road') {
road = game.children[i];
break;
}
}
// Place building only on sidewalks
var roadLeftEdge = road ? (2048 - road.width) / 2 : 424;
var roadRightEdge = road ? 2048 - (2048 - road.width) / 2 : 1624;
// Determine which side to place the building on based on its current side
if (self.x < roadLeftEdge) {
// Keep on left side
self.x = Math.random() * (roadLeftEdge - buildingGraphics.width / 2);
} else {
// Keep on right side
self.x = roadRightEdge + Math.random() * (roadLeftEdge - buildingGraphics.width / 2);
}
var shade = Math.floor(Math.random() * 40) + 40;
buildingGraphics.tint = shade << 16 | shade << 8 | shade;
buildingGraphics.height = Math.random() * 600 + 400;
buildingGraphics.width = Math.random() * 100 + 100;
// Recreate windows when building resets
self.removeChildren(1); // Keep only the main building graphic
var windowCount = Math.floor(buildingGraphics.height / 80);
var windowsPerRow = Math.floor(buildingGraphics.width / 40);
for (var i = 0; i < windowCount; i++) {
for (var j = 0; j < windowsPerRow; j++) {
var windowLight = self.addChild(new Container());
var windowAsset = windowLight.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 30,
tint: 0xFFFF99
});
windowLight.x = j * 40 - buildingGraphics.width / 2 + 25;
windowLight.y = i * 80 + 50;
// Randomly light windows
windowLight.alpha = Math.random() > 0.3 ? 0.8 : 0;
}
}
}
};
return self;
});
var Car = Container.expand(function () {
var self = Container.call(this);
// Create car body - using just the car asset without additional square
var carGraphics = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x3366CC // Blue car color
});
// Add headlights
var leftHeadlight = self.addChild(new Container());
var leftHeadlightAsset = leftHeadlight.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0xFFFFAA
});
leftHeadlight.x = -60;
leftHeadlight.y = -140;
var rightHeadlight = self.addChild(new Container());
var rightHeadlightAsset = rightHeadlight.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0xFFFFAA
});
rightHeadlight.x = 60;
rightHeadlight.y = -140;
self.jumping = false;
self.jumpHeight = 100;
self.originalY = 0;
self.lastY = 0; // Track last Y position for intersection detection
self.jump = function () {
if (self.jumping) {
return;
}
self.jumping = true;
self.originalY = self.y;
// Play jump sound
LK.getSound('jump').play();
// Jump animation using tween
tween(self, {
y: self.y - self.jumpHeight
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Fall back down
tween(self, {
y: self.originalY
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.jumping = false;
}
});
}
});
};
self.down = function () {
self.jump();
};
self.update = function () {
self.lastY = self.y;
// Create headlight glow effect
if (LK.ticks % 5 === 0) {
leftHeadlightAsset.alpha = 0.7 + Math.random() * 0.3;
rightHeadlightAsset.alpha = 0.7 + Math.random() * 0.3;
}
};
return self;
});
var Line = Container.expand(function () {
var self = Container.call(this);
// Create road line marking
var lineGraphics = self.attachAsset('line', {
anchorX: 0.5,
anchorY: 0.5
});
// Make line vertical
lineGraphics.rotation = Math.PI / 2; // Rotate 90 degrees
// Road lines have fixed width to better match car size
lineGraphics.width = 400; // Line length (now vertical)
lineGraphics.height = 60; // Line thickness (more visible)
self.lastY = 0; // Track last Y position for intersection detection
self.speed = 15; // Default speed
self.active = true; // Whether the line is active for scoring
self.scored = false; // Whether this line has been scored already
// Add visual effect to make lines more prominent
var glowEffect = self.addChild(new Container());
var glowAsset = glowEffect.attachAsset('line', {
anchorX: 0.5,
anchorY: 0.5,
width: lineGraphics.width + 20,
height: lineGraphics.height + 20,
tint: 0xFFFF00,
alpha: 0.3
});
glowAsset.rotation = Math.PI / 2;
self.update = function () {
self.lastY = self.y; // Store last position for collision detection
self.y += self.speed;
// Pulse glow effect
if (LK.ticks % 10 === 0) {
glowAsset.alpha = 0.2 + Math.random() * 0.2;
}
// Remove when off screen
if (self.y > 2732 + lineGraphics.width / 2) {
self.active = false;
}
};
return self;
});
var ScoreEffect = Container.expand(function () {
var self = Container.call(this);
var effectGraphics = self.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.show = function (x, y, isPositive) {
self.x = x;
self.y = y;
if (!isPositive) {
effectGraphics.tint = 0xFF0000;
// Play penalty sound
LK.getSound('penalty').play();
} else {
effectGraphics.tint = 0x00FF00;
// Play point sound
LK.getSound('point').play();
}
// Animation for the effect
tween(self, {
alpha: 0,
y: y - 100
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.alpha = 1;
self.visible = false;
}
});
self.visible = true;
};
return self;
});
var Store = Container.expand(function () {
var self = Container.call(this);
// Create store building
var storeGraphics = self.attachAsset('building', {
anchorX: 0.5,
anchorY: 0,
width: 200,
height: 250,
tint: 0xFAE5D3 // Light beige for store
});
// Add store sign
var storeSign = self.addChild(new Container());
var storeSignAsset = storeSign.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 180,
height: 60,
tint: 0x3498DB // Blue for store sign
});
storeSign.y = 40;
// Add store window
var storeWindow = self.addChild(new Container());
var storeWindowAsset = storeWindow.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 100,
tint: 0xD6EAF8 // Light blue for window
});
storeWindow.y = 120;
// Add store door
var storeDoor = self.addChild(new Container());
var storeDoorAsset = storeDoor.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 100,
tint: 0x784212 // Brown for door
});
storeDoor.y = 180;
self.speed = 10; // Default speed
self.update = function () {
self.y += self.speed;
// Reset store when it goes off screen
if (self.y > 2732 + storeGraphics.height) {
self.y = -storeGraphics.height;
// Randomize store appearance
var storeTypes = [{
building: 0xFAE5D3,
sign: 0x3498DB,
window: 0xD6EAF8
},
// Blue store
{
building: 0xFCF3CF,
sign: 0xE74C3C,
window: 0xFADBD8
},
// Red store
{
building: 0xD5F5E3,
sign: 0x8E44AD,
window: 0xEBDEF0
},
// Purple store
{
building: 0xEAEDED,
sign: 0xF39C12,
window: 0xFAE5D3
} // Orange store
];
var type = storeTypes[Math.floor(Math.random() * storeTypes.length)];
storeGraphics.tint = type.building;
storeSignAsset.tint = type.sign;
storeWindowAsset.tint = type.window;
// Vary store size slightly
var sizeVar = 0.8 + Math.random() * 0.4;
storeGraphics.width = 200 * sizeVar;
storeGraphics.height = 250 * sizeVar;
storeSign.scale.set(sizeVar);
storeWindow.scale.set(sizeVar);
storeDoor.scale.set(sizeVar);
// Adjust positions based on new size
storeSign.y = 40 * sizeVar;
storeWindow.y = 120 * sizeVar;
storeDoor.y = 180 * sizeVar;
}
};
return self;
});
var StreetLight = Container.expand(function () {
var self = Container.call(this);
// Create pole
var pole = self.addChild(new Container());
var poleAsset = pole.attachAsset('building', {
anchorX: 0.5,
anchorY: 1.0,
width: 20,
height: 200,
tint: 0x555555 // Dark gray for pole
});
// Create light
var light = self.addChild(new Container());
var lightAsset = light.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0xFFFF99 // Yellow light
});
light.y = -200;
// Light arm
var arm = self.addChild(new Container());
var armAsset = arm.attachAsset('building', {
anchorX: 0,
anchorY: 0.5,
width: 40,
height: 10,
tint: 0x555555 // Dark gray for arm
});
arm.y = -190;
self.speed = 10; // Default speed
self.isLit = Math.random() > 0.2; // Most lights are on
self.update = function () {
self.y += self.speed;
// Make light flicker occasionally
if (self.isLit && LK.ticks % 20 === 0 && Math.random() > 0.7) {
lightAsset.alpha = 0.6 + Math.random() * 0.4;
}
// Reset light when it goes off screen
if (self.y > 2732 + 200) {
self.y = -200;
self.isLit = Math.random() > 0.2;
lightAsset.alpha = self.isLit ? 1 : 0.2;
}
};
return self;
});
var TapEffect = Container.expand(function () {
var self = Container.call(this);
var effectGraphics = self.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 300,
alpha: 0.6
});
self.show = function (x, y, isPositive) {
self.x = x;
self.y = y;
// Set color based on whether it's a positive or negative tap
if (isPositive) {
effectGraphics.tint = 0x00FF00; // Green for good taps
// Play point sound with lower volume to avoid overwhelming
LK.getSound('point').play({
volume: 0.3
});
} else {
effectGraphics.tint = 0xFF0000; // Red for bad taps
// Play penalty sound with lower volume to avoid overwhelming
LK.getSound('penalty').play({
volume: 0.3
});
}
// Reset properties before animation
self.alpha = 1;
effectGraphics.alpha = 0.6;
effectGraphics.scale.x = 0.5;
effectGraphics.scale.y = 0.5;
self.visible = true;
// Animate the effect - expand and fade out for a nice ripple effect
tween(effectGraphics, {
alpha: 0,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.visible = false;
effectGraphics.scale.x = 0.5;
effectGraphics.scale.y = 0.5;
}
});
};
return self;
});
var Tree = Container.expand(function () {
var self = Container.call(this);
// Create tree trunk
var trunk = self.addChild(new Container());
var trunkAsset = trunk.attachAsset('building', {
anchorX: 0.5,
anchorY: 1.0,
width: 40,
height: 120,
tint: 0x8B4513 // Brown color for trunk
});
// Create tree foliage (multiple layers for depth)
var foliage1 = self.addChild(new Container());
var foliageAsset1 = foliage1.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.8,
width: 150,
height: 150,
tint: 0x228B22 // Forest green
});
foliage1.y = -80;
var foliage2 = self.addChild(new Container());
var foliageAsset2 = foliage2.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.7,
width: 120,
height: 120,
tint: 0x32CD32 // Lime green for highlight
});
foliage2.y = -100;
self.speed = 10; // Default speed
self.update = function () {
self.y += self.speed;
// Reset tree when it goes off screen
if (self.y > 2732 + 150) {
self.y = -150;
// Randomize tree appearance when resetting
trunkAsset.height = 80 + Math.random() * 80;
trunkAsset.width = 30 + Math.random() * 20;
var scale = 0.8 + Math.random() * 0.5;
foliage1.scale.set(scale);
foliage2.scale.set(scale * 0.9);
// Slightly vary green shades
var greenVariation = Math.floor(Math.random() * 40);
foliageAsset1.tint = 34 + greenVariation << 16 | 139 + greenVariation << 8 | 34;
foliageAsset2.tint = 50 + greenVariation << 16 | 205 + greenVariation << 8 | 50;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var car;
var lines = [];
var buildings = [];
var trees = [];
var stores = [];
var streetLights = [];
var score = 0;
var scoreText;
var speedMultiplier = 1;
var lineSpawnRate = 60; // Frames between line spawns
var difficulty = 1;
var isGameActive = true;
var scoreEffects = [];
var tapEffects = [];
var effectIndex = 0;
var tapEffectIndex = 0;
var lastJumpScore = 0;
// Create a sky background
game.setBackgroundColor(0x87CEEB); // Light blue sky
// Initialize the road
var road = game.addChild(LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1200,
// Make the road narrower than the full width
tint: 0x444444 // Darker gray for asphalt
}));
// Add road edges/sidewalks
var leftSidewalk = game.addChild(LK.getAsset('building', {
anchorX: 0,
anchorY: 0.5,
width: (2048 - road.width) / 2,
height: 2732,
x: 0,
y: 2732 / 2,
tint: 0x999999 // Light gray for sidewalk
}));
var rightSidewalk = game.addChild(LK.getAsset('building', {
anchorX: 1,
anchorY: 0.5,
width: (2048 - road.width) / 2,
height: 2732,
x: 2048,
y: 2732 / 2,
tint: 0x999999 // Light gray for sidewalk
}));
// Initialize score display
scoreText = new Text2('SCORE: 0', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 5
});
scoreText.anchor.set(0.5, 0);
scoreText.y = 50; // Move down slightly from top
LK.gui.top.addChild(scoreText);
// Add game instructions
var instructionsText = new Text2('Tap when no lines\nAvoid tapping on lines\nScore 1000 to win', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x800080,
strokeThickness: 10,
fontWeight: 'bold',
align: 'center'
});
instructionsText.anchor.set(0.5, 0);
instructionsText.y = 150;
LK.gui.top.addChild(instructionsText);
// Create buildings for city skyline on both sides of the road (not on the road)
for (var i = 0; i < 20; i++) {
var building = new Building();
// Position buildings only on the sides, ensuring they stay off the road
var roadLeftEdge = (2048 - road.width) / 2;
var roadRightEdge = 2048 - roadLeftEdge;
if (i % 2 === 0) {
// Left side only - ensure buildings stay completely on sidewalk
var buildingWidth = building.children[0].width;
building.x = Math.random() * (roadLeftEdge - buildingWidth / 2); // Left side only, accounting for building width
} else {
// Right side only - ensure buildings stay completely on sidewalk
var buildingWidth = building.children[0].width;
building.x = roadRightEdge + Math.random() * (roadLeftEdge - buildingWidth / 2); // Right side only
}
building.y = Math.random() * 2732;
buildings.push(building);
game.addChild(building);
}
// Add trees along the road
var trees = [];
for (var i = 0; i < 15; i++) {
var tree = new Tree();
// Position trees on the sidewalks
if (i % 2 === 0) {
// Left side trees - closer to road
tree.x = (2048 - road.width) / 2 - 80 - Math.random() * 60;
} else {
// Right side trees - closer to road
tree.x = 2048 - (2048 - road.width) / 2 + 80 + Math.random() * 60;
}
// Distribute trees along the road
tree.y = -150 + i * 350 + Math.random() * 200;
trees.push(tree);
game.addChild(tree);
}
// Add stores on the sides
var stores = [];
for (var i = 0; i < 8; i++) {
var store = new Store();
// Position stores on the sidewalks, further from the road than trees
if (i % 2 === 0) {
// Left side stores
store.x = (2048 - road.width) / 4;
} else {
// Right side stores
store.x = 2048 - (2048 - road.width) / 4;
}
// Distribute stores along the road with some spacing
store.y = -300 + i * 650 + Math.random() * 300;
stores.push(store);
game.addChild(store);
}
// Add street lights
var streetLights = [];
for (var i = 0; i < 12; i++) {
var light = new StreetLight();
// Position lights on the edge of the road
if (i % 2 === 0) {
// Left side lights
light.x = (2048 - road.width) / 2 - 20;
} else {
// Right side lights
light.x = 2048 - (2048 - road.width) / 2 + 20;
}
// Distribute lights evenly
light.y = -200 + i * 400 + i % 2 * 200;
streetLights.push(light);
game.addChild(light);
}
// Create car
car = new Car();
car.x = 2048 / 2;
car.y = 2732 - 400;
game.addChild(car);
// Bring car to front to make sure it appears on top of lines
game.setChildIndex(car, game.children.length - 1);
// Create score effects pool
for (var i = 0; i < 5; i++) {
var effect = new ScoreEffect();
effect.visible = false;
scoreEffects.push(effect);
game.addChild(effect);
}
// Create tap effects pool - increased size to handle multiple effects
for (var i = 0; i < 10; i++) {
var tapEffect = new TapEffect();
tapEffect.visible = false;
tapEffects.push(tapEffect);
game.addChild(tapEffect);
}
// Game click handler
game.down = function (x, y, obj) {
if (!isGameActive) {
return;
}
// Check if car is already jumping
if (!car.jumping) {
car.jump();
// Check if car is over a road line
var overLine = false;
for (var i = 0; i < lines.length; i++) {
// Check if the car intersects with any vertical road line
if (lines[i].active && car.intersects(lines[i])) {
overLine = true;
break;
}
}
// Score or penalty based on jump
if (overLine) {
// Penalty for jumping over a line
score = Math.max(0, score - 10);
// Show penalty effect
var effect = scoreEffects[effectIndex];
effectIndex = (effectIndex + 1) % scoreEffects.length;
effect.show(car.x, car.y - 50, false);
// Show tap feedback effect (red for penalty)
var tapEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
tapEffect.show(x, y, false);
// Add multiple red effects for stronger visual feedback
for (var j = 0; j < 2; j++) {
var extraEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
extraEffect.show(x + (Math.random() * 100 - 50), y + (Math.random() * 100 - 50), false);
}
// Flash screen red for bad tap
LK.effects.flashScreen(0xff0000, 300);
} else {
// Points for jumping on empty road
var pointsGained = 20 * difficulty;
score += pointsGained;
lastJumpScore = pointsGained;
// Show score effect
var effect = scoreEffects[effectIndex];
effectIndex = (effectIndex + 1) % scoreEffects.length;
effect.show(car.x, car.y - 50, true);
// Show tap feedback effect (green for points)
var tapEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
tapEffect.show(x, y, true);
// Add multiple green effects for stronger visual feedback
for (var j = 0; j < 2; j++) {
var extraEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
extraEffect.show(x + (Math.random() * 100 - 50), y + (Math.random() * 100 - 50), true);
}
}
// Update score display
scoreText.setText(Math.round(score));
LK.setScore(Math.round(score));
}
};
// Start background music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});
// Game update function
game.update = function () {
if (!isGameActive) {
return;
}
// Update car
car.update();
// Update all lines
for (var i = lines.length - 1; i >= 0; i--) {
lines[i].update();
// We don't penalize for just passing over a line anymore
// Only interactions happen when player taps
if (!lines[i].scored && lines[i].lastY < car.y && lines[i].y >= car.y) {
lines[i].scored = true; // Mark as scored
}
// Remove inactive lines
if (!lines[i].active) {
lines[i].destroy();
lines.splice(i, 1);
}
}
// Update all buildings
for (var i = 0; i < buildings.length; i++) {
buildings[i].speed = 5 * speedMultiplier;
buildings[i].update();
}
// Update all trees
for (var i = 0; i < trees.length; i++) {
trees[i].speed = 7 * speedMultiplier;
trees[i].update();
}
// Update all stores
for (var i = 0; i < stores.length; i++) {
stores[i].speed = 5 * speedMultiplier;
stores[i].update();
}
// Update all street lights
for (var i = 0; i < streetLights.length; i++) {
streetLights[i].speed = 7 * speedMultiplier;
streetLights[i].update();
}
// Create new lines at intervals - always come from center of the screen
if (LK.ticks % Math.floor(lineSpawnRate / speedMultiplier) === 0) {
var line = new Line();
// Position line at the center of the screen
line.x = 2048 / 2; // Center X coordinate
line.y = -50;
line.speed = 15 * speedMultiplier;
lines.push(line);
game.addChild(line);
// Ensure car is always rendered on top of lines
game.setChildIndex(car, game.children.length - 1);
}
// Increase difficulty over time
if (LK.ticks % 600 === 0) {
// Every 10 seconds
difficulty += 0.1;
// Speed up game after 500 points, but not too much, and slow down a bit after 800
if (score > 800) {
speedMultiplier = 1 + (difficulty - 1) * 1.0; // Reduced speed multiplier after 800 points
lineSpawnRate = Math.max(15, 40 - difficulty * 3); // Reduced line spawn rate
} else if (score > 500) {
speedMultiplier = 1 + (difficulty - 1) * 1.1; // Moderate speed increase after 500 points
lineSpawnRate = Math.max(12, 40 - difficulty * 4); // Moderate increase in line spawn rate
} else {
speedMultiplier = 1 + (difficulty - 1) * 0.5;
lineSpawnRate = Math.max(15, 60 - difficulty * 5);
}
}
// Check for game over condition
if (score < 0) {
isGameActive = false;
LK.showGameOver();
}
// Check for win condition (1000 points to win)
if (score >= 1000) {
isGameActive = false;
LK.showYouWin();
}
};