/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Badge = Container.expand(function () { var self = Container.call(this); // Create badge icon var badgeGraphic = self.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80, tint: 0xFF0000 // Red color for badge }); // Add a label to indicate what the badge does var labelText = self.addChild(new Text2("SPEED BOOST", { size: 20, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3 })); labelText.anchor.set(0.5, 0.5); labelText.y = 60; // Add a timer display var timerText = self.addChild(new Text2("", { size: 24, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3 })); timerText.anchor.set(0.5, 0.5); timerText.y = -60; timerText.visible = false; self.earned = false; // Whether badge has been earned self.active = false; self.timerSeconds = 30; self.pulseDirection = 1; self.originalScale = 1; self.visible = false; // Animation for when badge is first earned self.showEarned = function () { self.earned = true; self.visible = true; self.scale.set(0.1); self.alpha = 0; // Appear with a bounce effect tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.elasticOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 200 }); } }); }; // Activate the badge power self.activate = function () { if (self.earned && !self.active) { self.active = true; speedMultiplier = 0.5; // Slow down the road timerText.visible = true; // Visual feedback - flash effect LK.effects.flashScreen(0xFFD700, 300); // Start countdown self.timerSeconds = 30; timerText.setText(self.timerSeconds); // Start timer to count down and update display var timerInterval = LK.setInterval(function () { self.timerSeconds--; timerText.setText(self.timerSeconds); if (self.timerSeconds <= 0) { LK.clearInterval(timerInterval); } }, 1000); // Reset after 30 seconds LK.setTimeout(function () { speedMultiplier = 1; // Reset speed after 30 seconds self.active = false; timerText.visible = false; // Flash when effect ends LK.effects.flashScreen(0x87CEEB, 300); }, 30000); } }; // Update to pulse the badge when available self.update = function () { if (self.earned && !self.active) { // Pulse effect when ready to use if (self.pulseDirection > 0) { self.scale.x += 0.005; self.scale.y += 0.005; if (self.scale.x >= 1.1) { self.pulseDirection = -1; } } else { self.scale.x -= 0.005; self.scale.y -= 0.005; if (self.scale.x <= 0.9) { self.pulseDirection = 1; } } } else if (self.active) { // Rotate slightly when active self.rotation += 0.01; } }; return self; }); 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; 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); } // Check current environment for building appearance if (environment && environment.current === environment.BEACH) { // Beach resort buildings - lighter colors, more variation var r = Math.floor(Math.random() * 40) + 215; // Bright beige/white var g = Math.floor(Math.random() * 40) + 215; var b = Math.floor(Math.random() * 40) + 190; buildingGraphics.tint = r << 16 | g << 8 | b; buildingGraphics.height = Math.random() * 400 + 300; // Beach buildings are shorter buildingGraphics.width = Math.random() * 150 + 100; // But wider } else if (environment && environment.current === environment.JAPAN) { // Traditional Japanese buildings - pagoda style var shade = Math.floor(Math.random() * 40) + 40; // Dark base // Add red tint for some buildings to simulate temples/shrines if (Math.random() > 0.5) { buildingGraphics.tint = shade + 150 << 16 | shade << 8 | shade; buildingGraphics.height = Math.random() * 500 + 300; // Slightly shorter } else { buildingGraphics.tint = shade << 16 | shade << 8 | shade; buildingGraphics.height = Math.random() * 400 + 200; // Low traditional houses } buildingGraphics.width = Math.random() * 120 + 100; } else { // Default city buildings 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: 0xFFFF00 }); 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: 0xFFFF00 }); rightHeadlight.x = 60; rightHeadlight.y = -140; // Add light reflection gradient in front of the car // Left headlight reflection var leftReflection = self.addChild(new Container()); for (var i = 0; i < 5; i++) { var reflectionSegment = leftReflection.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.5, width: 30 - i * 3, height: 25 - i * 3, tint: 0xFFFF00, alpha: 0.4 - i * 0.07 }); reflectionSegment.y = -i * 15; } leftReflection.x = -60; leftReflection.y = -170; // Right headlight reflection var rightReflection = self.addChild(new Container()); for (var i = 0; i < 5; i++) { var reflectionSegment = rightReflection.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.5, width: 30 - i * 3, height: 25 - i * 3, tint: 0xFFFF00, alpha: 0.4 - i * 0.07 }); reflectionSegment.y = -i * 15; } rightReflection.x = 60; rightReflection.y = -170; 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(); // Enhanced neon effect during jump - random bright color var randomNeonColor = Math.random() > 0.5 ? 0xFFFFFF : 0x00FFFF; // Create a glowing neon effect tween(carGraphics, { tint: randomNeonColor, scaleX: 1.2, scaleY: 1.2 }, { duration: 150, onFinish: function onFinish() { // Return to current neon color after the flash tween(carGraphics, { tint: self.neonColors[self.currentColorIndex], scaleX: 1.0, scaleY: 1.0 }, { duration: 450 }); } }); // 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(); }; // Set up neon color cycling properties self.neonColors = [0x3366CC, // Blue 0xFF00FF, // Magenta 0x00FFFF, // Cyan 0x33CC33, // Green 0xFF6600, // Orange 0xFFFF00, // Yellow 0xFF3366, // Pink 0x9900FF // Purple ]; self.currentColorIndex = 0; self.colorTransitionTime = 0; self.colorTransitionDuration = 60; // About 1 second at 60fps - faster color cycling 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; // Animate reflection gradient // Loop through each reflection segment container's children for (var i = 0; i < 5; i++) { if (leftReflection.children[i]) { leftReflection.children[i].alpha = (0.4 - i * 0.07) * (0.7 + Math.random() * 0.3); } if (rightReflection.children[i]) { rightReflection.children[i].alpha = (0.4 - i * 0.07) * (0.7 + Math.random() * 0.3); } } } // Update neon color effect self.colorTransitionTime++; if (self.colorTransitionTime >= self.colorTransitionDuration) { self.colorTransitionTime = 0; self.currentColorIndex = (self.currentColorIndex + 1) % self.neonColors.length; // Start transition to the next color tween(carGraphics, { tint: self.neonColors[self.currentColorIndex] }, { duration: 1000, easing: tween.easeInOut }); } }; return self; }); var Coin = Container.expand(function () { var self = Container.call(this); // Create the coin graphic var coinGraphic = self.attachAsset('scoreEffect', { anchorX: 0.5, anchorY: 0.5, width: 40, height: 40, tint: 0xFFFF00 // Bright yellow color for coins }); // Initialize properties self.active = false; // Animate coin from source to destination self.animate = function (startX, startY, destX, destY) { self.active = true; self.visible = true; self.x = startX + (Math.random() * 120 - 60); self.y = startY + (Math.random() * 120 - 60); self.alpha = 0; self.rotation = Math.random() * Math.PI; self.scale.set(1); // Duration and timing variables var duration = 500 + Math.random() * 200; // First make the coin appear with a quick flash tween(self, { alpha: 1.0, scaleX: 1.2, scaleY: 1.2 }, { duration: 60, onFinish: function onFinish() { // Add a spin to the coins as they move self.rotation = Math.random() * Math.PI * 2; // Small initial burst to create a more dynamic feel tween(self, { y: self.y - 50 - Math.random() * 70, x: self.x + (Math.random() * 100 - 50), alpha: 1.0, rotation: self.rotation + Math.PI * (Math.random() > 0.5 ? 1 : -1) }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { // Now move directly to the score with arc path tween(self, { x: destX, y: destY, scaleX: 0.3, scaleY: 0.3, rotation: self.rotation + Math.PI * 2, alpha: 0 }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { // Flash the score text tween(scoreText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, onFinish: function onFinish() { tween(scoreText, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); // Reset coin self.active = false; self.visible = false; } }); } }); } }); }; return self; }); var Environment = Container.expand(function () { var self = Container.call(this); // Environment types self.CITY = 0; self.BEACH = 1; self.JAPAN = 2; // Current environment self.current = self.CITY; // Assets for each environment self.roadColors = [0x444444, 0xD2B48C, 0x8B4513]; // City, Beach, Japan self.sidewalkColors = [0x999999, 0xF5DEB3, 0x7D8570]; // City, Beach, Japan self.skyColors = [0x87CEEB, 0x87CEEB, 0xE0FFFF]; // City, Beach, Japan // Change environment based on score self.update = function (score) { var newEnvironment = self.current; // Determine environment based on score if (score >= 700) { newEnvironment = self.JAPAN; } else if (score >= 350) { newEnvironment = self.BEACH; } else { newEnvironment = self.CITY; } // Only update if environment changed if (newEnvironment !== self.current) { self.current = newEnvironment; return true; } return false; }; // Apply environment changes self.apply = function (road, leftSidewalk, rightSidewalk, game) { // Update colors based on current environment road.tint = self.roadColors[self.current]; leftSidewalk.tint = self.sidewalkColors[self.current]; rightSidewalk.tint = self.sidewalkColors[self.current]; // Update sky color game.setBackgroundColor(self.skyColors[self.current]); // Apply special transition effect LK.effects.flashScreen(0xFFFFFF, 500); // Return message to display for level change if (self.current === self.BEACH) { return "BEACH LEVEL UNLOCKED!"; } else if (self.current === self.JAPAN) { return "JAPAN LEVEL UNLOCKED!"; } return ""; }; 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; // Store types based on environment var storeTypes; if (environment && environment.current === environment.BEACH) { // Beach stores - surf shops, beach bars, cabanas storeTypes = [{ building: 0xF0F8FF, // AliceBlue sign: 0x00BFFF, // DeepSkyBlue window: 0xE0FFFF // LightCyan }, { building: 0xFFEFD5, // PapayaWhip sign: 0xFF6347, // Tomato window: 0xFFDAB9 // PeachPuff }, { building: 0x40E0D0, // Turquoise sign: 0xFFFFFF, // White window: 0xAFEEEE // PaleTurquoise }, { building: 0xF5DEB3, // Wheat sign: 0x1E90FF, // DodgerBlue window: 0xFFF5EE // SeaShell }]; } else if (environment && environment.current === environment.JAPAN) { // Japanese stores - traditional shops, ramen stands, markets storeTypes = [{ building: 0x8B4513, // SaddleBrown (wood) sign: 0xFF0000, // Red window: 0xFFD700 // Gold }, { building: 0xFFF5EE, // SeaShell (white paper walls) sign: 0x000000, // Black window: 0xFFF8DC // Cornsilk }, { building: 0x2F4F4F, // DarkSlateGray sign: 0xDC143C, // Crimson window: 0xF0FFF0 // Honeydew }, { building: 0xCD853F, // Peru (wood) sign: 0x4682B4, // SteelBlue window: 0xF5F5DC // Beige }]; } else { // Default city stores 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 }); // Trigger coin effects separately - they're now handled in the game code } 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); // Check current environment for tree appearance if (environment && environment.current === environment.BEACH) { // Palm trees for beach trunkAsset.tint = 0xA0522D; // Brown trunk for palm trees trunkAsset.height *= 1.5; // Make palm tree trunks much taller trunkAsset.width *= 0.7; // Make palm tree trunks thinner // Change foliage shape and color for palm trees foliageAsset1.width = 200; // Wider palm fronds foliageAsset1.height = 120; // Shorter palm fronds foliageAsset1.tint = 0xFFFF00; // Yellow palm fronds foliageAsset2.width = 220; // Even wider top fronds foliageAsset2.height = 100; // Shorter top fronds foliageAsset2.tint = 0xFFFF80; // Lighter yellow for highlights // Position foliage to look like palm fronds foliage1.y = -150; // Raise fronds higher for palm tree appearance foliage2.y = -170; // Raise top fronds even higher } else if (environment && environment.current === environment.JAPAN) { // Cherry blossom trees (sakura) trunkAsset.tint = 0x8B4513; // Brown trunk // More vibrant pink cherry blossoms foliageAsset1.width = 170; // Wider, fuller cherry blossom shape foliageAsset1.height = 170; // Rounder for blossom shape // Pure pink color for cherry blossoms foliageAsset1.tint = 0xFF00FF; // Bright pink cherry blossoms // Lighter pink for highlights foliageAsset2.width = 140; // Slightly smaller top layer foliageAsset2.height = 140; // Rounder for blossom shape foliageAsset2.tint = 0xFF80FF; // Lighter pink highlights // Position foliage to look more like cherry blossom foliage1.y = -100; // Position for traditional cherry blossom tree shape foliage2.y = -120; // Position for traditional cherry blossom tree shape } else { // Default city trees 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 coins = []; // Pool of coin objects var effectIndex = 0; var tapEffectIndex = 0; var coinIndex = 0; var lastJumpScore = 0; var badge; var environment; var levelText; var levelTextTimer = 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); // Initialize level transition text levelText = new Text2('', { size: 100, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 8, fontWeight: 'bold' }); levelText.anchor.set(0.5, 0.5); levelText.visible = false; LK.gui.center.addChild(levelText); // Initialize environment environment = new Environment(); // Add game instructions var instructionsText = new Text2('Tap when no lines\nAvoid tapping on lines\nScore 1000 to win', { size: 70, 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 - further from road than buildings tree.x = (2048 - road.width) / 2 - 250 - Math.random() * 60; } else { // Right side trees - further from road than buildings tree.x = 2048 - (2048 - road.width) / 2 + 250 + 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 badge badge = new Badge(); badge.x = 2048 - 150; // Position on the right side of screen badge.y = 2732 / 2; // Position in the middle vertically badge.visible = false; // Hidden until earned badge.scale.set(0); // Start with zero scale game.addChild(badge); 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); } // Create coin effects pool for (var i = 0; i < 15; i++) { var coin = new Coin(); coin.visible = false; coin.active = false; coins.push(coin); game.addChild(coin); } // Game click handler game.down = function (x, y, obj) { if (!isGameActive) { return; } // Check if we clicked on the badge if (badge.earned && !badge.active && x >= badge.x - badge.width / 2 && x <= badge.x + badge.width / 2 && y >= badge.y - badge.height / 2 && y <= badge.y + badge.height / 2) { badge.activate(); 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); } // Create coin animation effects var coinCount = 3 + Math.floor(Math.random() * 3); // 3-5 coins for (var i = 0; i < coinCount; i++) { // Get a coin from the pool var coin = coins[coinIndex]; coinIndex = (coinIndex + 1) % coins.length; // Only animate if not currently active if (!coin.active) { // Set destination coordinates (score text position) var destX = 2048 / 2; // Center of screen where the score is var destY = 50; // Near the top where the score is // Animate the coin coin.animate(x, y, destX, destY); } } } // 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 badge badge.update(); // 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 = 5 * speedMultiplier; // Changed from 7 to 5 to match building speed 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 = 5 * speedMultiplier; // Changed from 7 to 5 to match building speed 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) { // Cap the speedMultiplier to prevent game from becoming too fast speedMultiplier = Math.min(1.6, 1 + (difficulty - 1) * 0.6); // Further reduced speed cap lineSpawnRate = Math.max(30, 45 - difficulty * 1.5); // More forgiving line spawn rate } else if (score > 500) { // Cap the speedMultiplier to prevent game from becoming too fast speedMultiplier = Math.min(1.5, 1 + (difficulty - 1) * 0.7); // Lower speed cap lineSpawnRate = Math.max(25, 40 - difficulty * 2); // More moderate increase in line spawn rate } else { speedMultiplier = 1 + (difficulty - 1) * 0.5; lineSpawnRate = Math.max(15, 60 - difficulty * 5); } } // Check if score has reached 500 to give badge if (score >= 500 && !badge.earned) { badge.showEarned(); // Create notification text var badgeNotification = new Text2("SPEED BOOST UNLOCKED!", { size: 80, fill: 0xFFD700, stroke: 0x000000, strokeThickness: 6 }); badgeNotification.anchor.set(0.5, 0.5); LK.gui.center.addChild(badgeNotification); // Animate notification tween(badgeNotification, { alpha: 0, y: badgeNotification.y - 100 }, { duration: 2000, onFinish: function onFinish() { badgeNotification.destroy(); } }); } // Handle environment transitions based on score if (environment.update(Math.round(score))) { var message = environment.apply(road, leftSidewalk, rightSidewalk, game); // Show level transition message if (message) { levelText.setText(message); levelText.visible = true; levelTextTimer = 120; // Show for 2 seconds (120 frames) // Update car and environment elements based on the theme if (environment.current === environment.BEACH) { // Beach theme colors for buildings and objects for (var i = 0; i < buildings.length; i++) { var building = buildings[i]; if (building.children && building.children[0]) { var shade = Math.floor(Math.random() * 30) + 220; // Lighter buildings for beach building.children[0].tint = shade << 16 | shade << 8 | shade; } } } else if (environment.current === environment.JAPAN) { // Japan theme colors for buildings and objects for (var i = 0; i < buildings.length; i++) { var building = buildings[i]; if (building.children && building.children[0]) { // Reddish pagoda style buildings var red = Math.floor(Math.random() * 55) + 200; var green = Math.floor(red * 0.5); var blue = Math.floor(green * 0.5); building.children[0].tint = red << 16 | green << 8 | blue; } } } } } // Update level transition text timer if (levelTextTimer > 0) { levelTextTimer--; if (levelTextTimer === 0) { levelText.visible = false; } } // Check for game over condition if (score < 0) { isGameActive = false; LK.showGameOver(); } // Check for win condition (1000 points to win) if (score >= 1000) { // Only show win screen once if (isGameActive) { isGameActive = false; // Flash screen with celebration colors LK.effects.flashScreen(0xFFD700, 400); // Gold flash // Create coin shower effect for winning celebration LK.setTimeout(function () { for (var i = 0; i < 40; i++) { var coin = coins[Math.floor(Math.random() * coins.length)]; if (!coin.active) { // Random positions for coins var startX = Math.random() * 2048; var startY = -100; var destX = Math.random() * 2048; var destY = Math.random() * 2732; coin.animate(startX, startY, destX, destY); } } // Show win screen immediately after coin effect starts LK.showYouWin(); }, 300); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Badge = Container.expand(function () {
var self = Container.call(this);
// Create badge icon
var badgeGraphic = self.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80,
tint: 0xFF0000 // Red color for badge
});
// Add a label to indicate what the badge does
var labelText = self.addChild(new Text2("SPEED BOOST", {
size: 20,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3
}));
labelText.anchor.set(0.5, 0.5);
labelText.y = 60;
// Add a timer display
var timerText = self.addChild(new Text2("", {
size: 24,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3
}));
timerText.anchor.set(0.5, 0.5);
timerText.y = -60;
timerText.visible = false;
self.earned = false; // Whether badge has been earned
self.active = false;
self.timerSeconds = 30;
self.pulseDirection = 1;
self.originalScale = 1;
self.visible = false;
// Animation for when badge is first earned
self.showEarned = function () {
self.earned = true;
self.visible = true;
self.scale.set(0.1);
self.alpha = 0;
// Appear with a bounce effect
tween(self, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
};
// Activate the badge power
self.activate = function () {
if (self.earned && !self.active) {
self.active = true;
speedMultiplier = 0.5; // Slow down the road
timerText.visible = true;
// Visual feedback - flash effect
LK.effects.flashScreen(0xFFD700, 300);
// Start countdown
self.timerSeconds = 30;
timerText.setText(self.timerSeconds);
// Start timer to count down and update display
var timerInterval = LK.setInterval(function () {
self.timerSeconds--;
timerText.setText(self.timerSeconds);
if (self.timerSeconds <= 0) {
LK.clearInterval(timerInterval);
}
}, 1000);
// Reset after 30 seconds
LK.setTimeout(function () {
speedMultiplier = 1; // Reset speed after 30 seconds
self.active = false;
timerText.visible = false;
// Flash when effect ends
LK.effects.flashScreen(0x87CEEB, 300);
}, 30000);
}
};
// Update to pulse the badge when available
self.update = function () {
if (self.earned && !self.active) {
// Pulse effect when ready to use
if (self.pulseDirection > 0) {
self.scale.x += 0.005;
self.scale.y += 0.005;
if (self.scale.x >= 1.1) {
self.pulseDirection = -1;
}
} else {
self.scale.x -= 0.005;
self.scale.y -= 0.005;
if (self.scale.x <= 0.9) {
self.pulseDirection = 1;
}
}
} else if (self.active) {
// Rotate slightly when active
self.rotation += 0.01;
}
};
return self;
});
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;
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);
}
// Check current environment for building appearance
if (environment && environment.current === environment.BEACH) {
// Beach resort buildings - lighter colors, more variation
var r = Math.floor(Math.random() * 40) + 215; // Bright beige/white
var g = Math.floor(Math.random() * 40) + 215;
var b = Math.floor(Math.random() * 40) + 190;
buildingGraphics.tint = r << 16 | g << 8 | b;
buildingGraphics.height = Math.random() * 400 + 300; // Beach buildings are shorter
buildingGraphics.width = Math.random() * 150 + 100; // But wider
} else if (environment && environment.current === environment.JAPAN) {
// Traditional Japanese buildings - pagoda style
var shade = Math.floor(Math.random() * 40) + 40; // Dark base
// Add red tint for some buildings to simulate temples/shrines
if (Math.random() > 0.5) {
buildingGraphics.tint = shade + 150 << 16 | shade << 8 | shade;
buildingGraphics.height = Math.random() * 500 + 300; // Slightly shorter
} else {
buildingGraphics.tint = shade << 16 | shade << 8 | shade;
buildingGraphics.height = Math.random() * 400 + 200; // Low traditional houses
}
buildingGraphics.width = Math.random() * 120 + 100;
} else {
// Default city buildings
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: 0xFFFF00
});
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: 0xFFFF00
});
rightHeadlight.x = 60;
rightHeadlight.y = -140;
// Add light reflection gradient in front of the car
// Left headlight reflection
var leftReflection = self.addChild(new Container());
for (var i = 0; i < 5; i++) {
var reflectionSegment = leftReflection.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 30 - i * 3,
height: 25 - i * 3,
tint: 0xFFFF00,
alpha: 0.4 - i * 0.07
});
reflectionSegment.y = -i * 15;
}
leftReflection.x = -60;
leftReflection.y = -170;
// Right headlight reflection
var rightReflection = self.addChild(new Container());
for (var i = 0; i < 5; i++) {
var reflectionSegment = rightReflection.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 30 - i * 3,
height: 25 - i * 3,
tint: 0xFFFF00,
alpha: 0.4 - i * 0.07
});
reflectionSegment.y = -i * 15;
}
rightReflection.x = 60;
rightReflection.y = -170;
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();
// Enhanced neon effect during jump - random bright color
var randomNeonColor = Math.random() > 0.5 ? 0xFFFFFF : 0x00FFFF;
// Create a glowing neon effect
tween(carGraphics, {
tint: randomNeonColor,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
onFinish: function onFinish() {
// Return to current neon color after the flash
tween(carGraphics, {
tint: self.neonColors[self.currentColorIndex],
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 450
});
}
});
// 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();
};
// Set up neon color cycling properties
self.neonColors = [0x3366CC,
// Blue
0xFF00FF,
// Magenta
0x00FFFF,
// Cyan
0x33CC33,
// Green
0xFF6600,
// Orange
0xFFFF00,
// Yellow
0xFF3366,
// Pink
0x9900FF // Purple
];
self.currentColorIndex = 0;
self.colorTransitionTime = 0;
self.colorTransitionDuration = 60; // About 1 second at 60fps - faster color cycling
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;
// Animate reflection gradient
// Loop through each reflection segment container's children
for (var i = 0; i < 5; i++) {
if (leftReflection.children[i]) {
leftReflection.children[i].alpha = (0.4 - i * 0.07) * (0.7 + Math.random() * 0.3);
}
if (rightReflection.children[i]) {
rightReflection.children[i].alpha = (0.4 - i * 0.07) * (0.7 + Math.random() * 0.3);
}
}
}
// Update neon color effect
self.colorTransitionTime++;
if (self.colorTransitionTime >= self.colorTransitionDuration) {
self.colorTransitionTime = 0;
self.currentColorIndex = (self.currentColorIndex + 1) % self.neonColors.length;
// Start transition to the next color
tween(carGraphics, {
tint: self.neonColors[self.currentColorIndex]
}, {
duration: 1000,
easing: tween.easeInOut
});
}
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
// Create the coin graphic
var coinGraphic = self.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0xFFFF00 // Bright yellow color for coins
});
// Initialize properties
self.active = false;
// Animate coin from source to destination
self.animate = function (startX, startY, destX, destY) {
self.active = true;
self.visible = true;
self.x = startX + (Math.random() * 120 - 60);
self.y = startY + (Math.random() * 120 - 60);
self.alpha = 0;
self.rotation = Math.random() * Math.PI;
self.scale.set(1);
// Duration and timing variables
var duration = 500 + Math.random() * 200;
// First make the coin appear with a quick flash
tween(self, {
alpha: 1.0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 60,
onFinish: function onFinish() {
// Add a spin to the coins as they move
self.rotation = Math.random() * Math.PI * 2;
// Small initial burst to create a more dynamic feel
tween(self, {
y: self.y - 50 - Math.random() * 70,
x: self.x + (Math.random() * 100 - 50),
alpha: 1.0,
rotation: self.rotation + Math.PI * (Math.random() > 0.5 ? 1 : -1)
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Now move directly to the score with arc path
tween(self, {
x: destX,
y: destY,
scaleX: 0.3,
scaleY: 0.3,
rotation: self.rotation + Math.PI * 2,
alpha: 0
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Flash the score text
tween(scoreText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(scoreText, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
// Reset coin
self.active = false;
self.visible = false;
}
});
}
});
}
});
};
return self;
});
var Environment = Container.expand(function () {
var self = Container.call(this);
// Environment types
self.CITY = 0;
self.BEACH = 1;
self.JAPAN = 2;
// Current environment
self.current = self.CITY;
// Assets for each environment
self.roadColors = [0x444444, 0xD2B48C, 0x8B4513]; // City, Beach, Japan
self.sidewalkColors = [0x999999, 0xF5DEB3, 0x7D8570]; // City, Beach, Japan
self.skyColors = [0x87CEEB, 0x87CEEB, 0xE0FFFF]; // City, Beach, Japan
// Change environment based on score
self.update = function (score) {
var newEnvironment = self.current;
// Determine environment based on score
if (score >= 700) {
newEnvironment = self.JAPAN;
} else if (score >= 350) {
newEnvironment = self.BEACH;
} else {
newEnvironment = self.CITY;
}
// Only update if environment changed
if (newEnvironment !== self.current) {
self.current = newEnvironment;
return true;
}
return false;
};
// Apply environment changes
self.apply = function (road, leftSidewalk, rightSidewalk, game) {
// Update colors based on current environment
road.tint = self.roadColors[self.current];
leftSidewalk.tint = self.sidewalkColors[self.current];
rightSidewalk.tint = self.sidewalkColors[self.current];
// Update sky color
game.setBackgroundColor(self.skyColors[self.current]);
// Apply special transition effect
LK.effects.flashScreen(0xFFFFFF, 500);
// Return message to display for level change
if (self.current === self.BEACH) {
return "BEACH LEVEL UNLOCKED!";
} else if (self.current === self.JAPAN) {
return "JAPAN LEVEL UNLOCKED!";
}
return "";
};
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;
// Store types based on environment
var storeTypes;
if (environment && environment.current === environment.BEACH) {
// Beach stores - surf shops, beach bars, cabanas
storeTypes = [{
building: 0xF0F8FF,
// AliceBlue
sign: 0x00BFFF,
// DeepSkyBlue
window: 0xE0FFFF // LightCyan
}, {
building: 0xFFEFD5,
// PapayaWhip
sign: 0xFF6347,
// Tomato
window: 0xFFDAB9 // PeachPuff
}, {
building: 0x40E0D0,
// Turquoise
sign: 0xFFFFFF,
// White
window: 0xAFEEEE // PaleTurquoise
}, {
building: 0xF5DEB3,
// Wheat
sign: 0x1E90FF,
// DodgerBlue
window: 0xFFF5EE // SeaShell
}];
} else if (environment && environment.current === environment.JAPAN) {
// Japanese stores - traditional shops, ramen stands, markets
storeTypes = [{
building: 0x8B4513,
// SaddleBrown (wood)
sign: 0xFF0000,
// Red
window: 0xFFD700 // Gold
}, {
building: 0xFFF5EE,
// SeaShell (white paper walls)
sign: 0x000000,
// Black
window: 0xFFF8DC // Cornsilk
}, {
building: 0x2F4F4F,
// DarkSlateGray
sign: 0xDC143C,
// Crimson
window: 0xF0FFF0 // Honeydew
}, {
building: 0xCD853F,
// Peru (wood)
sign: 0x4682B4,
// SteelBlue
window: 0xF5F5DC // Beige
}];
} else {
// Default city stores
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
});
// Trigger coin effects separately - they're now handled in the game code
} 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);
// Check current environment for tree appearance
if (environment && environment.current === environment.BEACH) {
// Palm trees for beach
trunkAsset.tint = 0xA0522D; // Brown trunk for palm trees
trunkAsset.height *= 1.5; // Make palm tree trunks much taller
trunkAsset.width *= 0.7; // Make palm tree trunks thinner
// Change foliage shape and color for palm trees
foliageAsset1.width = 200; // Wider palm fronds
foliageAsset1.height = 120; // Shorter palm fronds
foliageAsset1.tint = 0xFFFF00; // Yellow palm fronds
foliageAsset2.width = 220; // Even wider top fronds
foliageAsset2.height = 100; // Shorter top fronds
foliageAsset2.tint = 0xFFFF80; // Lighter yellow for highlights
// Position foliage to look like palm fronds
foliage1.y = -150; // Raise fronds higher for palm tree appearance
foliage2.y = -170; // Raise top fronds even higher
} else if (environment && environment.current === environment.JAPAN) {
// Cherry blossom trees (sakura)
trunkAsset.tint = 0x8B4513; // Brown trunk
// More vibrant pink cherry blossoms
foliageAsset1.width = 170; // Wider, fuller cherry blossom shape
foliageAsset1.height = 170; // Rounder for blossom shape
// Pure pink color for cherry blossoms
foliageAsset1.tint = 0xFF00FF; // Bright pink cherry blossoms
// Lighter pink for highlights
foliageAsset2.width = 140; // Slightly smaller top layer
foliageAsset2.height = 140; // Rounder for blossom shape
foliageAsset2.tint = 0xFF80FF; // Lighter pink highlights
// Position foliage to look more like cherry blossom
foliage1.y = -100; // Position for traditional cherry blossom tree shape
foliage2.y = -120; // Position for traditional cherry blossom tree shape
} else {
// Default city trees
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 coins = []; // Pool of coin objects
var effectIndex = 0;
var tapEffectIndex = 0;
var coinIndex = 0;
var lastJumpScore = 0;
var badge;
var environment;
var levelText;
var levelTextTimer = 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);
// Initialize level transition text
levelText = new Text2('', {
size: 100,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 8,
fontWeight: 'bold'
});
levelText.anchor.set(0.5, 0.5);
levelText.visible = false;
LK.gui.center.addChild(levelText);
// Initialize environment
environment = new Environment();
// Add game instructions
var instructionsText = new Text2('Tap when no lines\nAvoid tapping on lines\nScore 1000 to win', {
size: 70,
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 - further from road than buildings
tree.x = (2048 - road.width) / 2 - 250 - Math.random() * 60;
} else {
// Right side trees - further from road than buildings
tree.x = 2048 - (2048 - road.width) / 2 + 250 + 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 badge
badge = new Badge();
badge.x = 2048 - 150; // Position on the right side of screen
badge.y = 2732 / 2; // Position in the middle vertically
badge.visible = false; // Hidden until earned
badge.scale.set(0); // Start with zero scale
game.addChild(badge);
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);
}
// Create coin effects pool
for (var i = 0; i < 15; i++) {
var coin = new Coin();
coin.visible = false;
coin.active = false;
coins.push(coin);
game.addChild(coin);
}
// Game click handler
game.down = function (x, y, obj) {
if (!isGameActive) {
return;
}
// Check if we clicked on the badge
if (badge.earned && !badge.active && x >= badge.x - badge.width / 2 && x <= badge.x + badge.width / 2 && y >= badge.y - badge.height / 2 && y <= badge.y + badge.height / 2) {
badge.activate();
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);
}
// Create coin animation effects
var coinCount = 3 + Math.floor(Math.random() * 3); // 3-5 coins
for (var i = 0; i < coinCount; i++) {
// Get a coin from the pool
var coin = coins[coinIndex];
coinIndex = (coinIndex + 1) % coins.length;
// Only animate if not currently active
if (!coin.active) {
// Set destination coordinates (score text position)
var destX = 2048 / 2; // Center of screen where the score is
var destY = 50; // Near the top where the score is
// Animate the coin
coin.animate(x, y, destX, destY);
}
}
}
// 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 badge
badge.update();
// 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 = 5 * speedMultiplier; // Changed from 7 to 5 to match building speed
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 = 5 * speedMultiplier; // Changed from 7 to 5 to match building speed
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) {
// Cap the speedMultiplier to prevent game from becoming too fast
speedMultiplier = Math.min(1.6, 1 + (difficulty - 1) * 0.6); // Further reduced speed cap
lineSpawnRate = Math.max(30, 45 - difficulty * 1.5); // More forgiving line spawn rate
} else if (score > 500) {
// Cap the speedMultiplier to prevent game from becoming too fast
speedMultiplier = Math.min(1.5, 1 + (difficulty - 1) * 0.7); // Lower speed cap
lineSpawnRate = Math.max(25, 40 - difficulty * 2); // More moderate increase in line spawn rate
} else {
speedMultiplier = 1 + (difficulty - 1) * 0.5;
lineSpawnRate = Math.max(15, 60 - difficulty * 5);
}
}
// Check if score has reached 500 to give badge
if (score >= 500 && !badge.earned) {
badge.showEarned();
// Create notification text
var badgeNotification = new Text2("SPEED BOOST UNLOCKED!", {
size: 80,
fill: 0xFFD700,
stroke: 0x000000,
strokeThickness: 6
});
badgeNotification.anchor.set(0.5, 0.5);
LK.gui.center.addChild(badgeNotification);
// Animate notification
tween(badgeNotification, {
alpha: 0,
y: badgeNotification.y - 100
}, {
duration: 2000,
onFinish: function onFinish() {
badgeNotification.destroy();
}
});
}
// Handle environment transitions based on score
if (environment.update(Math.round(score))) {
var message = environment.apply(road, leftSidewalk, rightSidewalk, game);
// Show level transition message
if (message) {
levelText.setText(message);
levelText.visible = true;
levelTextTimer = 120; // Show for 2 seconds (120 frames)
// Update car and environment elements based on the theme
if (environment.current === environment.BEACH) {
// Beach theme colors for buildings and objects
for (var i = 0; i < buildings.length; i++) {
var building = buildings[i];
if (building.children && building.children[0]) {
var shade = Math.floor(Math.random() * 30) + 220; // Lighter buildings for beach
building.children[0].tint = shade << 16 | shade << 8 | shade;
}
}
} else if (environment.current === environment.JAPAN) {
// Japan theme colors for buildings and objects
for (var i = 0; i < buildings.length; i++) {
var building = buildings[i];
if (building.children && building.children[0]) {
// Reddish pagoda style buildings
var red = Math.floor(Math.random() * 55) + 200;
var green = Math.floor(red * 0.5);
var blue = Math.floor(green * 0.5);
building.children[0].tint = red << 16 | green << 8 | blue;
}
}
}
}
}
// Update level transition text timer
if (levelTextTimer > 0) {
levelTextTimer--;
if (levelTextTimer === 0) {
levelText.visible = false;
}
}
// Check for game over condition
if (score < 0) {
isGameActive = false;
LK.showGameOver();
}
// Check for win condition (1000 points to win)
if (score >= 1000) {
// Only show win screen once
if (isGameActive) {
isGameActive = false;
// Flash screen with celebration colors
LK.effects.flashScreen(0xFFD700, 400); // Gold flash
// Create coin shower effect for winning celebration
LK.setTimeout(function () {
for (var i = 0; i < 40; i++) {
var coin = coins[Math.floor(Math.random() * coins.length)];
if (!coin.active) {
// Random positions for coins
var startX = Math.random() * 2048;
var startY = -100;
var destX = Math.random() * 2048;
var destY = Math.random() * 2732;
coin.animate(startX, startY, destX, destY);
}
}
// Show win screen immediately after coin effect starts
LK.showYouWin();
}, 300);
}
}
};